@coo-quack/sensitive-canary 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +11 -0
- package/CHANGELOG.md +73 -0
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/hooks/hooks.json +26 -0
- package/package.json +42 -0
- package/src/__tests__/pre-tool-use-hook.test.ts +646 -0
- package/src/__tests__/user-prompt-submit-hook.test.ts +255 -0
- package/src/lib/__tests__/inspector.test.ts +281 -0
- package/src/lib/__tests__/rules.test.ts +322 -0
- package/src/lib/inspector.ts +113 -0
- package/src/lib/rules.ts +308 -0
- package/src/pre-tool-use-hook.ts +316 -0
- package/src/user-prompt-submit-hook.ts +106 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coo-quack",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "coo-quack"
|
|
5
|
+
},
|
|
6
|
+
"metadata": {
|
|
7
|
+
"description": "Security hooks that block secrets and PII before they reach the Anthropic API"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "sensitive-canary",
|
|
12
|
+
"source": {
|
|
13
|
+
"source": "github",
|
|
14
|
+
"repo": "coo-quack/sensitive-canary"
|
|
15
|
+
},
|
|
16
|
+
"description": "Blocks secrets and PII before they reach the Anthropic API",
|
|
17
|
+
"version": "0.4.0",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"keywords": ["security", "secrets", "pii", "hooks"]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sensitive-canary",
|
|
3
|
+
"description": "Blocks secrets and PII before they reach the Anthropic API",
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "coo-quack"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/coo-quack/sensitive-canary",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["security", "secrets", "pii", "hooks"]
|
|
11
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.4.2 (2026-02-23)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- **Scoped package name** — renamed npm package from `sensitive-canary` to `@coo-quack/sensitive-canary`
|
|
8
|
+
- **Homepage** — added `homepage` field pointing to the documentation site
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## v0.4.1 (2026-02-23)
|
|
13
|
+
|
|
14
|
+
### Improvements
|
|
15
|
+
|
|
16
|
+
- **npm publish automation** — release workflow now publishes to npm with provenance on merge to main
|
|
17
|
+
- **Package metadata** — added `repository` and `files` fields, removed `private: true` for npm publishing
|
|
18
|
+
- **npm install docs** — added `npm install -g` setup instructions to README and docs
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## v0.4.0 (2026-02-23)
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
- **Allow tags are now single-use** — allow tags are consumed after the first tool call, preventing unintended persistent bypass across multiple tool uses in the same turn
|
|
27
|
+
|
|
28
|
+
### Fixes
|
|
29
|
+
|
|
30
|
+
- **Random bird emoji in block messages** — PreToolUse block messages now use `randomBird()` instead of a hardcoded emoji, matching the existing behavior in other messages
|
|
31
|
+
|
|
32
|
+
### Docs
|
|
33
|
+
|
|
34
|
+
- **README restructured** — new section order: Why → Quick Start → What Happens → Detection Rules → How It Works → Allow Tags
|
|
35
|
+
- **Docs site headings unified** — "How It Works" → "What Happens", "What Gets Detected" → "Detection Rules" for consistency with README
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## v0.3.1 (2026-02-23)
|
|
40
|
+
|
|
41
|
+
### Fixes
|
|
42
|
+
|
|
43
|
+
- **Bird emoji in PreToolUse block reason** — the bird emoji now appears in the block message shown by Claude Code, not only in the terminal output
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## v0.3.0 (2026-02-23)
|
|
48
|
+
|
|
49
|
+
### Features
|
|
50
|
+
|
|
51
|
+
- **Allow + Mask tag priority** — when both `[allow-*]` and `[mask-*]` tags appear in the same prompt, the first occurrence wins per category (`secret`, `pii`). `[allow-all]` and `[mask-all]` resolve both dimensions at once.
|
|
52
|
+
|
|
53
|
+
### Fixes
|
|
54
|
+
|
|
55
|
+
- Plugin install command corrected to `sensitive-canary@coo-quack`
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## v0.1.0 (2026-02-22)
|
|
60
|
+
|
|
61
|
+
Initial release.
|
|
62
|
+
|
|
63
|
+
### Features
|
|
64
|
+
|
|
65
|
+
- **UserPromptSubmit hook** — scans every prompt for secrets and PII before it is sent to the Anthropic API
|
|
66
|
+
- **PreToolUse hook** — blocks `.env`/`.env.*` files by name; scans file contents and Bash commands for secrets and PII
|
|
67
|
+
- **25+ detection rules** — AWS keys, GitHub/GitLab PATs, Stripe keys, Slack/Discord/Telegram tokens, JWTs, SendGrid/Mailgun/Mailchimp keys, Anthropic/OpenAI API keys, database connection strings, and more
|
|
68
|
+
- **PII detection** — email addresses, credit card numbers (Luhn-validated), US SSNs, US/JP phone numbers, Japanese postal codes, private IPv4 addresses
|
|
69
|
+
- **Entropy filtering** — suppresses false positives on low-entropy generic-secret and env-assignment matches
|
|
70
|
+
- **Allow tags** — `[allow-secret]`, `[allow-pii]`, `[allow-all]` bypass specific categories per prompt
|
|
71
|
+
- **[mask-xxx] tag handling** — explains that prompt masking is unsupported and suggests the correct allow tag
|
|
72
|
+
- **Environment variable expansion** — Bash commands referencing `$VAR` / `${VAR}` have their env values scanned
|
|
73
|
+
- **Deduplication** — repeated occurrences of the same secret value produce a single finding
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 coo-quack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# sensitive-canary
|
|
2
|
+
|
|
3
|
+
[](https://github.com/coo-quack/sensitive-canary/actions/workflows/ci.yml)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A security plugin that prevents unintended data leaks from Claude Code. Automatically detects and blocks secrets and PII — in prompts, file reads, and command executions — before they are sent to the Anthropic API.
|
|
7
|
+
|
|
8
|
+
No proxy server. No background process. Native Claude Code hooks only.
|
|
9
|
+
|
|
10
|
+
📖 **[Documentation](https://coo-quack.github.io/sensitive-canary/)** — Installation guide, detection rules reference, and allow tag details.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why sensitive-canary?
|
|
15
|
+
|
|
16
|
+
Claude Code is a powerful development tool, but file reads and command executions can inadvertently send secrets and personal information to the Anthropic API. API keys in `.env` files, tokens embedded in config files, credentials pasted into the terminal — once sent to the API, they leave your machine.
|
|
17
|
+
|
|
18
|
+
**sensitive-canary intercepts them before they are sent, preventing unintended data leaks.**
|
|
19
|
+
|
|
20
|
+
| Without sensitive-canary | With sensitive-canary |
|
|
21
|
+
|--------------------------|----------------------|
|
|
22
|
+
| `cat .env` → full contents sent to Claude ❌ | Blocked by name before Claude reads it ✅ |
|
|
23
|
+
| Paste `AKIAIOSFODNN7EXAMPLE` in prompt ❌ | Blocked before the API call is made ✅ |
|
|
24
|
+
| Tool result contains user@email.com ❌ | PII detected and blocked ✅ |
|
|
25
|
+
| `echo $API_KEY` with live key ❌ | Env var value scanned and blocked ✅ |
|
|
26
|
+
|
|
27
|
+
- **Two hooks** — `UserPromptSubmit` and `PreToolUse` cover both directions of risk
|
|
28
|
+
- **29 detection rules** — sourced from gitleaks and TruffleHog detector definitions
|
|
29
|
+
- **Entropy filtering** — reduces false positives on low-entropy values
|
|
30
|
+
- **Luhn validation** — credit card numbers are validated, not just pattern-matched
|
|
31
|
+
- **Local only** — all scanning runs in your terminal; nothing is sent anywhere
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Requirements
|
|
38
|
+
|
|
39
|
+
- Node.js **22.6.0** or later (required for `--experimental-strip-types`)
|
|
40
|
+
- Claude Code 1.0.33 or later
|
|
41
|
+
|
|
42
|
+
### Plugin install (recommended)
|
|
43
|
+
|
|
44
|
+
Install in two commands from inside a Claude Code session:
|
|
45
|
+
|
|
46
|
+
**1. Register the marketplace**
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
/plugin marketplace add coo-quack/sensitive-canary
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**2. Install the plugin**
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
/plugin install sensitive-canary@coo-quack
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Done. The hooks are enabled automatically.
|
|
59
|
+
|
|
60
|
+
> **Keeping up to date:** Third-party marketplaces have auto-update disabled by default. To receive automatic updates, run `/plugin` → **Marketplaces** tab → select the marketplace → **Enable auto-update**. You can also update manually from the same tab. See [Discover and install plugins](https://docs.anthropic.com/en/docs/claude-code/discover-plugins) for details.
|
|
61
|
+
|
|
62
|
+
<details>
|
|
63
|
+
<summary>npm install</summary>
|
|
64
|
+
|
|
65
|
+
Install locally via npm and configure hooks manually:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install -g @coo-quack/sensitive-canary
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Update to the latest version:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm update -g @coo-quack/sensitive-canary
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then add to `~/.claude/settings.json`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"hooks": {
|
|
82
|
+
"UserPromptSubmit": [
|
|
83
|
+
{
|
|
84
|
+
"hooks": [
|
|
85
|
+
{
|
|
86
|
+
"type": "command",
|
|
87
|
+
"command": "npx tsx $(npm root -g)/@coo-quack/sensitive-canary/src/user-prompt-submit-hook.ts"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"PreToolUse": [
|
|
93
|
+
{
|
|
94
|
+
"matcher": "Read|Bash",
|
|
95
|
+
"hooks": [
|
|
96
|
+
{
|
|
97
|
+
"type": "command",
|
|
98
|
+
"command": "npx tsx $(npm root -g)/@coo-quack/sensitive-canary/src/pre-tool-use-hook.ts"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> **Note:** `node_modules` 内の TypeScript は `--experimental-strip-types` で実行できないため、`npx tsx` を使用します。
|
|
108
|
+
|
|
109
|
+
</details>
|
|
110
|
+
|
|
111
|
+
<details>
|
|
112
|
+
<summary>Manual setup (git clone)</summary>
|
|
113
|
+
|
|
114
|
+
Clone the repository and configure hooks manually:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git clone https://github.com/coo-quack/sensitive-canary.git ~/sensitive-canary
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Update to the latest version:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cd ~/sensitive-canary && git pull
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Then add to `~/.claude/settings.json`:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"hooks": {
|
|
131
|
+
"UserPromptSubmit": [
|
|
132
|
+
{
|
|
133
|
+
"hooks": [
|
|
134
|
+
{
|
|
135
|
+
"type": "command",
|
|
136
|
+
"command": "node --experimental-strip-types ~/sensitive-canary/src/user-prompt-submit-hook.ts"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"PreToolUse": [
|
|
142
|
+
{
|
|
143
|
+
"matcher": "Read|Bash",
|
|
144
|
+
"hooks": [
|
|
145
|
+
{
|
|
146
|
+
"type": "command",
|
|
147
|
+
"command": "node --experimental-strip-types ~/sensitive-canary/src/pre-tool-use-hook.ts"
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
</details>
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## What Happens
|
|
161
|
+
|
|
162
|
+
### Prompt blocked
|
|
163
|
+
|
|
164
|
+
Prompts containing secrets or PII are blocked before being sent.
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
> My AWS key is AKIAIOSFODNN7EXAMPLE. Can you review this code?
|
|
168
|
+
|
|
169
|
+
🐤 sensitive-canary: sensitive data detected — blocked
|
|
170
|
+
|
|
171
|
+
[Secret] AWS Access Key ID (aws-access-key): AKIA****MPLE
|
|
172
|
+
|
|
173
|
+
To allow, add a tag to your prompt:
|
|
174
|
+
[allow-secret] — allow secrets
|
|
175
|
+
[allow-all] — bypass all sensitive-canary checks
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
To allow it through, add the suggested tag:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
> [allow-secret] My AWS key is AKIAIOSFODNN7EXAMPLE. Can you review this code?
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### .env file blocked
|
|
185
|
+
|
|
186
|
+
`.env` / `.env.*` files are blocked unconditionally, regardless of their contents.
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
> Read .env
|
|
190
|
+
|
|
191
|
+
📄 sensitive-canary: blocked — /path/to/.env
|
|
192
|
+
|
|
193
|
+
🐤 Blocked: .env and .env.* files contain secrets and must not be read into the conversation.
|
|
194
|
+
|
|
195
|
+
To allow this, the user must add an allow tag to their next prompt:
|
|
196
|
+
[allow-secret] — allow secrets
|
|
197
|
+
[allow-pii] — allow PII
|
|
198
|
+
[allow-all] — bypass all sensitive-canary checks
|
|
199
|
+
|
|
200
|
+
Example: "[allow-secret] please read /path/to/.env"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### File content blocked
|
|
204
|
+
|
|
205
|
+
Non-`.env` files are also blocked if their contents contain secrets or PII.
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
> Read config.yaml
|
|
209
|
+
|
|
210
|
+
📄 sensitive-canary: blocked — /path/to/config.yaml
|
|
211
|
+
|
|
212
|
+
🐤 Blocked: file contains sensitive data
|
|
213
|
+
|
|
214
|
+
[Secret] AWS Access Key ID (aws-access-key): AKIA****MPLE
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Allow tags
|
|
218
|
+
|
|
219
|
+
To intentionally bypass a block, include the appropriate tag in your **current prompt**.
|
|
220
|
+
|
|
221
|
+
| Tag | Effect |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `[allow-secret]` | Skip all secret-category checks |
|
|
224
|
+
| `[allow-pii]` | Skip all PII-category checks |
|
|
225
|
+
| `[allow-all]` | Skip all sensitive-canary checks |
|
|
226
|
+
|
|
227
|
+
> **Note:** Tags are read from the **current user message only**. Tags in previous messages are ignored — there is no risk of an accidental persistent bypass. Tags are case-insensitive. `[allow-secret]` does not bypass PII blocks (and vice versa). The name-based block on `.env`/`.env.*` files can be bypassed by any of the three allow tags.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Detection rules
|
|
232
|
+
|
|
233
|
+
### Secrets (22 rules)
|
|
234
|
+
|
|
235
|
+
| Rule ID | Description |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `aws-access-key` | AWS Access Key ID |
|
|
238
|
+
| `private-key` | PEM Private Key (RSA / EC / DSA / PGP / OpenSSH) |
|
|
239
|
+
| `github-pat` | GitHub Personal Access Token |
|
|
240
|
+
| `github-fine-grained` | GitHub Fine-Grained Token |
|
|
241
|
+
| `gitlab-pat` | GitLab Personal Access Token |
|
|
242
|
+
| `slack-token` | Slack Token |
|
|
243
|
+
| `slack-webhook` | Slack Webhook URL |
|
|
244
|
+
| `discord-webhook` | Discord Webhook URL |
|
|
245
|
+
| `telegram-bot-token` | Telegram Bot Token |
|
|
246
|
+
| `twilio-sid` | Twilio Account SID |
|
|
247
|
+
| `sendgrid-key` | SendGrid API Key |
|
|
248
|
+
| `mailgun-key` | Mailgun API Key |
|
|
249
|
+
| `mailchimp-key` | Mailchimp API Key |
|
|
250
|
+
| `stripe-secret-key` | Stripe Secret Key |
|
|
251
|
+
| `stripe-restricted-key` | Stripe Restricted Key |
|
|
252
|
+
| `openai-key` | OpenAI API Key (legacy format) |
|
|
253
|
+
| `openai-project-key` | OpenAI Project API Key (`sk-proj-` prefix) *(entropy ≥ 3.5)* |
|
|
254
|
+
| `anthropic-key` | Anthropic API Key |
|
|
255
|
+
| `jwt` | JSON Web Token (JWT) |
|
|
256
|
+
| `generic-secret` | Generic API key / secret assignment *(entropy ≥ 3.5)* |
|
|
257
|
+
| `env-assignment` | `.env`-style secret assignment *(entropy ≥ 3.0)* |
|
|
258
|
+
| `connection-string` | Database connection string with embedded credentials |
|
|
259
|
+
|
|
260
|
+
### PII (7 rules)
|
|
261
|
+
|
|
262
|
+
| Rule ID | Description | Validation |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `pii-email` | Email address | — |
|
|
265
|
+
| `pii-credit-card` | Credit card number | Luhn check |
|
|
266
|
+
| `pii-ssn` | US Social Security Number | Invalid prefix exclusion |
|
|
267
|
+
| `pii-phone-us` | US phone number | — |
|
|
268
|
+
| `pii-phone-jp` | Japanese phone number | — |
|
|
269
|
+
| `pii-postal-jp` | Japanese postal code (`〒` prefix required) | — |
|
|
270
|
+
| `pii-ipv4` | IPv4 address (RFC 1918 private ranges only) | — |
|
|
271
|
+
|
|
272
|
+
Detection patterns are based on rule definitions from [gitleaks](https://github.com/gitleaks/gitleaks) and [TruffleHog](https://github.com/trufflesecurity/trufflehog).
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## How It Works
|
|
277
|
+
|
|
278
|
+
### ① UserPromptSubmit hook
|
|
279
|
+
|
|
280
|
+
Runs just before a prompt is sent to the API.
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
User presses Enter
|
|
284
|
+
↓
|
|
285
|
+
UserPromptSubmit hook
|
|
286
|
+
↓ scans prompt
|
|
287
|
+
├─ secret / PII detected AND no matching [allow-xxx] tag → block (exit 2)
|
|
288
|
+
└─ nothing detected OR tag present → pass (exit 0)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
When blocked, the terminal shows what was detected and how to bypass it.
|
|
292
|
+
|
|
293
|
+
### ② PreToolUse hook
|
|
294
|
+
|
|
295
|
+
Runs just before Claude calls the `Read` or `Bash` tool.
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
Claude calls Read / Bash tool
|
|
299
|
+
↓
|
|
300
|
+
PreToolUse hook
|
|
301
|
+
↓
|
|
302
|
+
── Read tool ─────────────────────────────────────────────────────
|
|
303
|
+
│ 1. filename is .env / .env.* → blocked unconditionally
|
|
304
|
+
│ 2. file contents contain secret / PII → blocked
|
|
305
|
+
└─ Bash tool ─────────────────────────────────────────────────────
|
|
306
|
+
1. env var values referenced in the command contain secret / PII → blocked
|
|
307
|
+
2. command string itself contains secret / PII (e.g. echo AKIA...) → blocked
|
|
308
|
+
3. cat / head / tail / etc. targeting a file → file contents scanned
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
When blocked, Claude receives a JSON response explaining the reason and is prompted to tell the user.
|
|
312
|
+
The terminal also receives a direct message (via `/dev/tty`).
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Allow Tags (detailed)
|
|
317
|
+
|
|
318
|
+
Allow tags filter the scan results — the scan itself always runs. The `.env`/`.env.*` name block is the only exception: when an allow tag is present, the file is passed through immediately without scanning.
|
|
319
|
+
|
|
320
|
+
### Mask tags
|
|
321
|
+
|
|
322
|
+
`[mask-secret]`, `[mask-pii]`, and `[mask-all]` are recognised but **not supported**. Claude Code hooks cannot rewrite prompt content, so masking before sending is not possible.
|
|
323
|
+
|
|
324
|
+
If you include a mask tag, sensitive-canary will explain this and list what was detected:
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
> [mask-secret] My key is AKIAIOSFODNN7EXAMPLE, can you review this?
|
|
328
|
+
|
|
329
|
+
🐦 sensitive-canary: prompt masking is not supported
|
|
330
|
+
|
|
331
|
+
[mask-secret] cannot mask prompt content.
|
|
332
|
+
The following sensitive data was detected:
|
|
333
|
+
|
|
334
|
+
[Secret] AWS Access Key ID (aws-access-key): AKIA****MPLE
|
|
335
|
+
|
|
336
|
+
Please choose one of the following:
|
|
337
|
+
|
|
338
|
+
1. Manually redact the values above and resubmit
|
|
339
|
+
2. To send as-is, add an allow tag to your prompt:
|
|
340
|
+
[allow-secret] — allow secrets
|
|
341
|
+
[allow-all] — bypass all sensitive-canary checks
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Allow + Mask tag priority
|
|
345
|
+
|
|
346
|
+
When both `[allow-*]` and `[mask-*]` tags appear in the same prompt, **the tag that appears first wins** for each category (`secret`, `pii`). `[allow-all]` and `[mask-all]` resolve both categories at once.
|
|
347
|
+
|
|
348
|
+
| Example | Result |
|
|
349
|
+
|---------|--------|
|
|
350
|
+
| `[allow-secret] [mask-secret] …` | secret allowed |
|
|
351
|
+
| `[mask-secret] [allow-secret] …` | masking not supported error |
|
|
352
|
+
| `[allow-secret] [mask-pii] …` | secret allowed, PII mask error |
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## File structure
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
.claude-plugin/
|
|
360
|
+
plugin.json plugin manifest
|
|
361
|
+
marketplace.json marketplace catalog
|
|
362
|
+
hooks/
|
|
363
|
+
hooks.json Claude Code hook configuration
|
|
364
|
+
src/
|
|
365
|
+
user-prompt-submit-hook.ts UserPromptSubmit hook
|
|
366
|
+
pre-tool-use-hook.ts PreToolUse hook
|
|
367
|
+
lib/
|
|
368
|
+
inspector.ts allow tag parsing, message scanning
|
|
369
|
+
rules.ts secret and PII detection rule definitions
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Development
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
npm install # install dependencies
|
|
378
|
+
|
|
379
|
+
npm test # run tests
|
|
380
|
+
npm run test:watch # run tests in watch mode
|
|
381
|
+
npm run typecheck # type check (tsc)
|
|
382
|
+
npm run lint # lint with Biome (no changes)
|
|
383
|
+
npm run fix # lint + auto-fix with Biome
|
|
384
|
+
npm run ci # typecheck + lint + tests (for CI)
|
|
385
|
+
```
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Block secrets and PII before they reach the Anthropic API",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "Read|Bash",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node --experimental-strip-types \"${CLAUDE_PLUGIN_ROOT}/src/pre-tool-use-hook.ts\""
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"UserPromptSubmit": [
|
|
16
|
+
{
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node --experimental-strip-types \"${CLAUDE_PLUGIN_ROOT}/src/user-prompt-submit-hook.ts\""
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coo-quack/sensitive-canary",
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Claude Code hooks that block secrets and PII before they reach the Anthropic API",
|
|
5
|
+
"homepage": "https://coo-quack.github.io/sensitive-canary/",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/coo-quack/sensitive-canary.git"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
".claude-plugin/",
|
|
14
|
+
"hooks/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.6.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"lint": "biome lint src",
|
|
27
|
+
"format": "biome format --write src",
|
|
28
|
+
"format:check": "biome ci --linter-enabled=false src",
|
|
29
|
+
"fix": "biome check --write src",
|
|
30
|
+
"ci": "tsc --noEmit && biome check src && vitest run",
|
|
31
|
+
"docs:dev": "vitepress dev docs",
|
|
32
|
+
"docs:build": "vitepress build docs",
|
|
33
|
+
"docs:preview": "vitepress preview docs"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@biomejs/biome": "^2.4.4",
|
|
37
|
+
"@types/node": "^25.3.0",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitepress": "^1.6.4",
|
|
40
|
+
"vitest": "^4.0.18"
|
|
41
|
+
}
|
|
42
|
+
}
|