Pular para conteúdo

AUR-432 — Revisar código privado sem vazar segredo

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

aurumcode review --base <ref> reviews private code that may contain credentials. This card wires the single AUR-009 redaction filter (internal/security/redaction) into every path a secret could take out of that command: a secret present in the reviewed diff never reaches the model, and a secret echoed back by the model never reaches stdout, stderr, the prompt capture, or the report. The finding that cites the secret's line still arrives, because the filter replaces values, not context.

Command

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

Flags, output format, and exit codes are exactly those published by AUR-430 (docs/specs/AUR-430.md), AUR-431 (--fail-on), and AUR-436 (--modelo). This card changes none of them: for a secret-free review, stdout and stderr are byte-identical to the previous behavior.

Exit codes

  • 0 — the review ran; findings (if any) printed.
  • 1 — the review itself failed (provider, parse, repository error).
  • 2 — usage error.
  • 3 — --fail-on gate closed (AUR-431).

Redaction never changes an exit code: it sanitizes content, it does not judge it.

The three wiring points

  1. Send path (internal/review/reviewer.go): the diff — the untrusted material — is redacted before the prompt is assembled (redactDiff). Composition matters, not just coverage: a diff line carries a +/-/space marker and the filter's header rule is anchored at line start, so redacting the assembled prompt would let +Authorization: Bearer x through verbatim. Each line's marker is stripped, the bodies are redacted together (anchored header rules and the multi-line private-key rule both apply), and the markers are re-attached; a collapsed multi-line secret fails closed without markers. Secret values become [REDACTED]; key names, header names, file paths and line structure survive, so the model still sees that a credential sits on a line and can report it usefully.
  2. Model output boundary (internal/review/reviewer.go): the parsed response — untrusted model output that may echo a secret — is redacted field by field (file, message, suggestion, summary, comments) at the boundary. Prose fields get the same marker-stripping treatment, because a model quoting the offending diff line echoes it marker and all. This runs before the AUR-434 rule-citation suffix is appended: the suffix (rule security/hardcoded-secret: <title>) comes from the trusted embedded catalog and would itself be rewritten by the filter's secret: value rule, so redacting first is what keeps the published output byte-stable for secret-free reviews.
  3. Process sinks (cmd/aurumcode/main.go): stderr writes through redaction.NewWriter (every canonical stderr line is filter-identity, pinned by TestStderrLinesSurviveTheRedactionWriter); diff-derived notices pass Filter.Redact before stdout. The --modelo selection note masks an LLM_BASE_URL userinfo password at the origin with url.Redacted() (redactedEndpoint), and the stderr writer replaces the whole userinfo component on top of that.

The AURUM_SECRET_CANARY environment value, when present, is registered as an exact-match secret (redaction.FromEnv) in both the reviewer's filter and the CLI's sink filter. There is no response cache in this path (llm.NewOrchestrator(provider, nil, nil)); the only persisted prompt artifact is the opt-in capture below, which is written after redaction.

Observing the guarantee: AURUMCODE_PROMPT_CAPTURE

With the deterministic offline provider (AURUMCODE_LLM_FIXTURE), setting AURUMCODE_PROMPT_CAPTURE=<file> makes the provider write the exact prompt it received to <file> (mode 0600) before answering. That is the observability hook the acceptance uses to prove nothing secret leaves the process — with the wiring in place the captured prompt is already redacted. Live providers have no equivalent; the wire-level proof for them is tests/integration/AUR-432.go, which records the raw bytes a loopback endpoint receives.

Offline, secret-free example

cd "$(mktemp -d)"
cat > response.json <<'EOF'
{
  "issues": [
    {
      "file": "config/demo-tokens.txt",
      "line": 4,
      "severity": "error",
      "rule_id": "security/hardcoded-secret",
      "message": "A hardcoded credential is committed in plain text at this line."
    }
  ],
  "summary": "The change commits a plaintext credential."
}
EOF
cd <repo-under-review>
AURUMCODE_LLM_FIXTURE=$OLDPWD/response.json \
AURUMCODE_PROMPT_CAPTURE=$OLDPWD/prompt.txt \
aurumcode review --base HEAD~1
# prompt.txt now holds what the model was shown: values [REDACTED],
# context intact. Every value in this example is synthetic.

Failure diagnosis

  • A planted value found in prompt.txt, stdout, or stderr is the behavioral RED this card guards against (AUR-432/AC-001/behavior-missing in the acceptance vocabulary).
  • tests/acceptance/AUR-432.sh AC-001-MUT-001 proves the guard bites at both points: disabling the send-path diff redaction makes the planted secret appear in the capture, and disabling the output-boundary redaction makes the model-echoed secret appear on stdout; either way the accept fails, and restoring reproduces the GREEN.
  • The e2e (tests/e2e/AUR-432.sh) also pins that the AUR-437 fixture builder refuses credential-shaped content (git-demo/non-synthetic-secret), which is why credential shapes are exercised in-process by tests/unit/AUR-432.go with runtime-assembled values, never through tracked files.

Known residual notes (review 2026-08-13, non-blocking)

  • internal/llm/httpbase/client.go logs via log.Printf (default stderr), bypassing the CLI's stderr redaction writer. Today those lines carry only method/path/provider names, and httpbase's own RedactSecret truncates response bodies; wiring log.SetOutput to the redaction writer belongs to whichever card next touches httpbase.
  • AURUMCODE_PROMPT_CAPTURE is a truncating file write gated by an environment variable in shipped code. It is confined to the offline FakeProvider (live providers have no capture path) and, with the wiring in place, the captured prompt is already redacted.

Validation

  • Unit: tests/unit/AUR-432.go::TestAUR432 (send path, private-key block, output boundary, determinism — in process).
  • Integration: tests/integration/AUR-432.go::IntegrationAUR432 (real binary; offline capture and wire-level loopback proof; userinfo note).
  • E2E: tests/e2e/AUR-432.sh::E2EAUR432 (runtime-planted secrets in a rebuilt fixture repository, full user journey).
  • Acceptance: tests/acceptance/AUR-432.sh (AC-001, byte-pinned published formats, MUT-001).