Skip to content

v0.3.5 — a credential leak in staged-secret-guard

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 25 Aug 10:19
· 52 commits to main since this release
caa07a9

staged-secret-guard could let a staged credential through. If you use git commit -u, update.

claude plugin update guard-hooks@cc-agents-kit

Then restart open sessions. Affected releases are 0.3.2, 0.3.3, and 0.3.4; earlier ones do not carry the defect.

The leak

The guard's git commit flag table put -u / --untracked-files among the options that consume the next token as their value. git defines them as -u[<mode>] — the value is attached and optional, never a separate token.

So on git commit -u -m "fix: x" the guard read -m as -u's value and fix: x as a pathspec. It built a commit candidate for a path that does not exist, scanned nothing, and exited 0. Real git meanwhile parsed both flags correctly and committed the staged content. Confirmed end to end in a throwaway repository: exit 0, and the token sitting in HEAD.

-S / --gpg-sign carries the same optional-attached shape and is now handled with it. Both the separate-token forms and the attached forms (-uall, -S<key-id>) are covered by regression cases that exit 0 against the previous table.

The false block

git commit -q was refused as unparseable. -q / --quiet was simply absent from the no-value flag list, so it fell to the catch-all that blocks an unrecognised flag — a benign and extremely common flag failing closed. It is added along with the other no-value flags the table had omitted, each read off git commit's own option list rather than recalled: -z / --null, --verify, --no-signoff, --reset-author, --allow-empty, --allow-empty-message, --status / --no-status, --no-gpg-sign.

What stays refused, on purpose

  • Flags that change which content is committed: -i, -p, --interactive, --pathspec-from-file.
  • -e / --edit. It parses fine, but it opens $EDITOR against a shell with no TTY, so admitting it would trade a millisecond refusal for a hung tool call.
  • Bundled short flags (-sq) and anything unrecognised. The table is an allowlist because an unknown flag might take a value, and mis-parsing one is exactly how the leak above happened. The README's known-limits section now says so, rather than implying the list is exhaustive.

Verification

Every new case distinguishes "parsed, scanned, found the credential" from "refused to parse" — both exit 2, so the exit code alone proves nothing.

Coverage was checked by mutation, with none skipped. Four mutations, four caught: restoring -u to the value-consuming list, dropping -q, dropping the attached-value arm, and widening the list far enough to admit -p.

Versions

guard-hooks 0.2.3. context-handoff 0.1.2 and repo-gate 0.1.4 are unchanged.