@interven/claude-code-hook 0.1.0

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/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @interven/claude-code-hook
2
+
3
+ PreToolUse hook for [Claude Code](https://docs.anthropic.com/claude/docs/claude-code).
4
+ Scans every tool call — Read, Write, Edit, Bash, Glob, Grep, WebFetch, MCP — through
5
+ Interven before Claude Code executes it. Blocks `.env` reads, denies destructive
6
+ shell commands, redacts secrets, and routes risky actions through human approval.
7
+
8
+ **No Claude Code source changes.** One config file edit, one env var, done.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install -g @interven/claude-code-hook
14
+ # or use npx -y (no install needed, slower cold start)
15
+ ```
16
+
17
+ ## Wire it up
18
+
19
+ Add to `~/.claude/settings.json` (user-wide) or `<repo>/.claude/settings.json`
20
+ (per-repo):
21
+
22
+ ```json
23
+ {
24
+ "hooks": {
25
+ "PreToolUse": [
26
+ {
27
+ "matcher": "*",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "npx -y @interven/claude-code-hook",
32
+ "timeout": 10
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
38
+ }
39
+ ```
40
+
41
+ Set your Interven API key:
42
+
43
+ ```bash
44
+ export INTERVEN_API_KEY=iv_live_xxxxxxxxxxxxxx
45
+ ```
46
+
47
+ Next time Claude Code fires a tool, the hook runs against your Interven
48
+ policies. Try reading `.env` — should be denied with the policy-defined
49
+ reason.
50
+
51
+ ## What gets enforced
52
+
53
+ Depends on your Interven policies. A typical vibe-coder setup (from
54
+ `@interven/policy-packs` → `vibe-coder-starter.yaml`):
55
+
56
+ - **`.env`, `**/secrets/**`, `*.pem`, `id_rsa`, `credentials.json`** — denied on read
57
+ - **Destructive shell** (`rm -rf /`, `dd if=`, `mkfs`, `> /dev/sda`) — denied
58
+ - **`git push` to `main`** — paused for approval
59
+ - **Stripe/OpenAI/AWS/GitHub keys in tool args** — sanitized in audit log
60
+
61
+ Customize the policies to your stack via the Interven console or the
62
+ `@interven/policy-cli` YAML workflow.
63
+
64
+ ## Environment variables
65
+
66
+ | Variable | Default | Purpose |
67
+ |---|---|---|
68
+ | `INTERVEN_API_KEY` | _(required)_ | Your `iv_live_*` key |
69
+ | `INTERVEN_GATEWAY` | `https://api.intervensecurity.com` | Override for self-host |
70
+ | `INTERVEN_TIMEOUT_MS` | `5000` | Per-scan timeout |
71
+ | `INTERVEN_FAIL_CLOSED` | `0` (open) | Set to `1` to deny on hook error |
72
+
73
+ ## Decision mapping
74
+
75
+ | Interven decision | Claude Code response | Effect |
76
+ |---|---|---|
77
+ | `ALLOW` | `decision: "approve"` | Tool runs normally |
78
+ | `DENY` | `decision: "block"` | Tool blocked, reason shown to Claude |
79
+ | `SANITIZE` | `decision: "approve"` | Approved + scan logged for audit; sensitive fields are sanitized at the network layer if the tool then makes an HTTP call out |
80
+ | `REQUIRE_APPROVAL` | `decision: "block"` | Blocked; approve at `app.intervensecurity.com/approvals/<id>`. Retry within 10 min hits the approval-grant window and auto-allows |
81
+
82
+ ## Behavior on failure
83
+
84
+ Default: **fail-open** — if Interven is unreachable, the hook approves the
85
+ tool call and logs a warning to stderr. This keeps the developer loop
86
+ unblocked during partial outages.
87
+
88
+ For environments where blocking on hook failure is preferred (regulated
89
+ contexts):
90
+
91
+ ```bash
92
+ export INTERVEN_FAIL_CLOSED=1
93
+ ```
94
+
95
+ ## How it differs from `@interven/copilot-hook`
96
+
97
+ Same wire model (JSON in, JSON out), different agent. Use this package for
98
+ **Claude Code**; use `@interven/copilot-hook` for **GitHub Copilot Coding
99
+ Agent**. They share the underlying scan API and can be used together if
100
+ your team uses both agents.
101
+
102
+ ## Source + issues
103
+
104
+ [github.com/intervensecurity/claude-code-hook](https://github.com/intervensecurity/claude-code-hook)
package/dist/hook.d.ts ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code PreToolUse hook for Interven.
4
+ *
5
+ * Claude Code invokes the hook script with a JSON document on stdin before
6
+ * every tool call (Read, Write, Edit, Bash, Glob, Grep, WebFetch, MCP tools).
7
+ * The script writes a JSON decision to stdout, or signals via exit code.
8
+ *
9
+ * Wire-up: add to `~/.claude/settings.json` or `<repo>/.claude/settings.json`:
10
+ *
11
+ * {
12
+ * "hooks": {
13
+ * "PreToolUse": [
14
+ * {
15
+ * "matcher": "*",
16
+ * "hooks": [
17
+ * {
18
+ * "type": "command",
19
+ * "command": "npx -y @interven/claude-code-hook"
20
+ * }
21
+ * ]
22
+ * }
23
+ * ]
24
+ * }
25
+ * }
26
+ *
27
+ * Env vars:
28
+ * INTERVEN_API_KEY (required) — your iv_live_* key
29
+ * INTERVEN_GATEWAY (optional) — default https://api.intervensecurity.com
30
+ * INTERVEN_TIMEOUT_MS (optional) — default 5000
31
+ * INTERVEN_FAIL_CLOSED (optional) — set to 1 to deny on hook error; default fail-open
32
+ *
33
+ * Decision mapping (Interven → Claude Code):
34
+ * ALLOW → { "decision": "approve" }
35
+ * DENY → { "decision": "block", "reason": "<details>" }
36
+ * SANITIZE → { "decision": "approve" } with audit-logged sanitize event
37
+ * (Claude Code hooks can't rewrite args; sanitization is
38
+ * enforced when the agent's tool actually fires HTTP)
39
+ * REQUIRE_APPROVAL → { "decision": "block", "reason": "approve at <url>" }
40
+ * (next retry within 10min hits the approval-grant window
41
+ * and auto-allows)
42
+ *
43
+ * Failure modes (network down, gateway 5xx, malformed response):
44
+ * Default to "approve" with a warning logged to stderr so a partial outage
45
+ * doesn't break the developer loop. Set INTERVEN_FAIL_CLOSED=1 to flip.
46
+ */
47
+ /**
48
+ * Map a Claude Code tool call to the {method, url, body} shape Interven scans.
49
+ *
50
+ * Claude Code tools are local (Read, Write, Bash, Edit, ...) — they don't
51
+ * have a real HTTP URL. We synthesize a custom_proxy URL that encodes the
52
+ * tool name + arg fingerprint so policies/baseline can match on it. The
53
+ * body holds the full input.
54
+ */
55
+ export declare function buildScanRequest(toolName: string, toolInput: Record<string, unknown>): {
56
+ method: string;
57
+ url: string;
58
+ body: unknown;
59
+ agent_name: string;
60
+ };
61
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAwEH;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAqBpE"}
package/dist/hook.js ADDED
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code PreToolUse hook for Interven.
4
+ *
5
+ * Claude Code invokes the hook script with a JSON document on stdin before
6
+ * every tool call (Read, Write, Edit, Bash, Glob, Grep, WebFetch, MCP tools).
7
+ * The script writes a JSON decision to stdout, or signals via exit code.
8
+ *
9
+ * Wire-up: add to `~/.claude/settings.json` or `<repo>/.claude/settings.json`:
10
+ *
11
+ * {
12
+ * "hooks": {
13
+ * "PreToolUse": [
14
+ * {
15
+ * "matcher": "*",
16
+ * "hooks": [
17
+ * {
18
+ * "type": "command",
19
+ * "command": "npx -y @interven/claude-code-hook"
20
+ * }
21
+ * ]
22
+ * }
23
+ * ]
24
+ * }
25
+ * }
26
+ *
27
+ * Env vars:
28
+ * INTERVEN_API_KEY (required) — your iv_live_* key
29
+ * INTERVEN_GATEWAY (optional) — default https://api.intervensecurity.com
30
+ * INTERVEN_TIMEOUT_MS (optional) — default 5000
31
+ * INTERVEN_FAIL_CLOSED (optional) — set to 1 to deny on hook error; default fail-open
32
+ *
33
+ * Decision mapping (Interven → Claude Code):
34
+ * ALLOW → { "decision": "approve" }
35
+ * DENY → { "decision": "block", "reason": "<details>" }
36
+ * SANITIZE → { "decision": "approve" } with audit-logged sanitize event
37
+ * (Claude Code hooks can't rewrite args; sanitization is
38
+ * enforced when the agent's tool actually fires HTTP)
39
+ * REQUIRE_APPROVAL → { "decision": "block", "reason": "approve at <url>" }
40
+ * (next retry within 10min hits the approval-grant window
41
+ * and auto-allows)
42
+ *
43
+ * Failure modes (network down, gateway 5xx, malformed response):
44
+ * Default to "approve" with a warning logged to stderr so a partial outage
45
+ * doesn't break the developer loop. Set INTERVEN_FAIL_CLOSED=1 to flip.
46
+ */
47
+ import { createHash } from 'node:crypto';
48
+ const HOOK_VERSION = '0.1.0';
49
+ function readStdin() {
50
+ return new Promise((resolve, reject) => {
51
+ const chunks = [];
52
+ process.stdin.on('data', (c) => chunks.push(Buffer.from(c)));
53
+ process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
54
+ process.stdin.on('error', reject);
55
+ // 5s timeout — Claude Code pipes immediately; if nothing came in by 5s
56
+ // the hook probably wasn't invoked correctly. Fail open.
57
+ setTimeout(() => resolve(''), 5_000);
58
+ });
59
+ }
60
+ function emitDecision(out) {
61
+ process.stdout.write(JSON.stringify(out) + '\n');
62
+ }
63
+ function fail(message, exitCode = 0) {
64
+ const closed = process.env.INTERVEN_FAIL_CLOSED === '1';
65
+ emitDecision(closed
66
+ ? {
67
+ decision: 'block',
68
+ reason: `Interven hook failed-closed: ${message}`,
69
+ permissionDecision: 'deny',
70
+ permissionDecisionReason: `Interven hook failed-closed: ${message}`,
71
+ }
72
+ : {
73
+ decision: 'approve',
74
+ reason: `Interven hook failed-open: ${message}`,
75
+ permissionDecision: 'allow',
76
+ permissionDecisionReason: `Interven hook failed-open: ${message}`,
77
+ });
78
+ // eslint-disable-next-line no-console
79
+ console.error(`[interven-claude-code-hook] ${message}`);
80
+ process.exit(exitCode);
81
+ }
82
+ /**
83
+ * Map a Claude Code tool call to the {method, url, body} shape Interven scans.
84
+ *
85
+ * Claude Code tools are local (Read, Write, Bash, Edit, ...) — they don't
86
+ * have a real HTTP URL. We synthesize a custom_proxy URL that encodes the
87
+ * tool name + arg fingerprint so policies/baseline can match on it. The
88
+ * body holds the full input.
89
+ */
90
+ export function buildScanRequest(toolName, toolInput) {
91
+ const argKeys = Object.keys(toolInput).sort();
92
+ const fingerprint = createHash('sha256')
93
+ .update(JSON.stringify(toolInput))
94
+ .digest('hex')
95
+ .slice(0, 12);
96
+ // Map common Claude Code tools to HTTP verb hints — POST for writes, GET for reads.
97
+ // This lets policy rules match on `verbs: [READ]` or `verbs: [WRITE]` cleanly.
98
+ const writeTools = new Set(['Write', 'Edit', 'NotebookEdit', 'MultiEdit', 'Bash']);
99
+ const verbHint = writeTools.has(toolName) ? 'POST' : 'GET';
100
+ const urlPath = `/claude-code/${toolName}/${fingerprint}`;
101
+ const url = `https://claude-code.interven.local${urlPath}`;
102
+ return {
103
+ method: verbHint,
104
+ url,
105
+ body: { tool: toolName, args: toolInput, args_keys: argKeys },
106
+ agent_name: 'claude-code',
107
+ };
108
+ }
109
+ async function scan(req) {
110
+ const apiKey = process.env.INTERVEN_API_KEY;
111
+ if (!apiKey) {
112
+ throw new Error('INTERVEN_API_KEY environment variable is not set');
113
+ }
114
+ const gateway = (process.env.INTERVEN_GATEWAY || 'https://api.intervensecurity.com').replace(/\/+$/, '');
115
+ const ac = new AbortController();
116
+ const timeoutMs = parseInt(process.env.INTERVEN_TIMEOUT_MS || '5000', 10);
117
+ const timeout = setTimeout(() => ac.abort(), timeoutMs);
118
+ try {
119
+ const resp = await fetch(`${gateway}/v1/scan`, {
120
+ method: 'POST',
121
+ headers: {
122
+ 'content-type': 'application/json',
123
+ authorization: `Bearer ${apiKey}`,
124
+ 'user-agent': `interven-claude-code-hook/${HOOK_VERSION}`,
125
+ },
126
+ body: JSON.stringify({
127
+ method: req.method,
128
+ url: req.url,
129
+ body: req.body,
130
+ agent_name: req.agent_name,
131
+ runtime_type: 'claude_code',
132
+ }),
133
+ signal: ac.signal,
134
+ });
135
+ if (!resp.ok) {
136
+ throw new Error(`HTTP ${resp.status}: ${(await resp.text()).slice(0, 200)}`);
137
+ }
138
+ return (await resp.json());
139
+ }
140
+ finally {
141
+ clearTimeout(timeout);
142
+ }
143
+ }
144
+ async function main() {
145
+ const raw = await readStdin();
146
+ if (!raw.trim()) {
147
+ fail('empty stdin — hook invoked without payload');
148
+ }
149
+ let input;
150
+ try {
151
+ input = JSON.parse(raw);
152
+ }
153
+ catch (e) {
154
+ fail(`invalid JSON on stdin: ${e instanceof Error ? e.message : String(e)}`);
155
+ }
156
+ if (!input.tool_name) {
157
+ // Nothing to scan; allow.
158
+ emitDecision({ decision: 'approve', permissionDecision: 'allow' });
159
+ return;
160
+ }
161
+ const req = buildScanRequest(input.tool_name, input.tool_input ?? {});
162
+ let result;
163
+ try {
164
+ result = await scan(req);
165
+ }
166
+ catch (e) {
167
+ fail(`scan failed: ${e instanceof Error ? e.message : String(e)}`);
168
+ }
169
+ switch (result.decision) {
170
+ case 'ALLOW':
171
+ emitDecision({
172
+ decision: 'approve',
173
+ reason: `Interven ALLOW (risk=${result.risk_band})`,
174
+ permissionDecision: 'allow',
175
+ permissionDecisionReason: `Interven ALLOW (risk=${result.risk_band})`,
176
+ });
177
+ return;
178
+ case 'DENY': {
179
+ const reasons = result.reason_codes.length ? ` (${result.reason_codes.join(', ')})` : '';
180
+ const msg = `Interven DENY${reasons}. Trace: ${result.trace_id}. ` +
181
+ 'See https://app.intervensecurity.com/activity for details.';
182
+ emitDecision({
183
+ decision: 'block',
184
+ reason: msg,
185
+ permissionDecision: 'deny',
186
+ permissionDecisionReason: msg,
187
+ });
188
+ return;
189
+ }
190
+ case 'SANITIZE': {
191
+ // Hook can't rewrite tool args mid-flight. Allow with a notice; the
192
+ // scan event is recorded with SANITIZE for audit. If the agent's tool
193
+ // then makes a real HTTP call out (e.g. WebFetch), the gateway proxy
194
+ // will enforce sanitization at that layer.
195
+ const reasons = result.reason_codes.length ? ` (${result.reason_codes.join(', ')})` : '';
196
+ const msg = `Interven SANITIZE${reasons} — sensitive data detected; logged for audit.`;
197
+ emitDecision({
198
+ decision: 'approve',
199
+ reason: msg,
200
+ permissionDecision: 'allow',
201
+ permissionDecisionReason: msg,
202
+ });
203
+ return;
204
+ }
205
+ case 'REQUIRE_APPROVAL': {
206
+ const approvalUrl = result.approval_id
207
+ ? `https://app.intervensecurity.com/approvals/${result.approval_id}`
208
+ : 'https://app.intervensecurity.com/approvals';
209
+ const msg = `Interven REQUIRE_APPROVAL — analyst review needed. Approve at ${approvalUrl}, ` +
210
+ 'then retry within 10 minutes for auto-allow.';
211
+ emitDecision({
212
+ decision: 'block',
213
+ reason: msg,
214
+ permissionDecision: 'deny',
215
+ permissionDecisionReason: msg,
216
+ });
217
+ return;
218
+ }
219
+ default:
220
+ // Unknown decision — fail open with a notice.
221
+ emitDecision({
222
+ decision: 'approve',
223
+ reason: `Interven returned unknown decision: ${result.decision}`,
224
+ permissionDecision: 'allow',
225
+ permissionDecisionReason: `Interven returned unknown decision: ${result.decision}`,
226
+ });
227
+ }
228
+ }
229
+ main().catch((e) => {
230
+ fail(`unexpected error: ${e instanceof Error ? e.message : String(e)}`);
231
+ });
232
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ {
2
+ "_comment": "Drop this in ~/.claude/settings.json or <repo>/.claude/settings.json to wire Interven into Claude Code.",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "*",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "npx -y @interven/claude-code-hook",
11
+ "timeout": 10
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@interven/claude-code-hook",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code PreToolUse hook for Interven. Scans every Claude Code tool call (Read/Write/Edit/Bash/Glob/Grep/MCP) through Interven before execution. Blocks .env reads, denies destructive shell, redacts secrets — no agent code change.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "interven-claude-code-hook": "dist/hook.js"
9
+ },
10
+ "main": "dist/hook.js",
11
+ "files": [
12
+ "dist",
13
+ "examples",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsx src/hook.ts",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "interven",
23
+ "claude-code",
24
+ "anthropic",
25
+ "claude",
26
+ "ai-security",
27
+ "preToolUse",
28
+ "hook",
29
+ "agent-firewall"
30
+ ],
31
+ "author": "Interven Security <support@intervensecurity.com>",
32
+ "homepage": "https://docs.intervensecurity.com",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/intervensecurity/claude-code-hook"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.16.0",
42
+ "tsx": "^4.19.0",
43
+ "typescript": "^5.6.0"
44
+ }
45
+ }