Pular para conteúdo

AUR-450 — aurumcode review --seguranca: the pass names what it searched

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

Command

aurumcode review --base <ref> [--fail-on <level>] [--modelo <nome>] [--seguranca]

Same command AUR-435/AUR-442/AUR-449 published. This card adds no flag and changes no byte of stdout; it adds one line to stderr, printed only when --seguranca runs.

The measured defect (2026-08-14 re-test)

Code carrying hashlib.md5(password) (security/weak-crypto) and "<b>" + name + "</b>" (security/xss) gets Security findings (standards/security-review): No security findings. with exit 0. Five of the embedded catalog's eight security-category rules are metadata only (internal/review/rules/security.yml) — AUR-442 gave three of them (sql-injection, command-injection, hardcoded-secret) a real matcher and documented, scoped reasons for leaving the other five patternless — but the CLI never says which rules ran. A user reads the silent No security findings. as full coverage. This is not a matcher false positive or false negative; it is the output omitting its own scope.

The decision

--seguranca now prints, on stderr, exactly one additional line: how many and which security-category rules of the embedded catalog actually carry a matcher, against how many the category declares in total. The figures are derived from the catalog at run time (internal/review.RulesLoader.AppliedInCategory, cmd/aurumcode/main.go's printSecurityCoverage), never a hand-written list — a list typed once into a doc or a stderr format string would silently go stale the next time a card adds, removes, or gives a rule a matcher (internal/review/rules/ security.yml's own pattern: field is the single source of truth; nothing here duplicates it). tests/acceptance/AUR-442.sh's own MUT-001 case (which removes security/hardcoded-secret's matcher) demonstrates the derivation empirically: rerun on this card's tree, the note recomputes to "2 of 8" without a single line of AUR-450 code changing.

The note prints before the findings are known and is catalog-derived, not diff-derived — it does not depend on what the reviewed diff contains. That is deliberate: the empty case ("No security findings.") and the non-empty case must carry the byte-identical note, because the defect this card fixes is specifically the empty case being misread as full coverage.

Why stderr, not stdout

tests/acceptance/AUR-449.sh pins an exact sha256 of git-demo's --seguranca stdout with a configured provider (63c649af1c90e38b473e1bd45b4152b1f96ecad17d5d9c05c17bb94df7b8240f) as a regression guard. That pin is not this card's to break: internal/review and cmd/aurumcode are this card's paths, but the byte contract AUR-449 already published on that exact command is not a "format" this card is free to redefine — it is exactly the kind of already-published contract the card's own Compatibility section requires to stay inalterado. Any new byte on that pinned stdout — even a single trailing character — changes the hash and turns a passing sibling acceptance red for a reason unrelated to what AUR-449 actually tests.

This codebase already has an established idiom for exactly this situation — additive, caller-facing information that must not touch a byte-pinned stdout contract goes to stderr, prefixed aurumcode review:, the same sink and prefix AUR-433's cost lines, AUR-441's cache-reuse note, AUR-448's discard warning, and AUR-449's skip explanation already use. The coverage note follows that convention instead of inventing a new one. It is filter- identity under the AUR-432/AUR-009 redaction writer exactly like every other canonical stderr line this command publishes (pinned by cmd/aurumcode/redaction_wiring_test.go's TestStderrLinesSurviveTheRedactionWriter, which now includes this note verbatim) — the rule-id tokens it names (e.g. security/hardcoded-secret) never sit immediately before a bare :/= value, the shape the filter's secret-key rules require to trigger, so nothing in the note is ever rewritten or masked.

tests/acceptance/AUR-450.sh's nominal_case re-asserts the exact same AUR-449 stdout sha256, byte for byte, to prove this card's whole point: new information reaches the user without disturbing a single byte of a contract this card does not own.

Output shape

--seguranca, no provider, findings present (git-demo, config/demo-tokens.txt plants three secrets AUR-442 already teaches the pass to find):

Security findings (standards/security-review):
config/demo-tokens.txt:4: [error] Hardcoded credentials or API keys detected [standards/security-review SCR-003] (rule security/hardcoded-secret: Hardcoded Secrets)
config/demo-tokens.txt:5: [error] Hardcoded credentials or API keys detected [standards/security-review SCR-003] (rule security/hardcoded-secret: Hardcoded Secrets)
config/demo-tokens.txt:6: [error] Hardcoded credentials or API keys detected [standards/security-review SCR-003] (rule security/hardcoded-secret: Hardcoded Secrets)

stderr (new under this card, printed before the findings are known):

aurumcode review: security pass applied 3 of 8 security rules (security/command-injection, security/hardcoded-secret, security/sql-injection); see internal/review/rules/security.yml for the full catalog

--seguranca, nothing matches (e.g. --base HEAD against itself, an empty diff): stdout prints the honest absence, exactly as AUR-449 already publishes it —

Security findings (standards/security-review):
No security findings.

— and stderr carries the byte-identical coverage note above: the figures are catalog-derived, not diff-derived, so the empty case can never be told apart from the non-empty case by looking at the note alone. That identity is exactly what stops "No security findings." from being misread as "the whole catalog ran."

--seguranca, a provider configured: stdout is unaffected — the exact AUR-442/AUR-449 contract, sha256-pinned by both tests/acceptance/AUR-449.sh and this card's own acceptance program — and the same coverage note appears on stderr.

Without --seguranca: no coverage note anywhere, on either stream — the note is scoped to the security pass, never printed on a plain quality review.

With --modelo unavailable: the command still fails loudly (reportModelUnavailable, AUR-436) before --seguranca is ever reached, so neither the security section nor the coverage note prints.

Exit codes

Unchanged from AUR-435/AUR-442/AUR-449 (docs/specs/AUR-449.md). This card adds no new exit code and changes no exit code's meaning; only stderr grows one line, and only when --seguranca runs.

Offline, secret-free example

cd tests/fixtures/repos/git-demo/repo.git
unset AURUMCODE_LLM_FIXTURE LLM_API_KEY LLM_BASE_URL
aurumcode review --base HEAD~1 --seguranca

prints, with exit 0, on stdout (unchanged from AUR-442/AUR-449):

Security findings (standards/security-review):
config/demo-tokens.txt:4: [error] Hardcoded credentials or API keys detected [standards/security-review SCR-003] (rule security/hardcoded-secret: Hardcoded Secrets)
config/demo-tokens.txt:5: [error] Hardcoded credentials or API keys detected [standards/security-review SCR-003] (rule security/hardcoded-secret: Hardcoded Secrets)
config/demo-tokens.txt:6: [error] Hardcoded credentials or API keys detected [standards/security-review SCR-003] (rule security/hardcoded-secret: Hardcoded Secrets)

and on stderr (the AUR-449 skip note, since no provider is configured, followed by this card's new coverage note):

aurumcode review: no LLM provider configured: quality review skipped, running --seguranca only (...)
aurumcode review: security pass applied 3 of 8 security rules (security/command-injection, security/hardcoded-secret, security/sql-injection); see internal/review/rules/security.yml for the full catalog

Failure diagnosis

  • The security section says No security findings. and you are unsure whether the pass actually looked for what you expected — check the coverage note on stderr: it names every rule the pass can match, by id, every single run. A rule not in that list is metadata only (internal/review/rules/security.yml has no pattern: for it) and will never fire regardless of what the diff contains — see docs/specs/AUR-442.md's "The decision" for why each of the five stayed patternless.
  • No coverage note anywhere — either --seguranca was not given, or an earlier failure (a broken rules catalog, an unavailable --modelo) stopped the command before the security pass was reached; check the exit code and the rest of stderr.
  • aurumcode review: review rules unavailable: ... — the embedded rules catalog is broken; fix internal/review/rules/ and rebuild. Both the findings and the coverage note come from the same catalog load, so this failure blocks both, before either prints anything.

Security

  • No new sink: the coverage note is trusted catalog text (rule ids, a literal count, a literal repository-relative path to the catalog file) — never diff content, never a matched value, never a secret. It is filter- identity under the redaction writer (see "Why stderr, not stdout" above).
  • The AURUM_SECRET_CANARY value never reaches stdout, stderr, prompt, cache, or evidence alongside the new note; tests/acceptance/AUR-450.sh asserts this.

Compatibility

  • review --base without --seguranca, with --modelo, or with a configured provider is byte-identical to the already-published contract on both stdout and stderr: tests/acceptance/AUR-442.sh, tests/acceptance/AUR-443.sh, tests/acceptance/AUR-448.sh, and tests/acceptance/AUR-449.sh all pass unchanged against this card's tree (re-verified, exit 0 each, as part of this card's own validation).
  • tests/acceptance/AUR-449.sh's exact stdout sha256 pin for --seguranca with a provider configured is re-asserted, unchanged, by this card's own tests/acceptance/AUR-450.sh.
  • --fail-on's gate composition (docs/specs/AUR-442.md) is unaffected: it still gates on result.Issues plus the security findings, which this card does not touch — only the coverage figures are new, and they never enter the gate.
  • The findings themselves — content and order — are exactly what securityScanWithRules already produced; SecurityScanWithCoverage (the new entry point cmd/aurumcode calls) computes them with the exact same unexported helper SecurityScan uses, so the two can never diverge. SecurityScan itself is untouched: tests/unit/AUR-435.go and tests/unit/AUR-442.go keep calling it with its original two-value signature.

Validation

tests/acceptance/AUR-450.sh AC-001 (sealed: ./.board/bin/oci-run --profile bootstrap-readonly-v1 --card AUR-450), with selectors TestAUR450 (tests/unit/AUR-450.go), IntegrationAUR450 (tests/integration/AUR-450.go), E2EAUR450 (tests/e2e/AUR-450.sh) and AC-001-MUT-001 (silencing the coverage note's print call while leaving the security pass itself untouched, which must make the acceptance fail — internal/review.SecurityScanWithCoverage still returns the right figures; only cmd/aurumcode's print of them is suppressed).

Selector naming note (the same technique tests/unit/AUR-442.go and tests/unit/AUR-449.go document): the card text's TDD proof section names TestAUR435 / IntegrationAUR435 / E2EAUR435, copy-paste artifacts from the AUR-435 card this one was drafted against. Those symbols already exist in their packages (tests/unit/AUR-435.go, tests/integration/AUR-435.go) and TestAUR449/IntegrationAUR449 already occupy the next-available names too, so this card's proofs are TestAUR450 / IntegrationAUR450 / E2EAUR450 instead.