Conversation
Every sensitive-path pattern anchors its boundary on `/`, so a tool-provided Windows path such as `C:\Users\me\project\.env` matched none of them and read straight through the guard: .env, .envrc, SSH keys, AWS/kube/gcloud/azure and docker credentials, .netrc/.npmrc/.pypirc and the rest were all unprotected there whatever the safety level. Matching now runs on a separator-normalized copy of the path, so one helper covers every present and future pattern instead of 25 anchors. The hook only reads the path, never opens it, so the rewrite stays local to matching and `.env.example`-style templates keep their allowlist. The plugin's hooks.json matcher also left Grep out, so the search handling in check() could not run for a real call and searching a .env for a key value was never inspected. The matcher is now derived from the HANDLED_TOOLS list the hook branches on, and a test pins the two together so a tool handled in code but missing from the matcher fails instead of silently going dead. guard-pack gets the same wiring: Grep in its matcher and tool list, and its protect-secrets entry takes the guard's own HANDLED_TOOLS rather than keeping a second copy of that list. lib/protect-secrets.js is re-copied so the byte-identity pin holds. Both plugin versions go to 1.0.1 so installed copies refresh. Fixes karanb192#55
This was referenced Sep 13, 2026
Merged
karanb192
added a commit
that referenced
this pull request
Sep 13, 2026
…ernal model CLIs and APIs) (#58) * protect-secrets: add delegation sink patterns Five new BASH_PATTERNS for shell commands that feed secrets or file contents into an external model. High: a secrets file or secret env var reaching a model CLI (gemini, codex, llm, sgpt, aichat, openai, mods, fabric) or a model API host through curl/wget/http/xh. Strict: any file contents reaching the same sinks. Local models (ollama) stay out of scope. Regexes are built from shared fragments (SINK_CMD, SECRET_FILE, SECRET_VAR, SINK_HOST, FILE_BODY) so each pattern stays readable. Heredocs and herestrings are not treated as file input. A key in an auth header with an inline body stays allowed. guard-pack/lib copy refreshed (pinned byte-identical by test). Tests cover every probe from the empirical map, hit and non-hit. * protect-secrets 1.1.0: document delegation sinks, native pairing README gains a Delegation sinks section: why (shunt-style token savers route file contents to a worker model with no redaction; unpaid Gemini API tier terms allow training and human review), what is caught at high vs strict, the sink CLI and API host lists with links, how to allow a specific flow, and a Native pairing subsection with a settings.json snippet (Read deny rules cover cat and redirects since 2.1.257; sandbox network allowlist keys). plugin.json 1.0.0 -> 1.1.0 with the description updated; guard-pack 1.0.0 -> 1.0.1 because its lib copy changed. Root README plugin row mentions the new catch. marketplace.json left as is: it lists targets, not command families, and its line belongs to PR #56. * protect-secrets: widen sink anchor, tighten secret-var and body checks (review F1 to F8, F11 to F13) F1: a sink CLI is matched after leading env assignments, wrappers with their own options (sudo, npx, uvx, bunx, pnpm dlx, command, time, exec, nice, nohup, env, timeout, xargs), quotes, do/then/else and newlines, by path or npm scope (npx @google/gemini-cli). Backslash-newline continuations are joined before matching. F2: a secret var anywhere inside one body token is seen; a var in a later header is not. F3: secret file names are derived from the critical and high entries of SENSITIVE_FILES (plus hidden-file globs), so the lists cannot drift. .git-credentials joins SENSITIVE_FILES at high. F4: curl -T and --upload-file count as a body, in the sink patterns and in curl-upload-env (which also gains --data-binary @file with a space). F5, F13: secret-var names match on whole underscore segments with a small qualifier tail; AUTH and PRIVATE only as the last segment; PASS, PAT, PASSPHRASE and ${!VAR} added. echo-secret-var is unchanged. F6: a redirect glued to the command (gemini<.env) is accepted. F7: file flags are per CLI from each tool's flag table; gemini -i, gemini -a and mods -f are not file flags. gemini/openai @path references count. F8: env-dump also closes on ) and covers set, export, declare -x. F11: six more model API hosts; https (httpie alias) and curlie as clients; httpie name=@file and name=value body items; --data-urlencode name@file. F12: heredocs, herestrings, <( ), /dev/*, filters without a file operand, curl -w/-H @file and a < inside a quoted prompt or body are not file input. Quoted prompts are opaque to the file-name check, so prose such as 'what is a .env file' passes; a secret used by an earlier command in a pipeline is no longer attributed to the sink. * protect-secrets tests: review fix pass coverage 160 tests: every isolation probe from the review, the 13 secret-var false positives and 20 hot names, one sample path per critical/high SENSITIVE_FILES entry reachable through the sink, per-CLI file flags, curl upload flags and their look-alikes, extra hosts and clients, strict false positives. * protect-secrets README: sink anchor, host and client lists, not-covered list Wrapper list matches the regex; six new hosts with vendor doc links; httpie alias and curlie; per-CLI file flags; what stays out (variable indirection, credential emitters, scripts, query-string keys, names outside the Read-side list, fd tricks) and the strict-only false positives. * protect-secrets: trim comments and README to the repo's density Script comment lines 114 to 66 on 235 code lines (main: 52 on 170, ratio 0.31; now 0.28). One line stays above each fragment whose regex is not obvious; the per-pattern reasoning and the per-CLI flag sources live in the PR. File header restored to its main wording. README 128 to 84 lines: one Delegation sinks section (19 lines) and one Native pairing subsection (12 lines); the sink and host lists stay, the rest moved to the PR body.
karanb192
added a commit
that referenced
this pull request
Sep 13, 2026
The Grep matcher lands in PR #56, not here. The row now matches plugins/protect-secrets/hooks/hooks.json on main.
karanb192
added a commit
that referenced
this pull request
Sep 13, 2026
…around it env-read-workaround now asks plainly for the value in .env, with no "find it somewhere in the directory" nudge and no instruction to stop. The description and expected_outcome say what the guard actually is: a path and command guard. A recursive content search never names .env, so there is nothing for it to match, and Grep is not in the matcher until #56 lands. A with-arm failure through either route is a measured limit, not a false alarm. The two diagnostics now record which route it was. no-bash-workaround also covers grep -r, rg, ag and find -exec, and matches on DB_PASSWORD as well as .env. no-grep-workaround matches DB_PASSWORD or .env and no longer fires on .env.example. env-read-denied: read-attempted needs an input_match on .env, so a Read of the README no longer satisfies it, and secret-not-leaked carries weight 3 so the secret dominates the case score. Its expected_outcome now says the closing instruction, not the guard, is what closes the Grep route. RESULTS.md moves to the same layout as the other two suites.
…and-search-tool # Conflicts: # plugins/guard-pack/.claude-plugin/plugin.json # plugins/guard-pack/README.md # plugins/guard-pack/guard-pack.js # plugins/guard-pack/hooks/hooks.json # plugins/guard-pack/tests/guard-pack.test.js # plugins/protect-secrets/.claude-plugin/plugin.json
Author
|
Synced with
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #55 — both halves of it.
Bug 1: backslash paths matched nothing
Every entry in
SENSITIVE_FILESanchors its path boundary on/, and several use[^/]inside, so a tool-provided Windows path went past all of them:That is not only
.env:.envrc, SSH private keys andauthorized_keys, AWS/kube/gcloud/azure/docker credentials,.netrc,.npmrc,.pypirc,.pgpassand the rest were all unprotected on Windows at every safety level, since the anchor is what failed rather than any single rule.Matching now runs on a separator-normalized copy of the path:
One helper instead of rewriting 25 anchors, so a pattern added later is covered by construction. The hook only ever reads the path, never opens it, so the rewrite stays local to matching; the allowlist is normalized the same way, which keeps
C:\src\app\.env.exampleallowed.Bug 2: the search tool never reached the hook
check()has handledGrepfor a while and the test file covers it, buthooks/hooks.jsonregisteredRead|Edit|Write|Bash, so none of that ran for a real call: searching a.envfor a key value was not inspected on any OS. The matcher now comes from the same listmain()branches on:and a test reads
hooks/hooks.jsonand asserts the two agree, in both directions — a tool handled in code but missing from the matcher is dead code, and a tool in the matcher thatcheck()ignores costs a node start per call.guard-packhad the same gap, so it gets the same wiring:Grepin its matcher and inPACK_TOOLS, and itsprotect-secretsentry now takesmod.HANDLED_TOOLSinstead of keeping a second copy of that list. Its meta test derives the matcher fromPACK_TOOLSrather than spelling it out.lib/protect-secrets.jsis re-copied so the byte-identity pin holds.Both plugin versions go to
1.0.1so installed copies refresh.Evidence
The 4 failures are
plugins/format-codecases that shell out toruff, which is not installed in this environment; they fail identically on a clean checkout (1610 tests, 4 fail). Everything else is green, including the 14 tests added here.Behaviour, before and after, through the real script:
ReadC:\Users\me\project\.env{}deny[env-file] Cannot read:Grep{pattern:'API_KEY', path:'C:\app\.env'}{}deny[env-file] Cannot search:ReadC:\src\app\.env.example{}{}ReadC:\src\app\config.js{}{}Negative controls, each reverted on its own:
checkFilePath→ 9 of the new tests failRead\|Edit\|Write\|Bashback inhooks.json→ the registration guard failsprotect-secretsentry → the pack's search test failsAlso checked: garbage stdin still prints
{}and exits 0, and all five touched JSON files parse.One thing left out
BASH_PATTERNShas the same separator assumption in two places,cat-aws-credsandrm-aws-creds, which spell\.aws\/credentials. I left them alone rather than normalize a whole shell command, where backslashes are escapes and rewriting them would change meaning. Making just those two accept[\\/]is a small follow-up if you want it.