@aictrl/hush 0.1.6 → 0.1.7
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/.gitlab-ci.yml +59 -0
- package/README.md +150 -3
- package/dist/cli.js +30 -17
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +81 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/redact-hook.d.ts +12 -0
- package/dist/commands/redact-hook.d.ts.map +1 -0
- package/dist/commands/redact-hook.js +89 -0
- package/dist/commands/redact-hook.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/middleware/redactor.d.ts +5 -0
- package/dist/middleware/redactor.d.ts.map +1 -1
- package/dist/middleware/redactor.js +69 -0
- package/dist/middleware/redactor.js.map +1 -1
- package/dist/plugins/opencode-hush.d.ts +21 -0
- package/dist/plugins/opencode-hush.d.ts.map +1 -0
- package/dist/plugins/opencode-hush.js +25 -0
- package/dist/plugins/opencode-hush.js.map +1 -0
- package/dist/plugins/sensitive-patterns.d.ts +15 -0
- package/dist/plugins/sensitive-patterns.d.ts.map +1 -0
- package/dist/plugins/sensitive-patterns.js +69 -0
- package/dist/plugins/sensitive-patterns.js.map +1 -0
- package/dist/vault/token-vault.d.ts.map +1 -1
- package/dist/vault/token-vault.js +16 -3
- package/dist/vault/token-vault.js.map +1 -1
- package/examples/team-config/.claude/settings.json +19 -0
- package/examples/team-config/.codex/config.toml +4 -0
- package/examples/team-config/.opencode/plugins/hush.ts +76 -0
- package/examples/team-config/opencode.json +10 -0
- package/package.json +11 -1
- package/scripts/e2e-plugin-block.sh +142 -0
- package/scripts/e2e-proxy-live.sh +185 -0
- package/src/cli.ts +28 -16
- package/src/commands/init.ts +107 -0
- package/src/commands/redact-hook.ts +124 -0
- package/src/index.ts +1 -1
- package/src/middleware/redactor.ts +75 -0
- package/src/plugins/opencode-hush.ts +30 -0
- package/src/plugins/sensitive-patterns.ts +71 -0
- package/src/vault/token-vault.ts +18 -4
- package/tests/init.test.ts +101 -0
- package/tests/opencode-plugin.test.ts +148 -0
- package/tests/redact-hook.test.ts +142 -0
- package/tests/redaction.test.ts +96 -0
package/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- build
|
|
3
|
+
- e2e
|
|
4
|
+
|
|
5
|
+
build:
|
|
6
|
+
stage: build
|
|
7
|
+
image: node:22-slim
|
|
8
|
+
script:
|
|
9
|
+
- npm ci
|
|
10
|
+
- npm run build
|
|
11
|
+
- npm test
|
|
12
|
+
artifacts:
|
|
13
|
+
paths:
|
|
14
|
+
- dist/
|
|
15
|
+
expire_in: 1 hour
|
|
16
|
+
cache:
|
|
17
|
+
key:
|
|
18
|
+
files:
|
|
19
|
+
- package-lock.json
|
|
20
|
+
paths:
|
|
21
|
+
- node_modules/
|
|
22
|
+
|
|
23
|
+
e2e-plugin-blocks-env:
|
|
24
|
+
stage: e2e
|
|
25
|
+
image: node:22-slim
|
|
26
|
+
needs: [build]
|
|
27
|
+
cache:
|
|
28
|
+
key:
|
|
29
|
+
files:
|
|
30
|
+
- package-lock.json
|
|
31
|
+
paths:
|
|
32
|
+
- node_modules/
|
|
33
|
+
policy: pull
|
|
34
|
+
before_script:
|
|
35
|
+
- npm install -g opencode
|
|
36
|
+
script:
|
|
37
|
+
- chmod +x scripts/e2e-plugin-block.sh
|
|
38
|
+
- ./scripts/e2e-plugin-block.sh
|
|
39
|
+
variables:
|
|
40
|
+
ZHIPUAI_API_KEY: $ZHIPUAI_API_KEY
|
|
41
|
+
|
|
42
|
+
e2e-proxy-redacts-pii:
|
|
43
|
+
stage: e2e
|
|
44
|
+
image: node:22-slim
|
|
45
|
+
needs: [build]
|
|
46
|
+
cache:
|
|
47
|
+
key:
|
|
48
|
+
files:
|
|
49
|
+
- package-lock.json
|
|
50
|
+
paths:
|
|
51
|
+
- node_modules/
|
|
52
|
+
policy: pull
|
|
53
|
+
before_script:
|
|
54
|
+
- npm install -g opencode
|
|
55
|
+
script:
|
|
56
|
+
- chmod +x scripts/e2e-proxy-live.sh
|
|
57
|
+
- ./scripts/e2e-proxy-live.sh
|
|
58
|
+
variables:
|
|
59
|
+
ZHIPUAI_API_KEY: $ZHIPUAI_API_KEY
|
package/README.md
CHANGED
|
@@ -57,12 +57,15 @@ Create `opencode.json` in your project root:
|
|
|
57
57
|
|
|
58
58
|
### Gemini CLI
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
Add `.gemini/.env` to your project root (or set the env var directly):
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
|
|
63
|
+
# .gemini/.env
|
|
64
|
+
CODE_ASSIST_ENDPOINT=http://127.0.0.1:4000
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
Or: `CODE_ASSIST_ENDPOINT=http://127.0.0.1:4000 gemini`
|
|
68
|
+
|
|
66
69
|
### Verify it works
|
|
67
70
|
|
|
68
71
|
When your AI tool sends a request containing PII, the hush terminal shows:
|
|
@@ -73,6 +76,150 @@ INFO: Redacted sensitive data from request path="/v1/messages" tokenCount=2 d
|
|
|
73
76
|
|
|
74
77
|
Your tool still sees the real data (rehydrated locally). The LLM provider only ever sees tokens like `[USER_EMAIL_f22c5a]`.
|
|
75
78
|
|
|
79
|
+
## Enforce for Your Team
|
|
80
|
+
|
|
81
|
+
Commit config files to your repo so every developer automatically routes through hush — no manual setup per person.
|
|
82
|
+
|
|
83
|
+
Copy the files from [`examples/team-config/`](examples/team-config/) into your project root:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
your-project/
|
|
87
|
+
├── .claude/settings.json # Claude Code → hush
|
|
88
|
+
├── .codex/config.toml # Codex → hush
|
|
89
|
+
├── .gemini/.env # Gemini CLI → hush
|
|
90
|
+
└── opencode.json # OpenCode → hush
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Claude Code** — `.claude/settings.json`:
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"env": {
|
|
97
|
+
"ANTHROPIC_BASE_URL": "http://127.0.0.1:4000"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Codex** — `.codex/config.toml`:
|
|
103
|
+
```toml
|
|
104
|
+
model_provider = "hush"
|
|
105
|
+
|
|
106
|
+
[model_providers.hush]
|
|
107
|
+
base_url = "http://127.0.0.1:4000/v1"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**OpenCode** — `opencode.json`:
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"provider": {
|
|
114
|
+
"zai-coding-plan": {
|
|
115
|
+
"options": {
|
|
116
|
+
"baseURL": "http://127.0.0.1:4000/api/coding/paas/v4"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Gemini CLI** — `.gemini/.env`:
|
|
124
|
+
```
|
|
125
|
+
CODE_ASSIST_ENDPOINT=http://127.0.0.1:4000
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Each developer just needs `hush` running locally. All AI tools in the project will route through it automatically.
|
|
129
|
+
|
|
130
|
+
## Hooks Mode (Claude Code)
|
|
131
|
+
|
|
132
|
+
Hush can also run as a **Claude Code hook** — redacting PII from tool outputs *before Claude ever sees them*. No proxy required.
|
|
133
|
+
|
|
134
|
+
### Setup
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
hush init --hooks
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This adds a `PostToolUse` hook to `.claude/settings.json` that runs `hush redact-hook` after every `Bash`, `Read`, `Grep`, and `WebFetch` tool call.
|
|
141
|
+
|
|
142
|
+
Use `--local` to write to `settings.local.json` instead (for personal overrides not committed to the repo).
|
|
143
|
+
|
|
144
|
+
### How it works
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
Local files/commands → [Hook: redact before Claude sees] → Claude's context
|
|
148
|
+
↓
|
|
149
|
+
API request
|
|
150
|
+
↓
|
|
151
|
+
[Proxy: redact before cloud]
|
|
152
|
+
↓
|
|
153
|
+
LLM Provider
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
When a tool runs (e.g., `cat .env`), the hook inspects the response for PII. If PII is found, the hook **blocks** the raw output and provides Claude with the redacted version instead. Claude only ever sees `[USER_EMAIL_f22c5a]`, not `alice@company.com`.
|
|
157
|
+
|
|
158
|
+
### Hooks vs Proxy
|
|
159
|
+
|
|
160
|
+
| | Hooks Mode | Proxy Mode |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| **What's protected** | Tool outputs (before Claude sees them) | API requests (before they leave your machine) |
|
|
163
|
+
| **Setup** | `hush init --hooks` | `hush` + point `ANTHROPIC_BASE_URL` |
|
|
164
|
+
| **Works with** | Claude Code only | Any AI tool |
|
|
165
|
+
| **Defense-in-depth** | Use both for maximum coverage | Use both for maximum coverage |
|
|
166
|
+
|
|
167
|
+
### Defense-in-depth
|
|
168
|
+
|
|
169
|
+
For maximum protection, use both modes together. The team config example in [`examples/team-config/`](examples/team-config/) shows this setup — hooks redact tool outputs and the proxy redacts API requests.
|
|
170
|
+
|
|
171
|
+
## OpenCode Plugin
|
|
172
|
+
|
|
173
|
+
Hush provides an **OpenCode plugin** that blocks reads of sensitive files (`.env`, `*.pem`, `credentials.*`, `id_rsa`, etc.) before the tool executes — the AI model never sees the contents.
|
|
174
|
+
|
|
175
|
+
### Drop-in setup
|
|
176
|
+
|
|
177
|
+
Copy the plugin file and update your `opencode.json`:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
your-project/
|
|
181
|
+
├── .opencode/plugins/hush.ts # plugin file
|
|
182
|
+
└── opencode.json # add "plugin" array
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"provider": {
|
|
188
|
+
"zai-coding-plan": {
|
|
189
|
+
"options": {
|
|
190
|
+
"baseURL": "http://127.0.0.1:4000/api/coding/paas/v4"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"plugin": [".opencode/plugins/hush.ts"]
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Find the drop-in plugin at [`examples/team-config/.opencode/plugins/hush.ts`](examples/team-config/.opencode/plugins/hush.ts).
|
|
199
|
+
|
|
200
|
+
### npm import
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { HushPlugin } from '@aictrl/hush/opencode-plugin'
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### What it blocks
|
|
207
|
+
|
|
208
|
+
| Tool | Blocked when |
|
|
209
|
+
|------|-------------|
|
|
210
|
+
| `read` | File path matches `.env*`, `*credentials*`, `*secret*`, `*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.jks`, `*.keystore`, `*.asc`, `id_rsa*`, `.netrc`, `.pgpass` |
|
|
211
|
+
| `bash` | Commands like `cat`, `head`, `tail`, `less`, `more`, `bat` target a sensitive file |
|
|
212
|
+
|
|
213
|
+
### Plugin + Proxy = Defense-in-depth
|
|
214
|
+
|
|
215
|
+
The plugin blocks reads of known-sensitive filenames. The proxy catches PII in files with normal names (e.g., `config.txt` containing an email). Together they provide two layers of protection:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Tool reads .env → [Plugin: BLOCKED] → model never sees it
|
|
219
|
+
Tool reads config.txt → [Plugin: allowed] → proxy redacts PII → model sees tokens
|
|
220
|
+
(not a sensitive filename)
|
|
221
|
+
```
|
|
222
|
+
|
|
76
223
|
## How it Works
|
|
77
224
|
|
|
78
225
|
1. **Intercept** — Hush sits on your machine between your AI tool and the LLM provider.
|
|
@@ -88,7 +235,7 @@ Your tool still sees the real data (rehydrated locally). The LLM provider only e
|
|
|
88
235
|
| Claude Code | `~/.claude/settings.json` | `/v1/messages` → Anthropic |
|
|
89
236
|
| Codex | `~/.codex/config.toml` | `/v1/chat/completions` → OpenAI |
|
|
90
237
|
| OpenCode | `opencode.json` | `/api/paas/v4/**` → ZhipuAI |
|
|
91
|
-
| Gemini CLI | `
|
|
238
|
+
| Gemini CLI | `.gemini/.env` | `/v1beta/models/**` → Google |
|
|
92
239
|
| Any tool | Point base URL at hush | `/*` catch-all with auto-detect |
|
|
93
240
|
|
|
94
241
|
Hush forwards your existing auth headers transparently — no API keys need to be reconfigured.
|
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
2
|
+
const subcommand = process.argv[2];
|
|
3
|
+
if (subcommand === 'redact-hook') {
|
|
4
|
+
const { run } = await import('./commands/redact-hook.js');
|
|
5
|
+
await run();
|
|
6
|
+
}
|
|
7
|
+
else if (subcommand === 'init') {
|
|
8
|
+
const { run } = await import('./commands/init.js');
|
|
9
|
+
run(process.argv.slice(3));
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
// Default: start the proxy server
|
|
13
|
+
const { app } = await import('./index.js');
|
|
14
|
+
const { createLogger } = await import('./lib/logger.js');
|
|
15
|
+
const log = createLogger('hush-cli');
|
|
16
|
+
const PORT = process.env.PORT || 4000;
|
|
17
|
+
const server = app.listen(PORT, () => {
|
|
18
|
+
log.info(`Hush Semantic Gateway is listening on http://localhost:${PORT}`);
|
|
19
|
+
log.info(`Routes: /v1/messages → Anthropic, /v1/chat/completions → OpenAI, /api/paas/v4/** → ZhipuAI, * → Google`);
|
|
20
|
+
});
|
|
21
|
+
server.on('error', (err) => {
|
|
22
|
+
if (err.code === 'EADDRINUSE') {
|
|
23
|
+
log.error(`Port ${PORT} is already in use. Stop the other process or use PORT=<number> hush`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
log.error({ err }, 'Failed to start server');
|
|
27
|
+
}
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
19
32
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,UAAU,KAAK,aAAa,EAAE,CAAC;IACjC,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;IAC1D,MAAM,GAAG,EAAE,CAAC;AACd,CAAC;KAAM,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;IACjC,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACnD,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;KAAM,CAAC;IACN,kCAAkC;IAClC,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEzD,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;IAEtC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACnC,GAAG,CAAC,IAAI,CAAC,0DAA0D,IAAI,EAAE,CAAC,CAAC;QAC3E,GAAG,CAAC,IAAI,CAAC,wGAAwG,CAAC,CAAC;IACrH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;QAChD,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC9B,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,sEAAsE,CAAC,CAAC;QAChG,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,wBAAwB,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hush init — Generate Claude Code hook configuration
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* hush init --hooks Write to .claude/settings.json
|
|
6
|
+
* hush init --hooks --local Write to .claude/settings.local.json
|
|
7
|
+
*/
|
|
8
|
+
export declare function run(args: string[]): void;
|
|
9
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0DH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA0CxC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hush init — Generate Claude Code hook configuration
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* hush init --hooks Write to .claude/settings.json
|
|
6
|
+
* hush init --hooks --local Write to .claude/settings.local.json
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
const HOOK_CONFIG = {
|
|
11
|
+
hooks: {
|
|
12
|
+
PostToolUse: [
|
|
13
|
+
{
|
|
14
|
+
matcher: 'Bash|Read|Grep|WebFetch',
|
|
15
|
+
hooks: [
|
|
16
|
+
{
|
|
17
|
+
type: 'command',
|
|
18
|
+
command: 'hush redact-hook',
|
|
19
|
+
timeout: 10,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
function hasHushHook(settings) {
|
|
27
|
+
const postToolUse = settings.hooks?.PostToolUse;
|
|
28
|
+
if (!Array.isArray(postToolUse))
|
|
29
|
+
return false;
|
|
30
|
+
return postToolUse.some((entry) => entry.hooks?.some((h) => h.command?.includes('hush redact-hook')));
|
|
31
|
+
}
|
|
32
|
+
function mergeHooks(existing) {
|
|
33
|
+
const merged = { ...existing };
|
|
34
|
+
if (!merged.hooks) {
|
|
35
|
+
merged.hooks = {};
|
|
36
|
+
}
|
|
37
|
+
if (!Array.isArray(merged.hooks.PostToolUse)) {
|
|
38
|
+
merged.hooks.PostToolUse = [];
|
|
39
|
+
}
|
|
40
|
+
merged.hooks = { ...merged.hooks, PostToolUse: [...merged.hooks.PostToolUse, ...HOOK_CONFIG.hooks.PostToolUse] };
|
|
41
|
+
return merged;
|
|
42
|
+
}
|
|
43
|
+
export function run(args) {
|
|
44
|
+
const hasHooksFlag = args.includes('--hooks');
|
|
45
|
+
const isLocal = args.includes('--local');
|
|
46
|
+
if (!hasHooksFlag) {
|
|
47
|
+
process.stderr.write('Usage: hush init --hooks [--local]\n');
|
|
48
|
+
process.stderr.write('\n');
|
|
49
|
+
process.stderr.write('Options:\n');
|
|
50
|
+
process.stderr.write(' --hooks Generate Claude Code PostToolUse hook config\n');
|
|
51
|
+
process.stderr.write(' --local Write to settings.local.json instead of settings.json\n');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const claudeDir = join(process.cwd(), '.claude');
|
|
55
|
+
const filename = isLocal ? 'settings.local.json' : 'settings.json';
|
|
56
|
+
const filePath = join(claudeDir, filename);
|
|
57
|
+
// Ensure .claude/ exists
|
|
58
|
+
if (!existsSync(claudeDir)) {
|
|
59
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
// Read existing settings or start fresh
|
|
62
|
+
let settings = {};
|
|
63
|
+
if (existsSync(filePath)) {
|
|
64
|
+
try {
|
|
65
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
66
|
+
settings = JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
process.stderr.write(`Warning: could not parse ${filePath}, starting fresh\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Idempotency check
|
|
73
|
+
if (hasHushHook(settings)) {
|
|
74
|
+
process.stdout.write(`hush hooks already configured in ${filePath}\n`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const merged = mergeHooks(settings);
|
|
78
|
+
writeFileSync(filePath, JSON.stringify(merged, null, 2) + '\n');
|
|
79
|
+
process.stdout.write(`Wrote hush hooks config to ${filePath}\n`);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hush redact-hook — Claude Code PostToolUse hook handler
|
|
3
|
+
*
|
|
4
|
+
* Reads the hook payload from stdin, redacts PII from the tool response,
|
|
5
|
+
* and blocks the output (replacing it with redacted text) if PII was found.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* 0 — success (may or may not block)
|
|
9
|
+
* 2 — malformed input (blocks the tool call per hooks spec)
|
|
10
|
+
*/
|
|
11
|
+
export declare function run(): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=redact-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-hook.d.ts","sourceRoot":"","sources":["../../src/commands/redact-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwEH,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA0CzC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hush redact-hook — Claude Code PostToolUse hook handler
|
|
3
|
+
*
|
|
4
|
+
* Reads the hook payload from stdin, redacts PII from the tool response,
|
|
5
|
+
* and blocks the output (replacing it with redacted text) if PII was found.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* 0 — success (may or may not block)
|
|
9
|
+
* 2 — malformed input (blocks the tool call per hooks spec)
|
|
10
|
+
*/
|
|
11
|
+
import { Redactor } from '../middleware/redactor.js';
|
|
12
|
+
/** Collect all text from a tool_response object. */
|
|
13
|
+
function extractText(toolResponse) {
|
|
14
|
+
if (!toolResponse || typeof toolResponse !== 'object')
|
|
15
|
+
return null;
|
|
16
|
+
const parts = [];
|
|
17
|
+
if (typeof toolResponse.stdout === 'string' && toolResponse.stdout) {
|
|
18
|
+
parts.push(toolResponse.stdout);
|
|
19
|
+
}
|
|
20
|
+
if (typeof toolResponse.stderr === 'string' && toolResponse.stderr) {
|
|
21
|
+
parts.push(toolResponse.stderr);
|
|
22
|
+
}
|
|
23
|
+
// Read tool nests content under file.content
|
|
24
|
+
if (toolResponse.file && typeof toolResponse.file.content === 'string' && toolResponse.file.content) {
|
|
25
|
+
parts.push(toolResponse.file.content);
|
|
26
|
+
}
|
|
27
|
+
if (typeof toolResponse.content === 'string' && toolResponse.content) {
|
|
28
|
+
parts.push(toolResponse.content);
|
|
29
|
+
}
|
|
30
|
+
if (typeof toolResponse.output === 'string' && toolResponse.output) {
|
|
31
|
+
parts.push(toolResponse.output);
|
|
32
|
+
}
|
|
33
|
+
return parts.length > 0 ? parts.join('\n') : null;
|
|
34
|
+
}
|
|
35
|
+
/** Redact PII from the tool response text. */
|
|
36
|
+
function redactToolResponse(toolResponse, redactor) {
|
|
37
|
+
const text = extractText(toolResponse);
|
|
38
|
+
if (!text)
|
|
39
|
+
return { text: '', hasRedacted: false };
|
|
40
|
+
const { content, hasRedacted } = redactor.redact(text);
|
|
41
|
+
return { text: content, hasRedacted };
|
|
42
|
+
}
|
|
43
|
+
function readStdin() {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
47
|
+
process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
48
|
+
process.stdin.on('error', reject);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function run() {
|
|
52
|
+
let raw;
|
|
53
|
+
try {
|
|
54
|
+
raw = await readStdin();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
process.stderr.write('hush redact-hook: failed to read stdin\n');
|
|
58
|
+
process.exit(2);
|
|
59
|
+
}
|
|
60
|
+
if (!raw.trim()) {
|
|
61
|
+
// Empty stdin — nothing to redact
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
let payload;
|
|
65
|
+
try {
|
|
66
|
+
payload = JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
process.stderr.write('hush redact-hook: invalid JSON on stdin\n');
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
if (!payload.tool_response) {
|
|
73
|
+
// No tool_response to redact
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
const redactor = new Redactor();
|
|
77
|
+
const { text, hasRedacted } = redactToolResponse(payload.tool_response, redactor);
|
|
78
|
+
if (!hasRedacted) {
|
|
79
|
+
// No PII found — let Claude Code keep the original output
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
const response = {
|
|
83
|
+
decision: 'block',
|
|
84
|
+
reason: text,
|
|
85
|
+
};
|
|
86
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=redact-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-hook.js","sourceRoot":"","sources":["../../src/commands/redact-hook.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -131,7 +131,7 @@ async function proxyRequest(req, res, targetUrl, headers) {
|
|
|
131
131
|
}
|
|
132
132
|
catch (error) {
|
|
133
133
|
log.error({ err: error, path: req.path }, 'Failed to forward request');
|
|
134
|
-
res.status(
|
|
134
|
+
res.status(502).json({ error: 'Gateway forwarding failed' });
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
@@ -23,6 +23,11 @@ export declare class Redactor {
|
|
|
23
23
|
* Common PII Regex patterns.
|
|
24
24
|
*/
|
|
25
25
|
private static readonly PATTERNS;
|
|
26
|
+
/**
|
|
27
|
+
* Cloud provider key patterns — Tier 1 only (unique prefixes, very low false-positive risk).
|
|
28
|
+
* Sources: GitHub secret scanning, gitleaks, trufflehog.
|
|
29
|
+
*/
|
|
30
|
+
private static readonly CLOUD_KEY_PATTERNS;
|
|
26
31
|
/**
|
|
27
32
|
* Redact sensitive information from a JSON object or string.
|
|
28
33
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redactor.d.ts","sourceRoot":"","sources":["../../src/middleware/redactor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,GAAG,CAAC;IACb,uCAAuC;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAa9B;IAEF;;;;;OAKG;IACI,MAAM,CAAC,KAAK,EAAE,GAAG,GAAG,eAAe;
|
|
1
|
+
{"version":3,"file":"redactor.d.ts","sourceRoot":"","sources":["../../src/middleware/redactor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,GAAG,CAAC;IACb,uCAAuC;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAa9B;IAEF;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CA4CxC;IAEF;;;;;OAKG;IACI,MAAM,CAAC,KAAK,EAAE,GAAG,GAAG,eAAe;CA6H3C"}
|
|
@@ -30,6 +30,55 @@ export class Redactor {
|
|
|
30
30
|
/** Phone Numbers (robust version) */
|
|
31
31
|
PHONE: /(?:^|[\s:;])(?:\+\d{1,3}[-. ]?)?\(?\d{2,4}\)?[-. ]\d{3,4}[-. ]\d{3,4}(?:\s*(?:ext|x)\s*\d+)?/g,
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Cloud provider key patterns — Tier 1 only (unique prefixes, very low false-positive risk).
|
|
35
|
+
* Sources: GitHub secret scanning, gitleaks, trufflehog.
|
|
36
|
+
*/
|
|
37
|
+
static CLOUD_KEY_PATTERNS = [
|
|
38
|
+
// AWS
|
|
39
|
+
{ re: /\b((?:AKIA|ASIA|ABIA|ACCA)[A-Z2-7]{16})\b/g, label: 'AWS_KEY' },
|
|
40
|
+
// GCP / Firebase
|
|
41
|
+
{ re: /\b(AIza[\w-]{35})\b/g, label: 'GCP_KEY' },
|
|
42
|
+
{ re: /\b(GOCSPX-[a-zA-Z0-9_-]{28})\b/g, label: 'GCP_OAUTH' },
|
|
43
|
+
// GitHub
|
|
44
|
+
{ re: /\b(ghp_[0-9a-zA-Z]{36})\b/g, label: 'GITHUB_PAT' },
|
|
45
|
+
{ re: /\b(gho_[0-9a-zA-Z]{36})\b/g, label: 'GITHUB_OAUTH' },
|
|
46
|
+
{ re: /\b(ghu_[0-9a-zA-Z]{36})\b/g, label: 'GITHUB_U2S' },
|
|
47
|
+
{ re: /\b(ghs_[0-9a-zA-Z]{36})\b/g, label: 'GITHUB_S2S' },
|
|
48
|
+
{ re: /\b(ghr_[0-9a-zA-Z]{36})\b/g, label: 'GITHUB_REFRESH' },
|
|
49
|
+
{ re: /\b(github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})\b/g, label: 'GITHUB_FINE_PAT' },
|
|
50
|
+
// GitLab
|
|
51
|
+
{ re: /\b(glpat-[\w-]{20})\b/g, label: 'GITLAB_PAT' },
|
|
52
|
+
{ re: /\b(glptt-[a-zA-Z0-9_-]{40})\b/g, label: 'GITLAB_TRIGGER' },
|
|
53
|
+
// Slack
|
|
54
|
+
{ re: /\b(xoxb-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*)\b/g, label: 'SLACK_BOT' },
|
|
55
|
+
{ re: /\b(xox[pe]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9-]+)\b/g, label: 'SLACK_TOKEN' },
|
|
56
|
+
// Stripe
|
|
57
|
+
{ re: /\b(sk_(?:live|test)_[a-zA-Z0-9]{10,99})\b/g, label: 'STRIPE_SECRET' },
|
|
58
|
+
{ re: /\b(rk_(?:live|test)_[a-zA-Z0-9]{10,99})\b/g, label: 'STRIPE_RESTRICTED' },
|
|
59
|
+
{ re: /\b(whsec_[a-zA-Z0-9]{24,})\b/g, label: 'STRIPE_WEBHOOK' },
|
|
60
|
+
// SendGrid (SG. + base64url with internal dot separator)
|
|
61
|
+
{ re: /\b(SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43})\b/g, label: 'SENDGRID_KEY' },
|
|
62
|
+
// npm
|
|
63
|
+
{ re: /\b(npm_[a-z0-9]{36})\b/gi, label: 'NPM_TOKEN' },
|
|
64
|
+
// PyPI
|
|
65
|
+
{ re: /\b(pypi-AgEIcHlwaS5vcmc[\w-]{50,})\b/g, label: 'PYPI_TOKEN' },
|
|
66
|
+
// Docker Hub
|
|
67
|
+
{ re: /\b(dckr_pat_[a-zA-Z0-9_-]{27,})\b/g, label: 'DOCKER_PAT' },
|
|
68
|
+
// Anthropic
|
|
69
|
+
{ re: /\b(sk-ant-[a-zA-Z0-9_-]{36,})\b/g, label: 'ANTHROPIC_KEY' },
|
|
70
|
+
// OpenAI (with T3BlbkFJ marker)
|
|
71
|
+
{ re: /\b(sk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,})\b/g, label: 'OPENAI_KEY' },
|
|
72
|
+
// DigitalOcean
|
|
73
|
+
{ re: /\b(do[por]_v1_[a-f0-9]{64})\b/g, label: 'DIGITALOCEAN_TOKEN' },
|
|
74
|
+
// HashiCorp Vault
|
|
75
|
+
{ re: /\b(hvs\.[\w-]{90,})\b/g, label: 'VAULT_TOKEN' },
|
|
76
|
+
{ re: /\b(hvb\.[\w-]{90,})\b/g, label: 'VAULT_BATCH' },
|
|
77
|
+
// Supabase
|
|
78
|
+
{ re: /\b(sbp_[a-f0-9]{40})\b/g, label: 'SUPABASE_PAT' },
|
|
79
|
+
{ re: /\b(sb_secret_[a-zA-Z0-9_-]{20,})\b/g, label: 'SUPABASE_SECRET' },
|
|
80
|
+
// PEM private keys (multiline — matched separately in redactPEMKeys)
|
|
81
|
+
];
|
|
33
82
|
/**
|
|
34
83
|
* Redact sensitive information from a JSON object or string.
|
|
35
84
|
*
|
|
@@ -78,6 +127,26 @@ export class Redactor {
|
|
|
78
127
|
tokens.set(token, match);
|
|
79
128
|
return token;
|
|
80
129
|
});
|
|
130
|
+
// Redact cloud provider keys BEFORE generic patterns — specific prefixed
|
|
131
|
+
// keys must be matched first so they don't get partially eaten by SECRET
|
|
132
|
+
// or CREDIT_CARD patterns.
|
|
133
|
+
for (const { re, label } of Redactor.CLOUD_KEY_PATTERNS) {
|
|
134
|
+
re.lastIndex = 0;
|
|
135
|
+
text = text.replace(re, (match, p1) => {
|
|
136
|
+
hasRedacted = true;
|
|
137
|
+
const val = p1 || match;
|
|
138
|
+
const token = `[${label}_${tokenHash(val)}]`;
|
|
139
|
+
tokens.set(token, val);
|
|
140
|
+
return token;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Redact PEM private keys
|
|
144
|
+
text = text.replace(/-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY-----[\s\S]{64,}?-----END[ A-Z0-9_-]{0,100}PRIVATE KEY-----/g, (match) => {
|
|
145
|
+
hasRedacted = true;
|
|
146
|
+
const token = `[PRIVATE_KEY_${tokenHash(match)}]`;
|
|
147
|
+
tokens.set(token, match);
|
|
148
|
+
return token;
|
|
149
|
+
});
|
|
81
150
|
// Redact Secrets in text (e.g. "api_key=...")
|
|
82
151
|
text = text.replace(Redactor.PATTERNS.SECRET, (match, p1) => {
|
|
83
152
|
hasRedacted = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redactor.js","sourceRoot":"","sources":["../../src/middleware/redactor.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"redactor.js","sourceRoot":"","sources":["../../src/middleware/redactor.ts"],"names":[],"mappings":"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"}
|