davidho27941/cockpit/tree/master/plugins/secret-guard
secret-guard
一個 Claude Code 插件,可掃描輸入對話的每個文字(提示、工具結果、子代理程式行),並在檢測到的 API 金鑰、雲端憑證和其他機密到達模型之前將其替換為有意義的佔位符。
關於這個 mod
#secret-guard
將 API 金鑰、雲端憑證和其他機密保留在傳送到模型的內容之外。進入對話的每個文字都會被掃描以查找已知的秘密形狀;每場比賽都會被替換為有意義的佔位符,祝酒詞說明被編輯的內容,樂隊會記錄計數。該值本身永遠不會被記錄、儲存或顯示。
之前(Claude 會讀到的內容):
$ cat .env
GOOGLE_API_KEY=AIzaSyA-fake-key-for-the-readme-0000000
DATABASE_URL=postgres://app:[email protected]:5432/app
之後(Claude 讀到的內容):
$ cat .env
GOOGLE_API_KEY=<google api key>
DATABASE_URL=postgres://app:<password>@db.internal:5432/app
🛡 secret-guard · 2 redacted this session · last: password (tool result)
它在哪裡,以及為什麼
|鉤|是什麼穿過它|
|---|---|
| session.append |對話在儲存之前保留的每一行:您的提示、斜線指令的輸出、每個工具結果(檔案內容、指令輸出、網頁)、傳遞的訊息、注入的註解、壓縮摘要。主要對話和每個副特工的都一樣。引擎儲存鏈回答的內容,因此模型的下一個請求攜帶經過編輯的行。模型本身的回應會阻止並注意到它永遠不會讀取,因此不會被單獨處理。 |
| prompt.submit |您的訊息,因此會以經過編輯的方式顯示和儲存。 |
| prompt.context|上下文阻止第一個訊息攜帶(CLAUDE.md 和朋友)。 |
| prompt.attachment |引擎自行注入的文字:提及的檔案、提醒、設定掛鉤的輸出。 |
session.append 是阻塞點:它是工具結果或代理程式行到達模型的地方,這就是為什麼在不掛鉤每個工具的情況下覆蓋金鑰檔案的 Read 或代理程式中的 cat .env 的原因。
它偵測到什麼
|佔位符 |比賽|
|---|---|
| <gcp service account private key>、<gcp service account private key id> | "type": "service_account" JSON 檔案的兩個關鍵欄位; client_email 和 project_id 保持可讀 |
| <private key> |任何 PEM 私鑰區塊(RSA、EC、DSA、OPENSSH、PGP、加密;真實或 \n-escaped 換行符號);憑證不是秘密|
| <google api key> | AIza…(39 字元)|
| <google oauth access token>、<google oauth refresh token>、<google oauth client secret> | ya29.…、1//0…、GOCSPX-… |
| <aws access key id> | AKIA…、ASIA…、ABIA…、ACCA…、A3T…(20 字元)|
| <aws secret access key> | aws_secret_access_key / secretAccessKey 旁的 40-character 值 |
| <anthropic api key>、<openai api key> | sk-ant-…、sk-… / sk-proj-… |
| <github token>、<gitlab token> | ghp_ / gho_ / ghu_ / ghs_ / ghr_、github_pat_…、glpat-… |
| <npm token>、<pypi token>、<hugging face token> | npm_…、pypi-AgEIcHlwaS5vcmc…、hf_… |
| <slack token>、<slack webhook url> | xox[abposre]-…,https://hooks.slack.com/services/T…/B…/… |
| <telegram bot token>, <discord bot token> | 123456789:AA…, the three-part Discord shape |
| <stripe secret key>, <sendgrid api key>, <twilio api key> | sk_live_ / sk_test_ / rk_…, SG.….…, SK + 32 hex |
| QXKE CZP | eyJ….eyJ….… |
| <bearer token>, <basic auth>, <token> | the value after 授權:承載|基本|Token |
| <api key> | the value after x-api-key: / api-key: / apikey: |
| <password> | the password in 方案://user:密碼@host |
| <secret value> | the value of api_key = …, 密碼:…, "client_secret": "…", export TOKEN=… and the like (names: api key, secret key, client secret, access / auth / refresh token, private key, password, passwd, pwd, token, secret), 8+ characters, when it does not look like a placeholder, an environment reference, code, a path or a URL |
| <high-entropy secret> | a random-looking token of 32+ characters with Shannon entropy ≥ 4.0 bits/char that sits within 60 characters after a secret-ish word (key, secret, token, password, credential, auth, signature, bearer, …); hex hashes, UUIDs and base64 data URIs are excluded |
Detectors run in that order, specific before generic, and redact() is idempotent: a placeholder never matches a detector, so a row that is scanned twice (the prompt passes prompt.submit and then session.PXZPsession.PXZAQPp關閉 / on | pause / resume for this session (the enabled setting is the permanent switch) |
| /secret-guard test | runs every detector over a built-in sample of obviously fake values and prints which labels fired; a self-test, no real secrets involved |
Settings
| Setting | Default | Meaning |
|---|---|---|
| language | auto | UI language: auto (from LC_ALL / LC_MESSAGES / LANG), en, zh-TW, ja |
| enabled | true | Off: nothing is scanned or rewritten |
| keep_hint | false | Keep the last 4 characters in the placeholder |
| entropy_backstop | true | The high-entropy detector |
| custom_patterns | empty | Extra detectors, one per line as label=regex (JavaScript regex; /…/i form accepted; g is added). An invalid line is shown once in the band and in the status, and ignored. |
| allow_patterns | empty | One regex per line; a match that also matches one of these is left alone. The AWS documentation example pair (AKIAIOSFODNN7EXAMPLE and its secret) is always allowed. |
| scan_tool_results | true | Off: only your prompts, slash-command rows and the context blocks are scanned; tool results, attachments, deliveries, notes and compaction summaries pass through |
Set them with /plugin 設定secret-guard@cockpit.
What it does not cover
- Text already in the context before the mod loaded, and anything the model read in earlier turns.
- The screen and the transcript's structured record. The terminal may draw a tool result just before its rewrite, and a tool's structured record (
toolUseResult) is stored as the tool made it: the transcript file on disk can still hold the raw value even though the model never reads it. Treat transcript files as sensitive regardless. - Model output. If the model reproduces a secret it already knows, that is not scanned.
- Images and documents, secrets split across lines or obfuscated (base64-wrapped, reversed, in a screenshot), and shapes the table does not know.
- The
prompt.contextrewrite makes the engine forget which files were behind theclaudeMdblock (its documented rule for a rewritten text); only the files list is affected, the text is still sent.
This is a safety net, not data-loss prevention. Keep secrets out of the repository and the shell history, use a secret manager, and rotate anything that was pasted by mistake.
False positives and how to allowlist
The generic <secret value> rule is the one most likely to fire on something harmless, such as a password-shaped test fixture. Add a regex to allow_patterns that matches the fixture, or turn the value into an obvious placeholder (<your-password>, ${PASSWORD}, xxx), which the rule skips. Hex hashes, UUIDs, git SHAs, package integrity hashes and ordinary base64 blobs are excluded from the entropy backstop; if it still fires on something, entropy_backstop 將其關閉而不會遺失的偵測器。
安裝之前它會做什麼
claude plugin validate ./plugins/secret-guard
結果(v0.1.0、Claude Code 2.1.289):
hooks: session.start, prompt.submit, session.append, prompt.context, prompt.attachment,
command.run{command=secret-guard}, ui.render{component=AbovePrompt}
calls: $.clock.now, $.command.register, $.env.get, $.state.get, $.state.set, $.ui.resolve, $.ui.toast
env reads: LANG, LC_ALL, LC_MESSAGES · env writes: nothing
沒有 $.fs,沒有 $.process,沒有 $.http,沒有模型呼叫:mod 只讀取通過它的行,除了重寫之外不寫入任何內容。它在 $.state 中保留的是計數、標籤以及發生編輯的位置,而不是值; Toast 和偵錯日誌也是如此。
限制
- 透過形狀進行檢測。不常見格式、跨兩行或編碼的秘密是看不到的。
- 一切都在模型讀取的每一行上執行,在主對話和代理程式中;正則表達式是線性的,並且與模型請求相比,成本可以忽略不計,但會完整掃描非常大的工具結果(兆字節)。
- 樂隊是一條線; status 命令有詳細資訊。
- 僅重寫文字區塊和
tool_result區塊。思考,tool_use,圖像和文件區塊被引擎固定或不攜帶任何文字。
## 發展
claude plugin validate ./plugins/secret-guard
claude plugin test ./plugins/secret-guard
測試從片段('AIza' + 'A'.repeat(35))建立每個夾具,因此測試檔案本身從不包含 credential-shaped 文字。 session.append 決策是 hooks/logic.ts 中的純 planAppend(),因為測試套件本身無法引發該事件; hooks/register.tsx 中的鉤子僅在其周圍添加狀態和 toast 呼叫。
安裝
請先查看作者 README,確認 marketplace 與外掛名稱;指令可能隨儲存庫結構而變動。
claude plugin marketplace add davidho27941/cockpit claude plugin install secret-guard
原文 / README
secret-guard
Keeps API keys, cloud credentials and other secrets out of what is sent to the model. Every text that enters the conversation is scanned for known secret shapes; each match is replaced with a meaningful placeholder, a toast says what was redacted, and the band keeps a count. The value itself is never logged, stored or shown.
Before (what Claude would have read):
$ cat .env
GOOGLE_API_KEY=AIzaSyA-fake-key-for-the-readme-0000000
DATABASE_URL=postgres://app:[email protected]:5432/app
After (what Claude reads):
$ cat .env
GOOGLE_API_KEY=<google api key>
DATABASE_URL=postgres://app:<password>@db.internal:5432/app
🛡 secret-guard · 2 redacted this session · last: password (tool result)
Where it hooks, and why
| Hook | What passes through it |
|---|---|
| session.append | Every row a conversation keeps, before it is stored: your prompt, a slash command's output, every tool result (file contents, command output, web pages), delivered messages, injected notes, compaction summaries. The main conversation and every subagent's alike. The engine stores what the chain answers, so the model's next request carries the redacted row. The model's own response blocks and notices it never reads are left alone. |
| prompt.submit | Your message, so it is shown and stored redacted. |
| prompt.context | The context blocks the first message carries (CLAUDE.md and friends). |
| prompt.attachment | Texts the engine injects on its own: a mentioned file, a reminder, a settings hook's output. |
session.append is the choke point: it is the one place through which a tool result or a subagent's row reaches the model, which is why a Read of a key file or a cat .env in a sub agent is covered without hooking each tool.
What it detects
| Placeholder | Matches |
|---|---|
| <gcp service account private key>, <gcp service account private key id> | the two key fields of a "type": "service_account" JSON file; client_email and project_id stay readable |
| <private key> | any PEM private key block (RSA, EC, DSA, OPENSSH, PGP, encrypted; real or \n-escaped newlines); certificates are not secrets |
| <google api key> | AIza… (39 characters) |
| <google oauth access token>, <google oauth refresh token>, <google oauth client secret> | ya29.…, 1//0…, GOCSPX-… |
| <aws access key id> | AKIA…, ASIA…, ABIA…, ACCA…, A3T… (20 characters) |
| <aws secret access key> | a 40-character value next to aws_secret_access_key / secretAccessKey |
| <anthropic api key>, <openai api key> | sk-ant-…, sk-… / sk-proj-… |
| <github token>, <gitlab token> | ghp_ / gho_ / ghu_ / ghs_ / ghr_, github_pat_…, glpat-… |
| <npm token>, <pypi token>, <hugging face token> | npm_…, pypi-AgEIcHlwaS5vcmc…, hf_… |
| <slack token>, <slack webhook url> | xox[abposre]-…, https://hooks.slack.com/services/T…/B…/… |
| <telegram bot token>, <discord bot token> | 123456789:AA…, the three-part Discord shape |
| <stripe secret key>, <sendgrid api key>, <twilio api key> | sk_live_ / sk_test_ / rk_…, SG.….…, SK + 32 hex |
| <jwt> | eyJ….eyJ….… |
| <bearer token>, <basic auth>, <token> | the value after Authorization: Bearer|Basic|Token |
| <api key> | the value after x-api-key: / api-key: / apikey: |
| <password> | the password in scheme://user:password@host |
| <secret value> | the value of api_key = …, password: …, "client_secret": "…", export TOKEN=… and the like (names: api key, secret key, client secret, access / auth / refresh token, private key, password, passwd, pwd, token, secret), 8+ characters, when it does not look like a placeholder, an environment reference, code, a path or a URL |
| <high-entropy secret> | a random-looking token of 32+ characters with Shannon entropy ≥ 4.0 bits/char that sits within 60 characters after a secret-ish word (key, secret, token, password, credential, auth, signature, bearer, …); hex hashes, UUIDs and base64 data URIs are excluded |
Detectors run in that order, specific before generic, and redact() is idempotent: a placeholder never matches a detector, so a row that is scanned twice (the prompt passes prompt.submit and then session.append) is rewritten once.
Placeholders are English in every UI language: the model reads them and they must be stable. With keep_hint on, the last four characters ride along (<google api key …Zx3f>) so two keys can be told apart; off by default.
Usage
Installed, it is on. The band above the prompt appears once something has been redacted.
| Command | Does |
|---|---|
| /secret-guard | status: enabled / paused, what is scanned, totals by label, the last 10 redactions as label · where · time ago |
| /secret-guard off / on | pause / resume for this session (the enabled setting is the permanent switch) |
| /secret-guard test | runs every detector over a built-in sample of obviously fake values and prints which labels fired; a self-test, no real secrets involved |
Settings
| Setting | Default | Meaning |
|---|---|---|
| language | auto | UI language: auto (from LC_ALL / LC_MESSAGES / LANG), en, zh-TW, ja |
| enabled | true | Off: nothing is scanned or rewritten |
| keep_hint | false | Keep the last 4 characters in the placeholder |
| entropy_backstop | true | The high-entropy detector |
| custom_patterns | empty | Extra detectors, one per line as label=regex (JavaScript regex; /…/i form accepted; g is added). An invalid line is shown once in the band and in the status, and ignored. |
| allow_patterns | empty | One regex per line; a match that also matches one of these is left alone. The AWS documentation example pair (AKIAIOSFODNN7EXAMPLE and its secret) is always allowed. |
| scan_tool_results | true | Off: only your prompts, slash-command rows and the context blocks are scanned; tool results, attachments, deliveries, notes and compaction summaries pass through |
Set them with /plugin configure secret-guard@cockpit.
What it does not cover
- Text already in the context before the mod loaded, and anything the model read in earlier turns.
- The screen and the transcript's structured record. The terminal may draw a tool result just before its rewrite, and a tool's structured record (
toolUseResult) is stored as the tool made it: the transcript file on disk can still hold the raw value even though the model never reads it. Treat transcript files as sensitive regardless. - Model output. If the model reproduces a secret it already knows, that is not scanned.
- Images and documents, secrets split across lines or obfuscated (base64-wrapped, reversed, in a screenshot), and shapes the table does not know.
- The
prompt.contextrewrite makes the engine forget which files were behind theclaudeMdblock (its documented rule for a rewritten text); only the files list is affected, the text is still sent.
This is a safety net, not data-loss prevention. Keep secrets out of the repository and the shell history, use a secret manager, and rotate anything that was pasted by mistake.
False positives and how to allowlist
The generic <secret value> rule is the one most likely to fire on something harmless, such as a password-shaped test fixture. Add a regex to allow_patterns that matches the fixture, or turn the value into an obvious placeholder (<your-password>, ${PASSWORD}, xxx), which the rule skips. Hex hashes, UUIDs, git SHAs, package integrity hashes and ordinary base64 blobs are excluded from the entropy backstop; if it still fires on something, entropy_backstop turns it off without losing the specific detectors.
What it does before you install it
claude plugin validate ./plugins/secret-guard
Result (v0.1.0, Claude Code 2.1.289):
hooks: session.start, prompt.submit, session.append, prompt.context, prompt.attachment,
command.run{command=secret-guard}, ui.render{component=AbovePrompt}
calls: $.clock.now, $.command.register, $.env.get, $.state.get, $.state.set, $.ui.resolve, $.ui.toast
env reads: LANG, LC_ALL, LC_MESSAGES · env writes: nothing
No $.fs, no $.process, no $.http, no model calls: the mod reads nothing but the rows that pass through it and writes nothing but their rewrite. What it keeps in $.state is counts, labels and where a redaction happened, never a value; the same goes for toasts and the debug log.
Limits
- Detection is by shape. A secret in an unusual format, cut across two lines, or encoded is not seen.
- Everything runs on every row the model reads, in the main conversation and in subagents; the regexes are linear and the cost is negligible next to a model request, but a very large tool result (megabytes) is scanned in full.
- The band is one line; the status command has the detail.
- Only text blocks and
tool_resultblocks are rewritten. Thinking, tool_use, image and document blocks are pinned by the engine or carry no text.
Development
claude plugin validate ./plugins/secret-guard
claude plugin test ./plugins/secret-guard
The tests build every fixture from pieces ('AIza' + 'A'.repeat(35)) so the test file itself never contains a credential-shaped literal. The session.append decision is the pure planAppend() in hooks/logic.ts, because the test kit cannot raise that event itself; the hook in hooks/register.tsx only adds the state and toast calls around it.
其他同名作品
- secret-guardShriD5 · ★ 0
- secret-guard0xGondarxyz · ★ 0
