@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.
Files changed (47) hide show
  1. package/.gitlab-ci.yml +59 -0
  2. package/README.md +150 -3
  3. package/dist/cli.js +30 -17
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/init.d.ts +9 -0
  6. package/dist/commands/init.d.ts.map +1 -0
  7. package/dist/commands/init.js +81 -0
  8. package/dist/commands/init.js.map +1 -0
  9. package/dist/commands/redact-hook.d.ts +12 -0
  10. package/dist/commands/redact-hook.d.ts.map +1 -0
  11. package/dist/commands/redact-hook.js +89 -0
  12. package/dist/commands/redact-hook.js.map +1 -0
  13. package/dist/index.js +1 -1
  14. package/dist/middleware/redactor.d.ts +5 -0
  15. package/dist/middleware/redactor.d.ts.map +1 -1
  16. package/dist/middleware/redactor.js +69 -0
  17. package/dist/middleware/redactor.js.map +1 -1
  18. package/dist/plugins/opencode-hush.d.ts +21 -0
  19. package/dist/plugins/opencode-hush.d.ts.map +1 -0
  20. package/dist/plugins/opencode-hush.js +25 -0
  21. package/dist/plugins/opencode-hush.js.map +1 -0
  22. package/dist/plugins/sensitive-patterns.d.ts +15 -0
  23. package/dist/plugins/sensitive-patterns.d.ts.map +1 -0
  24. package/dist/plugins/sensitive-patterns.js +69 -0
  25. package/dist/plugins/sensitive-patterns.js.map +1 -0
  26. package/dist/vault/token-vault.d.ts.map +1 -1
  27. package/dist/vault/token-vault.js +16 -3
  28. package/dist/vault/token-vault.js.map +1 -1
  29. package/examples/team-config/.claude/settings.json +19 -0
  30. package/examples/team-config/.codex/config.toml +4 -0
  31. package/examples/team-config/.opencode/plugins/hush.ts +76 -0
  32. package/examples/team-config/opencode.json +10 -0
  33. package/package.json +11 -1
  34. package/scripts/e2e-plugin-block.sh +142 -0
  35. package/scripts/e2e-proxy-live.sh +185 -0
  36. package/src/cli.ts +28 -16
  37. package/src/commands/init.ts +107 -0
  38. package/src/commands/redact-hook.ts +124 -0
  39. package/src/index.ts +1 -1
  40. package/src/middleware/redactor.ts +75 -0
  41. package/src/plugins/opencode-hush.ts +30 -0
  42. package/src/plugins/sensitive-patterns.ts +71 -0
  43. package/src/vault/token-vault.ts +18 -4
  44. package/tests/init.test.ts +101 -0
  45. package/tests/opencode-plugin.test.ts +148 -0
  46. package/tests/redact-hook.test.ts +142 -0
  47. 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
- Gemini CLI only supports env vars for endpoint override (no settings file option):
60
+ Add `.gemini/.env` to your project root (or set the env var directly):
61
61
 
62
62
  ```bash
63
- CODE_ASSIST_ENDPOINT=http://127.0.0.1:4000 gemini
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 | `CODE_ASSIST_ENDPOINT` env var | `/v1beta/models/**` → Google |
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
- import { app } from './index.js';
3
- import { createLogger } from './lib/logger.js';
4
- const log = createLogger('hush-cli');
5
- const PORT = process.env.PORT || 4000;
6
- const server = app.listen(PORT, () => {
7
- log.info(`Hush Semantic Gateway is listening on http://localhost:${PORT}`);
8
- log.info(`Routes: /v1/messages Anthropic, /v1/chat/completions OpenAI, /api/paas/v4/** → ZhipuAI, * → Google`);
9
- });
10
- server.on('error', (err) => {
11
- if (err.code === 'EADDRINUSE') {
12
- log.error(`Port ${PORT} is already in use. Stop the other process or use PORT=<number> hush`);
13
- }
14
- else {
15
- log.error({ err }, 'Failed to start server');
16
- }
17
- process.exit(1);
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":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;AACrC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACnC,GAAG,CAAC,IAAI,CAAC,0DAA0D,IAAI,EAAE,CAAC,CAAC;IAC3E,GAAG,CAAC,IAAI,CAAC,wGAAwG,CAAC,CAAC;AACrH,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;IAChD,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC9B,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,sEAAsE,CAAC,CAAC;IAChG,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,wBAAwB,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
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(500).json({ error: 'Gateway forwarding failed' });
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;CAoG3C"}
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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,0EAA0E;AAC1E,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC;AAcD;;GAEG;AACH,MAAM,OAAO,QAAQ;IACnB;;OAEG;IACK,MAAM,CAAU,QAAQ,GAAG;QACjC,kFAAkF;QAClF,KAAK,EAAE,kEAAkE;QACzE,yBAAyB;QACzB,IAAI,EAAE,8BAA8B;QACpC,yBAAyB;QACzB,IAAI,EAAE,spBAAspB;QAC5pB,wDAAwD;QACxD,MAAM,EAAE,+GAA+G;QACvH,0BAA0B;QAC1B,WAAW,EAAE,0BAA0B;QACvC,qCAAqC;QACrC,KAAK,EAAE,+FAA+F;KACvG,CAAC;IAEF;;;OAGG;IACK,MAAM,CAAU,kBAAkB,GAAyC;QACjF,MAAM;QACN,EAAE,EAAE,EAAE,4CAA4C,EAAE,KAAK,EAAE,SAAS,EAAE;QACtE,iBAAiB;QACjB,EAAE,EAAE,EAAE,sBAAsB,EAAE,KAAK,EAAE,SAAS,EAAE;QAChD,EAAE,EAAE,EAAE,iCAAiC,EAAE,KAAK,EAAE,WAAW,EAAE;QAC7D,SAAS;QACT,EAAE,EAAE,EAAE,4BAA4B,EAAE,KAAK,EAAE,YAAY,EAAE;QACzD,EAAE,EAAE,EAAE,4BAA4B,EAAE,KAAK,EAAE,cAAc,EAAE;QAC3D,EAAE,EAAE,EAAE,4BAA4B,EAAE,KAAK,EAAE,YAAY,EAAE;QACzD,EAAE,EAAE,EAAE,4BAA4B,EAAE,KAAK,EAAE,YAAY,EAAE;QACzD,EAAE,EAAE,EAAE,4BAA4B,EAAE,KAAK,EAAE,gBAAgB,EAAE;QAC7D,EAAE,EAAE,EAAE,mDAAmD,EAAE,KAAK,EAAE,iBAAiB,EAAE;QACrF,SAAS;QACT,EAAE,EAAE,EAAE,wBAAwB,EAAE,KAAK,EAAE,YAAY,EAAE;QACrD,EAAE,EAAE,EAAE,gCAAgC,EAAE,KAAK,EAAE,gBAAgB,EAAE;QACjE,QAAQ;QACR,EAAE,EAAE,EAAE,oDAAoD,EAAE,KAAK,EAAE,WAAW,EAAE;QAChF,EAAE,EAAE,EAAE,wDAAwD,EAAE,KAAK,EAAE,aAAa,EAAE;QACtF,SAAS;QACT,EAAE,EAAE,EAAE,4CAA4C,EAAE,KAAK,EAAE,eAAe,EAAE;QAC5E,EAAE,EAAE,EAAE,4CAA4C,EAAE,KAAK,EAAE,mBAAmB,EAAE;QAChF,EAAE,EAAE,EAAE,+BAA+B,EAAE,KAAK,EAAE,gBAAgB,EAAE;QAChE,yDAAyD;QACzD,EAAE,EAAE,EAAE,iDAAiD,EAAE,KAAK,EAAE,cAAc,EAAE;QAChF,MAAM;QACN,EAAE,EAAE,EAAE,0BAA0B,EAAE,KAAK,EAAE,WAAW,EAAE;QACtD,OAAO;QACP,EAAE,EAAE,EAAE,uCAAuC,EAAE,KAAK,EAAE,YAAY,EAAE;QACpE,aAAa;QACb,EAAE,EAAE,EAAE,oCAAoC,EAAE,KAAK,EAAE,YAAY,EAAE;QACjE,YAAY;QACZ,EAAE,EAAE,EAAE,kCAAkC,EAAE,KAAK,EAAE,eAAe,EAAE;QAClE,gCAAgC;QAChC,EAAE,EAAE,EAAE,+EAA+E,EAAE,KAAK,EAAE,YAAY,EAAE;QAC5G,eAAe;QACf,EAAE,EAAE,EAAE,gCAAgC,EAAE,KAAK,EAAE,oBAAoB,EAAE;QACrE,kBAAkB;QAClB,EAAE,EAAE,EAAE,wBAAwB,EAAE,KAAK,EAAE,aAAa,EAAE;QACtD,EAAE,EAAE,EAAE,wBAAwB,EAAE,KAAK,EAAE,aAAa,EAAE;QACtD,WAAW;QACX,EAAE,EAAE,EAAE,yBAAyB,EAAE,KAAK,EAAE,cAAc,EAAE;QACxD,EAAE,EAAE,EAAE,qCAAqC,EAAE,KAAK,EAAE,iBAAiB,EAAE;QACvE,qEAAqE;KACtE,CAAC;IAEF;;;;;OAKG;IACI,MAAM,CAAC,KAAU;QACtB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,IAAI,WAAW,GAAG,KAAK,CAAC;QAExB,0DAA0D;QAC1D,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YACxD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC,CAAC,KAAK,CAAC;QAEV,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;QAEjG,MAAM,OAAO,GAAG,CAAC,IAAS,EAAE,OAAgB,EAAO,EAAE;YACnD,wCAAwC;YACxC,IAAI,OAAO,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACxC,MAAM,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBACjE,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBACxD,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,qBAAqB,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;oBACtD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBACxB,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;YAED,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,IAAI,IAAI,GAAG,IAAI,CAAC;gBAEhB,gBAAgB;gBAChB,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;oBACrD,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,eAAe,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;oBACjD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;oBACzB,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,2BAA2B;gBAC3B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;oBACpD,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,eAAe,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;oBACjD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;oBACzB,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,2BAA2B;gBAC3B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;oBACpD,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,kBAAkB,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;oBACpD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;oBACzB,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,yEAAyE;gBACzE,yEAAyE;gBACzE,2BAA2B;gBAC3B,KAAK,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,QAAQ,CAAC,kBAAkB,EAAE,CAAC;oBACxD,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;oBACjB,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,EAAU,EAAE,EAAE;wBAC5C,WAAW,GAAG,IAAI,CAAC;wBACnB,MAAM,GAAG,GAAG,EAAE,IAAI,KAAK,CAAC;wBACxB,MAAM,KAAK,GAAG,IAAI,KAAK,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;wBAC7C,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;wBACvB,OAAO,KAAK,CAAC;oBACf,CAAC,CAAC,CAAC;gBACL,CAAC;gBAED,0BAA0B;gBAC1B,IAAI,GAAG,IAAI,CAAC,OAAO,CACjB,qGAAqG,EACrG,CAAC,KAAK,EAAE,EAAE;oBACR,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,gBAAgB,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;oBAClD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;oBACzB,OAAO,KAAK,CAAC;gBACf,CAAC,CACF,CAAC;gBAEF,8CAA8C;gBAC9C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;oBAC1D,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,qBAAqB,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC;oBACpD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;oBACtB,OAAO,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBAClC,CAAC,CAAC,CAAC;gBAEH,sBAAsB;gBACtB,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC3D,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,iBAAiB,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;oBACnD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;oBACzB,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,uBAAuB;gBACvB,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;oBACrD,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM,KAAK,GAAG,iBAAiB,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;oBACnD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;oBACzB,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACzC,CAAC;YAED,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9C,MAAM,GAAG,GAAQ,EAAE,CAAC;gBACpB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;oBAChD,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBACjC,CAAC;gBACD,OAAO,GAAG,CAAC;YACb,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QAEF,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAExC,OAAO;YACL,OAAO,EAAE,eAAe;YACxB,WAAW;YACX,MAAM;SACP,CAAC;IACJ,CAAC"}