You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bug Description
security-guidance read secret files in to the context while I was editing them and before I was able to encrypt them.
Claude should never read secrets in clear into its context. I have several guardrails for that, however the plugin bypassed them all.
I traced the current public implementation to narrow down which path needs fixing. The per-edit handler runs local pattern checks; the Stop handler reviews a Git diff, while the commit reviewer can use Read/Grep/Glob over the repository. These are different paths, so the report alone does not yet establish which one bypassed your guardrails.
For reference, the public commit reviewer sets setting_sources=[]: review options. That is a lead to investigate, not confirmation that it caused this incident.
Could you share the following using dummy values only, with no real secrets or private file contents?
The installed security-guidance version and marketplace.
Whether the read happened after an Edit/Write, when the turn stopped, or after a Git commit; a redacted hook/event name would help.
The guardrail type: a permissions.deny rule, a hook, or an instruction. A sanitized rule using a dummy path is enough.
A minimal temporary-repository example with a fake marker, plus where that marker appeared (main conversation, review output, or logs).
This should distinguish direct diff collection from the separate review agent's file access before choosing a fix. No need to upload your original files. Source inspection prepared with AI assistance; I have not reproduced your reported incident.
Thanks for the pointers @7487. I reproduced it with a throwaway repository and a fake marker. Everything below uses dummy values only.
Environment
security-guidance 2.0.8 from claude-plugins-official (commit 2cd88e79), user scope
Claude Code 2.1.280, macOS
In this install the Agent SDK import fails (agentic_review: SDK unavailable (No module named 'pydantic_core._pydantic_core'); falling back), so the reviews below ran on the single-shot fallback path, not the agentic Read/Grep/Glob reviewer.
Guardrail type
No permissions.deny rule. The guardrail is a user-level PreToolUse, PostToolUse hook matched on Read, Write and Bash that blocks reading files containing secrets using keywords, Shannon entropy and heuristics, together with instructions not to read secrets. None of these get a chance to fire here: the secret never goes through a tool call in the main session. It arrives as Stop-hook feedback.
Minimal repro
git init repro &&cd repro
printf'# repro\n'> README.md
cat > mk.sh <<'EOF'#!/usr/bin/env bash# Writes a dummy secret; the value never appears on the command line.printf 'db:\n host: db.example.invalid\n password: %s\n' "SG_FAKE_MARKER_${1}_$(printf 7f3a91)" > secrets.ymlEOF
chmod +x mk.sh && git add .&& git commit -qm init
echo"Run exactly: ./mk.sh A2 Then reply: done" \
| claude -p --model haiku --output-format stream-json --verbose --allowedTools "Bash(./mk.sh:*)"
mk.sh stands in for "the user is editing a plaintext secrets file in the working tree before encrypting it". The main agent only ever sees the command ./mk.sh A2, never the file content.
Where the marker appeared
Location
Marker present?
Main conversation (transcript and stream-json), as a Stop hook hook_blocking_error / "Stop hook feedback" user message
Not captured directly (fallback path). The debug log shows Stop hook: review_set=1 … reviewing 1 changed files (standard diff), and the finding quotes the value, so the file content was in the review input.
Path: the Stop hook. An untracked secrets.yml modified during the turn becomes part of the review set (.yml is in SOURCE_CODE_EXTENSIONS). Its full content goes to the review model, and the finding text quotes the secret value back into the main conversation.
The leak is non-deterministic: an identical run before it reported "no vulnerabilities found", and then nothing reached the main conversation. The value is still sent to the review model in both cases.
With .env instead of .yml there was no review at all (no source code files in diff).
A commit variant (./mk.sh B && git add -A && git commit) ran the commit review and then the Stop review (both fallback). Neither reported a finding, so the marker did not appear in the main conversation in that run.
Suggested fix direction
Never echo matched secret values in findings. Redact them (e.g. password: ****91) before building the Stop-hook feedback.
Consider excluding untracked files that look like secret stores (*secret*, *.tfvars, SOPS-candidate YAML) from the diff sent to the reviewer, or redacting high-entropy values before sending.
Independently of the fallback path: the agentic reviewer runs with setting_sources=[], so a user's PreToolUse Read guards would not apply to its Read/Grep/Glob either. I could not exercise that path here because the SDK import fails.
Thanks for reducing this to a throwaway repo and dummy values. This clarifies that your observed Stop case uses custom tool hooks, not a permissions.deny rule. The earlier setting_sources=[] concern is a separate agentic-review path. In the current public code, Stop is deliberately single-shot, so an SDK import failure is not required for this Stop path.
I checked public main d78be948 and draft #96434 at 3bd80115, using isolated Git repositories with untracked files and fake markers. I ran the real get_git_diff → parse_diff_into_files → analyze_code_security functions, intercepting the model-call boundary; no real model or user hook was run. Results:
The .env control was excluded in all four cases. I also ran the PR's 14 unittest tests successfully.
This means the PR's default secret-filename filter covers the secrets.yml input in your repro, independently of permissions.deny. It does not replay arbitrary content-based user hooks, and the finding formatter still emits vulnerableCode/fix without new redaction. A synthetic finding quoting a marker in the remaining ordinary-named config file still appears in the formatted feedback; that checks the formatting boundary, not whether a real model would produce that finding.
For #96434, the no-deny-rule Stop case and this custom-hook/output-redaction boundary are worth keeping explicit in the tests and documentation. The PR is still a draft; I have not verified native CLI/model end-to-end behavior or the reporter's marketplace build. These checks were prepared with AI assistance.
Activity
7487 commentedon Sep 23, 2026
I traced the current public implementation to narrow down which path needs fixing. The per-edit handler runs local pattern checks; the Stop handler reviews a Git diff, while the commit reviewer can use Read/Grep/Glob over the repository. These are different paths, so the report alone does not yet establish which one bypassed your guardrails.
For reference, the public commit reviewer sets
setting_sources=[]: review options. That is a lead to investigate, not confirmation that it caused this incident.Could you share the following using dummy values only, with no real secrets or private file contents?
permissions.denyrule, a hook, or an instruction. A sanitized rule using a dummy path is enough.This should distinguish direct diff collection from the separate review agent's file access before choosing a fix. No need to upload your original files. Source inspection prepared with AI assistance; I have not reproduced your reported incident.
Frisch12 commentedon Sep 23, 2026
Thanks for the pointers @7487. I reproduced it with a throwaway repository and a fake marker. Everything below uses dummy values only.
Environment
2.0.8fromclaude-plugins-official(commit2cd88e79), user scopeagentic_review: SDK unavailable (No module named 'pydantic_core._pydantic_core'); falling back), so the reviews below ran on the single-shot fallback path, not the agentic Read/Grep/Glob reviewer.Guardrail type
No
permissions.denyrule. The guardrail is a user-levelPreToolUse,PostToolUsehook matched onRead,WriteandBashthat blocks reading files containing secrets using keywords, Shannon entropy and heuristics, together with instructions not to read secrets. None of these get a chance to fire here: the secret never goes through a tool call in the main session. It arrives as Stop-hook feedback.Minimal repro
mk.shstands in for "the user is editing a plaintext secrets file in the working tree before encrypting it". The main agent only ever sees the command./mk.sh A2, never the file content.Where the marker appeared
Stophookhook_blocking_error/ "Stop hook feedback" user message1. [HIGH] [Hardcoded Secrets] password: SG_FAKE_MARKER_A2_7f3a91SECURITY_GUIDANCE_DEBUG_LOG)Stop hook: review_set=1 … reviewing 1 changed files (standard diff), and the finding quotes the value, so the file content was in the review input.Relevant debug log lines:
Observations
secrets.ymlmodified during the turn becomes part of the review set (.ymlis inSOURCE_CODE_EXTENSIONS). Its full content goes to the review model, and the finding text quotes the secret value back into the main conversation..envinstead of.ymlthere was no review at all (no source code files in diff)../mk.sh B && git add -A && git commit) ran the commit review and then the Stop review (both fallback). Neither reported a finding, so the marker did not appear in the main conversation in that run.Suggested fix direction
password: ****91) before building the Stop-hook feedback.*secret*,*.tfvars, SOPS-candidate YAML) from the diff sent to the reviewer, or redacting high-entropy values before sending.setting_sources=[], so a user'sPreToolUseRead guards would not apply to its Read/Grep/Glob either. I could not exercise that path here because the SDK import fails.7487 commentedon Sep 23, 2026
Thanks for reducing this to a throwaway repo and dummy values. This clarifies that your observed Stop case uses custom tool hooks, not a
permissions.denyrule. The earliersetting_sources=[]concern is a separate agentic-review path. In the current public code, Stop is deliberately single-shot, so an SDK import failure is not required for this Stop path.I checked public main
d78be948and draft #96434 at3bd80115, using isolated Git repositories with untracked files and fake markers. I ran the realget_git_diff→parse_diff_into_files→analyze_code_securityfunctions, intercepting the model-call boundary; no real model or user hook was run. Results:secrets.ymlmarker in review promptruntime-config.ymlmarker in review promptSG_SKIP_SECRET_FILES=0Read(./runtime-config.yml)denyThe
.envcontrol was excluded in all four cases. I also ran the PR's 14 unittest tests successfully.This means the PR's default secret-filename filter covers the
secrets.ymlinput in your repro, independently of permissions.deny. It does not replay arbitrary content-based user hooks, and the finding formatter still emitsvulnerableCode/fixwithout new redaction. A synthetic finding quoting a marker in the remaining ordinary-named config file still appears in the formatted feedback; that checks the formatting boundary, not whether a real model would produce that finding.For #96434, the no-deny-rule Stop case and this custom-hook/output-redaction boundary are worth keeping explicit in the tests and documentation. The PR is still a draft; I have not verified native CLI/model end-to-end behavior or the reporter's marketplace build. These checks were prepared with AI assistance.
security-guidance: mask credential values in review feedback and sess…