@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 +104 -0
- package/dist/hook.d.ts +61 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +232 -0
- package/dist/hook.js.map +1 -0
- package/examples/settings.json +17 -0
- package/package.json +45 -0
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
|
package/dist/hook.js.map
ADDED
|
@@ -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
|
+
}
|