@solongate/proxy 0.2.2 → 0.2.3
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/dist/index.js +153 -10
- package/dist/init.js +152 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -140,9 +140,12 @@ POLICY PRESETS
|
|
|
140
140
|
function installClaudeCodeHooks(apiKey) {
|
|
141
141
|
const hooksDir = resolve2(".claude", "hooks");
|
|
142
142
|
mkdirSync(hooksDir, { recursive: true });
|
|
143
|
-
const
|
|
144
|
-
writeFileSync(
|
|
145
|
-
console.error(` Created ${
|
|
143
|
+
const guardPath = join(hooksDir, "guard.mjs");
|
|
144
|
+
writeFileSync(guardPath, GUARD_SCRIPT);
|
|
145
|
+
console.error(` Created ${guardPath}`);
|
|
146
|
+
const auditPath = join(hooksDir, "audit.mjs");
|
|
147
|
+
writeFileSync(auditPath, AUDIT_SCRIPT);
|
|
148
|
+
console.error(` Created ${auditPath}`);
|
|
146
149
|
const settingsPath = resolve2(".claude", "settings.json");
|
|
147
150
|
let settings = {};
|
|
148
151
|
if (existsSync2(settingsPath)) {
|
|
@@ -152,6 +155,18 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
152
155
|
}
|
|
153
156
|
}
|
|
154
157
|
settings.hooks = {
|
|
158
|
+
PreToolUse: [
|
|
159
|
+
{
|
|
160
|
+
matcher: ".*",
|
|
161
|
+
hooks: [
|
|
162
|
+
{
|
|
163
|
+
type: "command",
|
|
164
|
+
command: "node .claude/hooks/guard.mjs",
|
|
165
|
+
timeout: 5
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
],
|
|
155
170
|
PostToolUse: [
|
|
156
171
|
{
|
|
157
172
|
matcher: ".*",
|
|
@@ -173,7 +188,8 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
173
188
|
console.error(` Created ${settingsPath}`);
|
|
174
189
|
console.error("");
|
|
175
190
|
console.error(" Claude Code hooks installed!");
|
|
176
|
-
console.error("
|
|
191
|
+
console.error(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
|
|
192
|
+
console.error(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
|
|
177
193
|
}
|
|
178
194
|
function ensureEnvFile() {
|
|
179
195
|
const envPath = resolve2(".env");
|
|
@@ -393,7 +409,7 @@ async function main() {
|
|
|
393
409
|
console.error(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
394
410
|
console.error("");
|
|
395
411
|
}
|
|
396
|
-
var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS,
|
|
412
|
+
var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, GUARD_SCRIPT, AUDIT_SCRIPT;
|
|
397
413
|
var init_init = __esm({
|
|
398
414
|
"src/init.ts"() {
|
|
399
415
|
"use strict";
|
|
@@ -404,10 +420,139 @@ var init_init = __esm({
|
|
|
404
420
|
".claude/mcp.json"
|
|
405
421
|
];
|
|
406
422
|
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
|
|
407
|
-
|
|
423
|
+
GUARD_SCRIPT = `#!/usr/bin/env node
|
|
424
|
+
/**
|
|
425
|
+
* SolonGate Guard Hook for Claude Code (PreToolUse)
|
|
426
|
+
* Blocks dangerous tool calls BEFORE they execute.
|
|
427
|
+
* Exit code 2 = BLOCK, exit code 0 = ALLOW.
|
|
428
|
+
* Auto-installed by: npx @solongate/proxy init
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
const API_KEY = process.env.SOLONGATE_API_KEY || '';
|
|
432
|
+
const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
|
|
433
|
+
|
|
434
|
+
// \u2500\u2500 Input Guard Patterns \u2500\u2500
|
|
435
|
+
const PATH_TRAVERSAL = [
|
|
436
|
+
/\\.\\.\\//, /\\\\.\\.\\\\\\\\/, /%2e%2e/i, /%2e\\./i, /\\.%2e/i, /%252e%252e/i,
|
|
437
|
+
];
|
|
438
|
+
const SENSITIVE_PATHS = [
|
|
439
|
+
/\\/etc\\/passwd/i, /\\/etc\\/shadow/i, /\\/proc\\//i,
|
|
440
|
+
/c:\\\\windows\\\\system32/i, /\\.env(\\.|$)/i,
|
|
441
|
+
/\\.aws\\/credentials/i, /\\.ssh\\/id_/i, /\\.kube\\/config/i,
|
|
442
|
+
/\\.git\\/config/i, /\\.npmrc/i, /\\.pypirc/i,
|
|
443
|
+
];
|
|
444
|
+
const SHELL_INJECTION = [
|
|
445
|
+
/\\$\\(/, /\\$\\{/, /\\\`/, /\\beval\\b/i, /\\bexec\\b/i, /\\bsystem\\b/i,
|
|
446
|
+
/%0a/i, /%0d/i,
|
|
447
|
+
];
|
|
448
|
+
const SSRF = [
|
|
449
|
+
/^https?:\\/\\/localhost\\b/i, /^https?:\\/\\/127\\./, /^https?:\\/\\/0\\.0\\.0\\.0/,
|
|
450
|
+
/^https?:\\/\\/10\\./, /^https?:\\/\\/172\\.(1[6-9]|2\\d|3[01])\\./,
|
|
451
|
+
/^https?:\\/\\/192\\.168\\./, /^https?:\\/\\/169\\.254\\./,
|
|
452
|
+
/metadata\\.google\\.internal/i,
|
|
453
|
+
];
|
|
454
|
+
const SQL_INJECTION = [
|
|
455
|
+
/'\\s{0,20}(OR|AND)\\s{0,20}'.{0,200}'/i,
|
|
456
|
+
/'\\s{0,10};\\s{0,10}(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|EXEC)/i,
|
|
457
|
+
/UNION\\s+(ALL\\s+)?SELECT/i, /\\bSLEEP\\s*\\(/i, /\\bWAITFOR\\s+DELAY/i,
|
|
458
|
+
];
|
|
459
|
+
|
|
460
|
+
// Dangerous command patterns for Bash tool
|
|
461
|
+
const DANGEROUS_COMMANDS = [
|
|
462
|
+
/\\brm\\s+(-[a-zA-Z]*f|-[a-zA-Z]*r|--force|--recursive)/i,
|
|
463
|
+
/\\brm\\s+-rf\\b/i,
|
|
464
|
+
/\\bmkfs\\b/i, /\\bdd\\s+if=/i,
|
|
465
|
+
/\\b(shutdown|reboot|halt|poweroff)\\b/i,
|
|
466
|
+
/\\bchmod\\s+777\\b/,
|
|
467
|
+
/\\bcurl\\b.{0,200}\\|\\s*(sh|bash)\\b/i,
|
|
468
|
+
/\\bwget\\b.{0,200}\\|\\s*(sh|bash)\\b/i,
|
|
469
|
+
/\\bnc\\s+-[a-z]*l/i, // netcat listener
|
|
470
|
+
/>(\\s*)\\/dev\\/sd/, // writing to raw disk
|
|
471
|
+
/\\bgit\\s+push\\s+.*--force\\b/i,
|
|
472
|
+
/\\bgit\\s+reset\\s+--hard\\b/i,
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
function checkValue(val) {
|
|
476
|
+
if (typeof val !== 'string' || val.length < 2) return null;
|
|
477
|
+
for (const p of PATH_TRAVERSAL) if (p.test(val)) return 'Path traversal detected';
|
|
478
|
+
for (const p of SENSITIVE_PATHS) if (p.test(val)) return 'Sensitive file access blocked';
|
|
479
|
+
for (const p of SSRF) if (p.test(val)) return 'SSRF attempt blocked';
|
|
480
|
+
for (const p of SQL_INJECTION) if (p.test(val)) return 'SQL injection detected';
|
|
481
|
+
if (val.length > 10000) return 'Input too long (max 10000 chars)';
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function checkBashCommand(cmd) {
|
|
486
|
+
if (typeof cmd !== 'string') return null;
|
|
487
|
+
for (const p of DANGEROUS_COMMANDS) if (p.test(cmd)) return 'Dangerous command blocked: ' + cmd.slice(0, 80);
|
|
488
|
+
for (const p of SHELL_INJECTION) if (p.test(cmd)) return null; // shell injection is normal for Bash
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let input = '';
|
|
493
|
+
process.stdin.on('data', c => input += c);
|
|
494
|
+
process.stdin.on('end', async () => {
|
|
495
|
+
try {
|
|
496
|
+
const data = JSON.parse(input);
|
|
497
|
+
const tool = data.tool_name || '';
|
|
498
|
+
const args = data.tool_input || {};
|
|
499
|
+
const start = Date.now();
|
|
500
|
+
|
|
501
|
+
let threat = null;
|
|
502
|
+
|
|
503
|
+
// Check Bash commands for dangerous patterns
|
|
504
|
+
if (tool === 'Bash' && args.command) {
|
|
505
|
+
threat = checkBashCommand(args.command);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Check all string arguments for injection patterns
|
|
509
|
+
if (!threat) {
|
|
510
|
+
for (const [key, val] of Object.entries(args)) {
|
|
511
|
+
if (tool === 'Bash' && key === 'command') continue; // already checked
|
|
512
|
+
threat = checkValue(val);
|
|
513
|
+
if (threat) { threat = threat + ' (in ' + key + ')'; break; }
|
|
514
|
+
// Check nested strings
|
|
515
|
+
if (typeof val === 'object' && val) {
|
|
516
|
+
for (const v of Object.values(val)) {
|
|
517
|
+
threat = checkValue(v);
|
|
518
|
+
if (threat) { threat = threat + ' (in ' + key + ')'; break; }
|
|
519
|
+
}
|
|
520
|
+
if (threat) break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const ms = Date.now() - start;
|
|
526
|
+
|
|
527
|
+
if (threat) {
|
|
528
|
+
// Send DENY audit log
|
|
529
|
+
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
530
|
+
fetch(API_URL + '/api/v1/audit-logs', {
|
|
531
|
+
method: 'POST',
|
|
532
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
533
|
+
body: JSON.stringify({
|
|
534
|
+
tool, arguments: Object.fromEntries(Object.entries(args).map(([k,v]) =>
|
|
535
|
+
[k, typeof v === 'string' && v.length > 200 ? v.slice(0,200)+'...' : v])),
|
|
536
|
+
decision: 'DENY', reason: threat, source: 'claude-code-guard',
|
|
537
|
+
evaluationTimeMs: ms,
|
|
538
|
+
}),
|
|
539
|
+
signal: AbortSignal.timeout(5000),
|
|
540
|
+
}).catch(() => {});
|
|
541
|
+
}
|
|
542
|
+
// Exit 2 = BLOCK. Message printed to stdout is shown to user.
|
|
543
|
+
process.stdout.write('SolonGate BLOCKED: ' + threat);
|
|
544
|
+
process.exit(2);
|
|
545
|
+
}
|
|
546
|
+
} catch {
|
|
547
|
+
// On error, allow (fail-open)
|
|
548
|
+
}
|
|
549
|
+
process.exit(0);
|
|
550
|
+
});
|
|
551
|
+
`;
|
|
552
|
+
AUDIT_SCRIPT = `#!/usr/bin/env node
|
|
408
553
|
/**
|
|
409
|
-
* SolonGate Audit Hook for Claude Code
|
|
410
|
-
*
|
|
554
|
+
* SolonGate Audit Hook for Claude Code (PostToolUse)
|
|
555
|
+
* Logs ALL tool calls to SolonGate Cloud after execution.
|
|
411
556
|
* Auto-installed by: npx @solongate/proxy init
|
|
412
557
|
*/
|
|
413
558
|
|
|
@@ -424,7 +569,6 @@ process.stdin.on('end', async () => {
|
|
|
424
569
|
const toolName = data.tool_name || 'unknown';
|
|
425
570
|
const toolInput = data.tool_input || {};
|
|
426
571
|
|
|
427
|
-
// Skip logging the audit hook itself to avoid loops
|
|
428
572
|
if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
|
|
429
573
|
process.exit(0);
|
|
430
574
|
}
|
|
@@ -433,7 +577,6 @@ process.stdin.on('end', async () => {
|
|
|
433
577
|
data.tool_response?.exitCode > 0 ||
|
|
434
578
|
data.tool_response?.isError;
|
|
435
579
|
|
|
436
|
-
// Truncate large argument values for security
|
|
437
580
|
const argsSummary = {};
|
|
438
581
|
for (const [k, v] of Object.entries(toolInput)) {
|
|
439
582
|
argsSummary[k] = typeof v === 'string' && v.length > 200
|
package/dist/init.js
CHANGED
|
@@ -139,10 +139,139 @@ POLICY PRESETS
|
|
|
139
139
|
`;
|
|
140
140
|
console.error(help);
|
|
141
141
|
}
|
|
142
|
-
var
|
|
142
|
+
var GUARD_SCRIPT = `#!/usr/bin/env node
|
|
143
143
|
/**
|
|
144
|
-
* SolonGate
|
|
145
|
-
*
|
|
144
|
+
* SolonGate Guard Hook for Claude Code (PreToolUse)
|
|
145
|
+
* Blocks dangerous tool calls BEFORE they execute.
|
|
146
|
+
* Exit code 2 = BLOCK, exit code 0 = ALLOW.
|
|
147
|
+
* Auto-installed by: npx @solongate/proxy init
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
const API_KEY = process.env.SOLONGATE_API_KEY || '';
|
|
151
|
+
const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
|
|
152
|
+
|
|
153
|
+
// \u2500\u2500 Input Guard Patterns \u2500\u2500
|
|
154
|
+
const PATH_TRAVERSAL = [
|
|
155
|
+
/\\.\\.\\//, /\\\\.\\.\\\\\\\\/, /%2e%2e/i, /%2e\\./i, /\\.%2e/i, /%252e%252e/i,
|
|
156
|
+
];
|
|
157
|
+
const SENSITIVE_PATHS = [
|
|
158
|
+
/\\/etc\\/passwd/i, /\\/etc\\/shadow/i, /\\/proc\\//i,
|
|
159
|
+
/c:\\\\windows\\\\system32/i, /\\.env(\\.|$)/i,
|
|
160
|
+
/\\.aws\\/credentials/i, /\\.ssh\\/id_/i, /\\.kube\\/config/i,
|
|
161
|
+
/\\.git\\/config/i, /\\.npmrc/i, /\\.pypirc/i,
|
|
162
|
+
];
|
|
163
|
+
const SHELL_INJECTION = [
|
|
164
|
+
/\\$\\(/, /\\$\\{/, /\\\`/, /\\beval\\b/i, /\\bexec\\b/i, /\\bsystem\\b/i,
|
|
165
|
+
/%0a/i, /%0d/i,
|
|
166
|
+
];
|
|
167
|
+
const SSRF = [
|
|
168
|
+
/^https?:\\/\\/localhost\\b/i, /^https?:\\/\\/127\\./, /^https?:\\/\\/0\\.0\\.0\\.0/,
|
|
169
|
+
/^https?:\\/\\/10\\./, /^https?:\\/\\/172\\.(1[6-9]|2\\d|3[01])\\./,
|
|
170
|
+
/^https?:\\/\\/192\\.168\\./, /^https?:\\/\\/169\\.254\\./,
|
|
171
|
+
/metadata\\.google\\.internal/i,
|
|
172
|
+
];
|
|
173
|
+
const SQL_INJECTION = [
|
|
174
|
+
/'\\s{0,20}(OR|AND)\\s{0,20}'.{0,200}'/i,
|
|
175
|
+
/'\\s{0,10};\\s{0,10}(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|EXEC)/i,
|
|
176
|
+
/UNION\\s+(ALL\\s+)?SELECT/i, /\\bSLEEP\\s*\\(/i, /\\bWAITFOR\\s+DELAY/i,
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
// Dangerous command patterns for Bash tool
|
|
180
|
+
const DANGEROUS_COMMANDS = [
|
|
181
|
+
/\\brm\\s+(-[a-zA-Z]*f|-[a-zA-Z]*r|--force|--recursive)/i,
|
|
182
|
+
/\\brm\\s+-rf\\b/i,
|
|
183
|
+
/\\bmkfs\\b/i, /\\bdd\\s+if=/i,
|
|
184
|
+
/\\b(shutdown|reboot|halt|poweroff)\\b/i,
|
|
185
|
+
/\\bchmod\\s+777\\b/,
|
|
186
|
+
/\\bcurl\\b.{0,200}\\|\\s*(sh|bash)\\b/i,
|
|
187
|
+
/\\bwget\\b.{0,200}\\|\\s*(sh|bash)\\b/i,
|
|
188
|
+
/\\bnc\\s+-[a-z]*l/i, // netcat listener
|
|
189
|
+
/>(\\s*)\\/dev\\/sd/, // writing to raw disk
|
|
190
|
+
/\\bgit\\s+push\\s+.*--force\\b/i,
|
|
191
|
+
/\\bgit\\s+reset\\s+--hard\\b/i,
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
function checkValue(val) {
|
|
195
|
+
if (typeof val !== 'string' || val.length < 2) return null;
|
|
196
|
+
for (const p of PATH_TRAVERSAL) if (p.test(val)) return 'Path traversal detected';
|
|
197
|
+
for (const p of SENSITIVE_PATHS) if (p.test(val)) return 'Sensitive file access blocked';
|
|
198
|
+
for (const p of SSRF) if (p.test(val)) return 'SSRF attempt blocked';
|
|
199
|
+
for (const p of SQL_INJECTION) if (p.test(val)) return 'SQL injection detected';
|
|
200
|
+
if (val.length > 10000) return 'Input too long (max 10000 chars)';
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function checkBashCommand(cmd) {
|
|
205
|
+
if (typeof cmd !== 'string') return null;
|
|
206
|
+
for (const p of DANGEROUS_COMMANDS) if (p.test(cmd)) return 'Dangerous command blocked: ' + cmd.slice(0, 80);
|
|
207
|
+
for (const p of SHELL_INJECTION) if (p.test(cmd)) return null; // shell injection is normal for Bash
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let input = '';
|
|
212
|
+
process.stdin.on('data', c => input += c);
|
|
213
|
+
process.stdin.on('end', async () => {
|
|
214
|
+
try {
|
|
215
|
+
const data = JSON.parse(input);
|
|
216
|
+
const tool = data.tool_name || '';
|
|
217
|
+
const args = data.tool_input || {};
|
|
218
|
+
const start = Date.now();
|
|
219
|
+
|
|
220
|
+
let threat = null;
|
|
221
|
+
|
|
222
|
+
// Check Bash commands for dangerous patterns
|
|
223
|
+
if (tool === 'Bash' && args.command) {
|
|
224
|
+
threat = checkBashCommand(args.command);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check all string arguments for injection patterns
|
|
228
|
+
if (!threat) {
|
|
229
|
+
for (const [key, val] of Object.entries(args)) {
|
|
230
|
+
if (tool === 'Bash' && key === 'command') continue; // already checked
|
|
231
|
+
threat = checkValue(val);
|
|
232
|
+
if (threat) { threat = threat + ' (in ' + key + ')'; break; }
|
|
233
|
+
// Check nested strings
|
|
234
|
+
if (typeof val === 'object' && val) {
|
|
235
|
+
for (const v of Object.values(val)) {
|
|
236
|
+
threat = checkValue(v);
|
|
237
|
+
if (threat) { threat = threat + ' (in ' + key + ')'; break; }
|
|
238
|
+
}
|
|
239
|
+
if (threat) break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const ms = Date.now() - start;
|
|
245
|
+
|
|
246
|
+
if (threat) {
|
|
247
|
+
// Send DENY audit log
|
|
248
|
+
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
249
|
+
fetch(API_URL + '/api/v1/audit-logs', {
|
|
250
|
+
method: 'POST',
|
|
251
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
252
|
+
body: JSON.stringify({
|
|
253
|
+
tool, arguments: Object.fromEntries(Object.entries(args).map(([k,v]) =>
|
|
254
|
+
[k, typeof v === 'string' && v.length > 200 ? v.slice(0,200)+'...' : v])),
|
|
255
|
+
decision: 'DENY', reason: threat, source: 'claude-code-guard',
|
|
256
|
+
evaluationTimeMs: ms,
|
|
257
|
+
}),
|
|
258
|
+
signal: AbortSignal.timeout(5000),
|
|
259
|
+
}).catch(() => {});
|
|
260
|
+
}
|
|
261
|
+
// Exit 2 = BLOCK. Message printed to stdout is shown to user.
|
|
262
|
+
process.stdout.write('SolonGate BLOCKED: ' + threat);
|
|
263
|
+
process.exit(2);
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
// On error, allow (fail-open)
|
|
267
|
+
}
|
|
268
|
+
process.exit(0);
|
|
269
|
+
});
|
|
270
|
+
`;
|
|
271
|
+
var AUDIT_SCRIPT = `#!/usr/bin/env node
|
|
272
|
+
/**
|
|
273
|
+
* SolonGate Audit Hook for Claude Code (PostToolUse)
|
|
274
|
+
* Logs ALL tool calls to SolonGate Cloud after execution.
|
|
146
275
|
* Auto-installed by: npx @solongate/proxy init
|
|
147
276
|
*/
|
|
148
277
|
|
|
@@ -159,7 +288,6 @@ process.stdin.on('end', async () => {
|
|
|
159
288
|
const toolName = data.tool_name || 'unknown';
|
|
160
289
|
const toolInput = data.tool_input || {};
|
|
161
290
|
|
|
162
|
-
// Skip logging the audit hook itself to avoid loops
|
|
163
291
|
if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
|
|
164
292
|
process.exit(0);
|
|
165
293
|
}
|
|
@@ -168,7 +296,6 @@ process.stdin.on('end', async () => {
|
|
|
168
296
|
data.tool_response?.exitCode > 0 ||
|
|
169
297
|
data.tool_response?.isError;
|
|
170
298
|
|
|
171
|
-
// Truncate large argument values for security
|
|
172
299
|
const argsSummary = {};
|
|
173
300
|
for (const [k, v] of Object.entries(toolInput)) {
|
|
174
301
|
argsSummary[k] = typeof v === 'string' && v.length > 200
|
|
@@ -201,9 +328,12 @@ process.stdin.on('end', async () => {
|
|
|
201
328
|
function installClaudeCodeHooks(apiKey) {
|
|
202
329
|
const hooksDir = resolve(".claude", "hooks");
|
|
203
330
|
mkdirSync(hooksDir, { recursive: true });
|
|
204
|
-
const
|
|
205
|
-
writeFileSync(
|
|
206
|
-
console.error(` Created ${
|
|
331
|
+
const guardPath = join(hooksDir, "guard.mjs");
|
|
332
|
+
writeFileSync(guardPath, GUARD_SCRIPT);
|
|
333
|
+
console.error(` Created ${guardPath}`);
|
|
334
|
+
const auditPath = join(hooksDir, "audit.mjs");
|
|
335
|
+
writeFileSync(auditPath, AUDIT_SCRIPT);
|
|
336
|
+
console.error(` Created ${auditPath}`);
|
|
207
337
|
const settingsPath = resolve(".claude", "settings.json");
|
|
208
338
|
let settings = {};
|
|
209
339
|
if (existsSync(settingsPath)) {
|
|
@@ -213,6 +343,18 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
213
343
|
}
|
|
214
344
|
}
|
|
215
345
|
settings.hooks = {
|
|
346
|
+
PreToolUse: [
|
|
347
|
+
{
|
|
348
|
+
matcher: ".*",
|
|
349
|
+
hooks: [
|
|
350
|
+
{
|
|
351
|
+
type: "command",
|
|
352
|
+
command: "node .claude/hooks/guard.mjs",
|
|
353
|
+
timeout: 5
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
],
|
|
216
358
|
PostToolUse: [
|
|
217
359
|
{
|
|
218
360
|
matcher: ".*",
|
|
@@ -234,7 +376,8 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
234
376
|
console.error(` Created ${settingsPath}`);
|
|
235
377
|
console.error("");
|
|
236
378
|
console.error(" Claude Code hooks installed!");
|
|
237
|
-
console.error("
|
|
379
|
+
console.error(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
|
|
380
|
+
console.error(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
|
|
238
381
|
}
|
|
239
382
|
function ensureEnvFile() {
|
|
240
383
|
const envPath = resolve(".env");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "MCP security proxy \u00e2\u20ac\u201d protect any MCP server with policies, input validation, rate limiting, and audit logging. Zero code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|