gosec findings and dated exceptions
The Security stage rejects gosec findings unless an existing baseline or a live, per-file
exception covers them. Resolve each finding by fixing the code, adding a justified
#nosec Gxxx -- reason comment at its line, or declaring a dated exception in .standards.yaml.
The scanner configuration stays .gosec.json; exceptions do not change its enabled rules.
Record an existing baseline
praetorctl baseline --record refuses a first .gosec-baseline.json when findings exist.
It compares the scan with the baseline on the repository's default branch, resolved by
forge.RepositoryDefaultBranch: the manifest declaration, the recorded origin HEAD, then
main. A present origin tracking ref takes precedence over the local branch. No network fetch
runs; refresh the checkout's refs before recording when they are out of date.
An existing baseline can keep or reduce its count. A hand-raised working-tree baseline cannot
increase the default branch's allowance. The working-tree baseline also cannot grow after it
has shrunk. The flags --allow-gosec-increase and --gosec-reason remain parseable for command
compatibility, but --allow-gosec-increase is refused. HISS baseline increase flags retain their
separate behavior. A clean first scan creates no gosec baseline.
These checks live in cmd/standardsctl/gosec_baseline_guard.go; the planted fixture tests are
TestGosecFirstBaselineGuard* in cmd/standardsctl/gosec_baseline_guard_test.go.
Adoption writes .gosec.json, but does not write a gosec baseline.
Declare one file's exception
exceptions:
- rule: gosec
path: internal/legacy/command.go
gosec_rules: [G204]
reason: Replace the legacy command interface before this exception expires
category: C
entered: 2026-10-23
expires: 2026-11-02
The strict manifest schema is config.Exception in internal/config/exceptions.go.
gosec_rules requires distinct IDs, each an uppercase G followed by exactly three digits.
The entry requires one clean repository-relative path with forward slashes; globs are refused.
It also requires a reason, explicit category, entry date and expiry. Category B allows at most
3 days from entered, C allows 10, D allows 120, and A uses the stated interval up to 90 days. Existing
category and date validation applies (internal/config/gosec_exceptions.go and
tools/secrets/rules). See the exception fields.
Security verdict
Security runs gosec first, then removes findings whose path and rule ID match a live entry
before applying the baseline ratchet. Expiry is inclusive: an entry still applies on its
expires day; the following day it excuses nothing. Remaining findings face the same ratchet
as findings without an exception.
The stage message names each applied entry, its path, rule IDs, reason, expiry and count of excused findings. An entry matching no finding fails as stale and names the entry to remove. Staleness is checked after all Go modules, so an exception for a nested module is not judged against only the root module's scan. Removing an entry restores the finding to the ratchet.
internal/gating/gosec_exceptions.go implements the filter and stage accounting.
TestGosecExceptions_FrozenStage supplies frozen scanner output and checks live, removed,
expired, stale, wrong-path, wrong-rule and expiry-day cases. The runner rejects unexpected
commands, so the test does not depend on downloading or executing security scanners.