Skip to content

fix(agent): harden narrationDirective against secret leakage - #211

Merged
Desperado merged 1 commit into
mainfrom
fix/narrate-redaction
Sep 26, 2026
Merged

Desperado merged 1 commit into
mainfrom
fix/narrate-redaction

Conversation

@Desperado

Copy link
Copy Markdown
Contributor

Summary

  • Addresses Copilot's HIGH-severity finding on feat(config): narrate_tool_calls knob for CLI agent transcript #210: the `narrationDirective` `full` mode asked the subprocess agent to echo commands and outputs verbatim, which can leak API tokens, bearer/session cookies, env-var values, and connection strings into the terminal transcript.
  • Adds a shared REDACTION clause to every mode (off/brief/full + "bogus"→brief fallback). Names the secret classes explicitly, specifies substitution with ``, and forbids quoting `env` / `printenv` / `.env*` output in narration.
  • Blocks the v1.37.0 release; without this, users who set `narrate_tool_calls full` inherit the leak.

Directive change

```go
// internal/agent/cc_agent.go — every mode now suffixes this shared clause
const redaction = " REDACTION (applies to any text you narrate — command, snippet, output, error): before printing, replace secrets with ``. Treat as secret: API keys, bearer/OAuth/session tokens, passwords, private keys, `Authorization:` and `Cookie:` header values, connection strings (postgres://user:pass@…, redis://…), `--token=`/`--api-key=`/`--password=`/`--secret=` flag values, `AWS_` / `GITHUB_TOKEN` / `ANTHROPIC_API_KEY` / other `_KEY` / `_TOKEN` / `_SECRET` env-var values, webhook signing secrets, and anything the surrounding text calls a key/token/secret. When in doubt, redact. Never quote raw `env` / `printenv` / `railway variables` output or `.env*` file contents in narration."
```

Why cover `off` too — that mode still says "only narrate on failure", and failure output is exactly where credentials tend to appear (401 responses, curl -v dumps, env listings from a broken test).

Guard test

```go
func TestNarrationDirectiveCarriesRedactionClause(t *testing.T) {
mustName := []string{
"REDACTION", "", "API keys", "bearer", "token",
"password", "private key", "Authorization:", "Cookie:",
"connection string", "--token=", "--api-key=", "env-var",
"webhook signing", "env", "printenv", ".env",
}
for _, mode := range []string{"off", "brief", "full", "bogus"} { ... }
}
```

Future edits that thin the directive without replacing the guard fail loudly.

Test plan

  • `go build -o qmax-code .`
  • `go test ./...` — 18 packages ok (agent 7.129s, tui, api, repl…)
  • Follow-up smoke: set `narrate_tool_calls full`, run an `/orch` task that touches an authenticated CLI (`gh auth status`, `aws sts get-caller-identity`, etc.), confirm the transcript shows `` instead of the token.

Release gate

Merging this unblocks `release: v1.37.0` — the release PR follows immediately after.

…210)

Copilot flagged HIGH-severity credential-exposure risk on PR #210: the
`full` narration directive asked the subprocess agent to echo commands
and output verbatim, which can carry API keys (argv `--token=`), bearer
tokens (`Authorization:` headers), env-var values from a `printenv`
call, connection strings, private keys, etc.

Every mode (off/brief/full — including the "bogus"→brief fallback) now
carries a shared REDACTION clause that:

- names the secret classes to substitute (API keys, bearer/OAuth/session
  tokens, passwords, private keys, Authorization/Cookie header values,
  connection strings, --token=/--api-key=/--password=/--secret= flag
  values, AWS_*/GITHUB_TOKEN/ANTHROPIC_API_KEY/*_KEY/*_TOKEN/*_SECRET
  env-var values, webhook signing secrets),
- specifies the substitution (`<REDACTED>`, not omission),
- forbids quoting `env`/`printenv`/`railway variables` output or `.env*`
  file contents in the narration channel,
- covers `off` mode too because "only narrate on failure" is exactly
  where secrets tend to appear (401 responses, curl -v dumps).

Subprocess agents (Codex CLI, Antigravity, opencode-selected models) do
not inherit qmax-code's own secret-handling rules from CLAUDE.md, so the
guard is spelled out in the injected system prompt.

TestNarrationDirectiveCarriesRedactionClause asserts each mode names the
key secret classes by string — a future edit that thins the directive
without replacing the guard fails loudly.
@qualitymaxapp

qualitymaxapp Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

QualityMax Review

Verdict: COMMENT · Confidence: evidence-backed scan

Files eligible: 2 · Files reviewed: 2 · Files with findings: 0 · Findings: 0 · Inline cards: 0

Priority findings

priority location finding
— — No blocking findings

Review gates

gate status
AI diff review completed · eligible 2, reviewed 2 · LLM · served gemini-3.1-flash-lite
SAST completed · eligible 2, reviewed 2 · hybrid · served qwen3.7-plus
Overall review evidence clean
Inline evidence not needed

Important files

file risk note next step
— — No findings —

Review lifecycle

Use the inline cards to inspect evidence and suggested remediation. Re-run the QualityMax review after pushing a fix; unchanged cards are identified by their stable finding marker. Dismiss with a reason through the existing QualityMax/GitHub review feedback flow. 0 prior card(s) are stale/resolved on this head. @qmax Q&A is tracked separately.

Proof legend: VERIFIED independently judged patch · REPRODUCED verified finding · GROUNDED deterministic evidence · MODEL-ONLY model judgment.

QualityMax project results are available in the configured project.

Receipt · commit 6afc0896baaef21c44e9c350a7d6d438a3463e63 · run 2026-09-26T21:56:36+00:00 · model served qwen3.7-plus, gemini-3.1-flash-lite · model requested qwen3.7-plus, gemini-3.1-flash-lite · model review recorded — 182 model output tokens · model source repository ai_review_preferences.preferred_model · re-review 2 · proof counts {}

@qualitymaxapp qualitymaxapp Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QualityMax Review — canonical overview updated; inline findings are attached to this review.

@qualitymaxapp

qualitymaxapp Bot commented Sep 26, 2026

Copy link
Copy Markdown

⚠️ QualityMax Pipeline

Gate Result
🔍 AI diff review ✅ Clean · gemini-3.1-flash-lite · completed · 2 eligible / 2 reviewed · gemini-3.1-flash-lite
🔍 SAST completed · 2 eligible / 2 reviewed · qwen3.7-plus
🔍 Canonical PR review delivery completed · 0 eligible / 0 reviewed · exact-head review #5327618075 and overview #5850218804 confirmed
🧪 Repo Tests ✅ 844/844 passed (go)

Powered by QualityMax — AI-Powered Test Automation

@Desperado
Desperado merged commit 4b35ce2 into main Sep 26, 2026
6 checks passed
@Desperado
Desperado deleted the fix/narrate-redaction branch September 26, 2026 21:58
@Desperado Desperado mentioned this pull request Sep 26, 2026
2 of 3 tasks
Desperado added a commit that referenced this pull request Sep 26, 2026
* release: v1.37.0

Ship the Opus 5.5 picker refresh (#209), the narrate_tool_calls
transcript-verbosity knob (#210), and the narration secret-redaction
hardening (#211).

* release: bump CHANGELOG date to 2026-09-27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant