davidho27941/cockpit/tree/master/plugins/secret-guard
secret-guard
프롬프트, 도구 결과, 하위 에이전트 행 등 대화에 입력되는 모든 텍스트를 검사하고 감지된 API 키, 클라우드 자격 증명 및 기타 비밀이 모델에 도달하기 전에 의미 있는 자리 표시자로 바꾸는 Claude Code 플러그인입니다.
이 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 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 구성 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 없음, 모델 호출 없음: 모드는 이를 통과하는 행만 읽고 다시 작성하는 것 외에는 아무것도 쓰지 않습니다. $.state에 보관되는 것은 개수, 레이블 및 수정이 발생한 위치이며 값은 아닙니다. 토스트 메시지와 디버그 로그도 마찬가지입니다.
한도
- 모양으로 감지됩니다. 두 줄에 걸쳐 자르거나 인코딩된 특이한 형식의 비밀은 표시되지 않습니다.
- 기본 대화와 하위 에이전트에서 모델이 읽는 모든 행에서 모든 것이 실행됩니다. 정규식은 선형이고 비용은 모델 요청에 비해 미미하지만 매우 큰 도구 결과(메가바이트)는 전체를 스캔합니다.
- 밴드는 한 줄입니다. status 명령에 세부 정보가 있습니다.
- 텍스트 블록과
tool_result블록만 다시 작성됩니다. Thinking, 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의 후크는 주변에 상태 및 토스트 호출만 추가합니다.
설치
먼저 작성자의 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
