@solongate/proxy 0.2.2 → 0.2.4
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 +157 -10
- package/dist/init.js +156 -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,143 @@ 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 checkPath(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
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function checkValue(val) {
|
|
483
|
+
if (typeof val !== 'string' || val.length < 2) return null;
|
|
484
|
+
for (const p of SSRF) if (p.test(val)) return 'SSRF attempt blocked';
|
|
485
|
+
for (const p of SQL_INJECTION) if (p.test(val)) return 'SQL injection detected';
|
|
486
|
+
if (val.length > 10000) return 'Input too long (max 10000 chars)';
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Arguments that contain file paths
|
|
491
|
+
const PATH_ARGS = ['file_path', 'path', 'pattern', 'directory', 'url', 'uri', 'notebook_path'];
|
|
492
|
+
|
|
493
|
+
function checkBashCommand(cmd) {
|
|
494
|
+
if (typeof cmd !== 'string') return null;
|
|
495
|
+
for (const p of DANGEROUS_COMMANDS) if (p.test(cmd)) return 'Dangerous command blocked: ' + cmd.slice(0, 80);
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
let input = '';
|
|
500
|
+
process.stdin.on('data', c => input += c);
|
|
501
|
+
process.stdin.on('end', async () => {
|
|
502
|
+
try {
|
|
503
|
+
const data = JSON.parse(input);
|
|
504
|
+
const tool = data.tool_name || '';
|
|
505
|
+
const args = data.tool_input || {};
|
|
506
|
+
const start = Date.now();
|
|
507
|
+
|
|
508
|
+
let threat = null;
|
|
509
|
+
|
|
510
|
+
// Check Bash commands for dangerous patterns
|
|
511
|
+
if (tool === 'Bash' && args.command) {
|
|
512
|
+
threat = checkBashCommand(args.command);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Check arguments based on type
|
|
516
|
+
if (!threat) {
|
|
517
|
+
for (const [key, val] of Object.entries(args)) {
|
|
518
|
+
if (tool === 'Bash' && key === 'command') continue;
|
|
519
|
+
if (key === 'content' || key === 'new_source' || key === 'new_string' || key === 'old_string' || key === 'description') continue;
|
|
520
|
+
if (PATH_ARGS.includes(key)) {
|
|
521
|
+
threat = checkPath(val);
|
|
522
|
+
} else {
|
|
523
|
+
threat = checkValue(val);
|
|
524
|
+
}
|
|
525
|
+
if (threat) { threat = threat + ' (in ' + key + ')'; break; }
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const ms = Date.now() - start;
|
|
530
|
+
|
|
531
|
+
if (threat) {
|
|
532
|
+
// Send DENY audit log
|
|
533
|
+
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
534
|
+
fetch(API_URL + '/api/v1/audit-logs', {
|
|
535
|
+
method: 'POST',
|
|
536
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
537
|
+
body: JSON.stringify({
|
|
538
|
+
tool, arguments: Object.fromEntries(Object.entries(args).map(([k,v]) =>
|
|
539
|
+
[k, typeof v === 'string' && v.length > 200 ? v.slice(0,200)+'...' : v])),
|
|
540
|
+
decision: 'DENY', reason: threat, source: 'claude-code-guard',
|
|
541
|
+
evaluationTimeMs: ms,
|
|
542
|
+
}),
|
|
543
|
+
signal: AbortSignal.timeout(5000),
|
|
544
|
+
}).catch(() => {});
|
|
545
|
+
}
|
|
546
|
+
// Exit 2 = BLOCK. Message printed to stdout is shown to user.
|
|
547
|
+
process.stdout.write('SolonGate BLOCKED: ' + threat);
|
|
548
|
+
process.exit(2);
|
|
549
|
+
}
|
|
550
|
+
} catch {
|
|
551
|
+
// On error, allow (fail-open)
|
|
552
|
+
}
|
|
553
|
+
process.exit(0);
|
|
554
|
+
});
|
|
555
|
+
`;
|
|
556
|
+
AUDIT_SCRIPT = `#!/usr/bin/env node
|
|
408
557
|
/**
|
|
409
|
-
* SolonGate Audit Hook for Claude Code
|
|
410
|
-
*
|
|
558
|
+
* SolonGate Audit Hook for Claude Code (PostToolUse)
|
|
559
|
+
* Logs ALL tool calls to SolonGate Cloud after execution.
|
|
411
560
|
* Auto-installed by: npx @solongate/proxy init
|
|
412
561
|
*/
|
|
413
562
|
|
|
@@ -424,7 +573,6 @@ process.stdin.on('end', async () => {
|
|
|
424
573
|
const toolName = data.tool_name || 'unknown';
|
|
425
574
|
const toolInput = data.tool_input || {};
|
|
426
575
|
|
|
427
|
-
// Skip logging the audit hook itself to avoid loops
|
|
428
576
|
if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
|
|
429
577
|
process.exit(0);
|
|
430
578
|
}
|
|
@@ -433,7 +581,6 @@ process.stdin.on('end', async () => {
|
|
|
433
581
|
data.tool_response?.exitCode > 0 ||
|
|
434
582
|
data.tool_response?.isError;
|
|
435
583
|
|
|
436
|
-
// Truncate large argument values for security
|
|
437
584
|
const argsSummary = {};
|
|
438
585
|
for (const [k, v] of Object.entries(toolInput)) {
|
|
439
586
|
argsSummary[k] = typeof v === 'string' && v.length > 200
|
package/dist/init.js
CHANGED
|
@@ -139,10 +139,143 @@ 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 checkPath(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
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function checkValue(val) {
|
|
202
|
+
if (typeof val !== 'string' || val.length < 2) return null;
|
|
203
|
+
for (const p of SSRF) if (p.test(val)) return 'SSRF attempt blocked';
|
|
204
|
+
for (const p of SQL_INJECTION) if (p.test(val)) return 'SQL injection detected';
|
|
205
|
+
if (val.length > 10000) return 'Input too long (max 10000 chars)';
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Arguments that contain file paths
|
|
210
|
+
const PATH_ARGS = ['file_path', 'path', 'pattern', 'directory', 'url', 'uri', 'notebook_path'];
|
|
211
|
+
|
|
212
|
+
function checkBashCommand(cmd) {
|
|
213
|
+
if (typeof cmd !== 'string') return null;
|
|
214
|
+
for (const p of DANGEROUS_COMMANDS) if (p.test(cmd)) return 'Dangerous command blocked: ' + cmd.slice(0, 80);
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let input = '';
|
|
219
|
+
process.stdin.on('data', c => input += c);
|
|
220
|
+
process.stdin.on('end', async () => {
|
|
221
|
+
try {
|
|
222
|
+
const data = JSON.parse(input);
|
|
223
|
+
const tool = data.tool_name || '';
|
|
224
|
+
const args = data.tool_input || {};
|
|
225
|
+
const start = Date.now();
|
|
226
|
+
|
|
227
|
+
let threat = null;
|
|
228
|
+
|
|
229
|
+
// Check Bash commands for dangerous patterns
|
|
230
|
+
if (tool === 'Bash' && args.command) {
|
|
231
|
+
threat = checkBashCommand(args.command);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check arguments based on type
|
|
235
|
+
if (!threat) {
|
|
236
|
+
for (const [key, val] of Object.entries(args)) {
|
|
237
|
+
if (tool === 'Bash' && key === 'command') continue;
|
|
238
|
+
if (key === 'content' || key === 'new_source' || key === 'new_string' || key === 'old_string' || key === 'description') continue;
|
|
239
|
+
if (PATH_ARGS.includes(key)) {
|
|
240
|
+
threat = checkPath(val);
|
|
241
|
+
} else {
|
|
242
|
+
threat = checkValue(val);
|
|
243
|
+
}
|
|
244
|
+
if (threat) { threat = threat + ' (in ' + key + ')'; break; }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const ms = Date.now() - start;
|
|
249
|
+
|
|
250
|
+
if (threat) {
|
|
251
|
+
// Send DENY audit log
|
|
252
|
+
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
253
|
+
fetch(API_URL + '/api/v1/audit-logs', {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
tool, arguments: Object.fromEntries(Object.entries(args).map(([k,v]) =>
|
|
258
|
+
[k, typeof v === 'string' && v.length > 200 ? v.slice(0,200)+'...' : v])),
|
|
259
|
+
decision: 'DENY', reason: threat, source: 'claude-code-guard',
|
|
260
|
+
evaluationTimeMs: ms,
|
|
261
|
+
}),
|
|
262
|
+
signal: AbortSignal.timeout(5000),
|
|
263
|
+
}).catch(() => {});
|
|
264
|
+
}
|
|
265
|
+
// Exit 2 = BLOCK. Message printed to stdout is shown to user.
|
|
266
|
+
process.stdout.write('SolonGate BLOCKED: ' + threat);
|
|
267
|
+
process.exit(2);
|
|
268
|
+
}
|
|
269
|
+
} catch {
|
|
270
|
+
// On error, allow (fail-open)
|
|
271
|
+
}
|
|
272
|
+
process.exit(0);
|
|
273
|
+
});
|
|
274
|
+
`;
|
|
275
|
+
var AUDIT_SCRIPT = `#!/usr/bin/env node
|
|
276
|
+
/**
|
|
277
|
+
* SolonGate Audit Hook for Claude Code (PostToolUse)
|
|
278
|
+
* Logs ALL tool calls to SolonGate Cloud after execution.
|
|
146
279
|
* Auto-installed by: npx @solongate/proxy init
|
|
147
280
|
*/
|
|
148
281
|
|
|
@@ -159,7 +292,6 @@ process.stdin.on('end', async () => {
|
|
|
159
292
|
const toolName = data.tool_name || 'unknown';
|
|
160
293
|
const toolInput = data.tool_input || {};
|
|
161
294
|
|
|
162
|
-
// Skip logging the audit hook itself to avoid loops
|
|
163
295
|
if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
|
|
164
296
|
process.exit(0);
|
|
165
297
|
}
|
|
@@ -168,7 +300,6 @@ process.stdin.on('end', async () => {
|
|
|
168
300
|
data.tool_response?.exitCode > 0 ||
|
|
169
301
|
data.tool_response?.isError;
|
|
170
302
|
|
|
171
|
-
// Truncate large argument values for security
|
|
172
303
|
const argsSummary = {};
|
|
173
304
|
for (const [k, v] of Object.entries(toolInput)) {
|
|
174
305
|
argsSummary[k] = typeof v === 'string' && v.length > 200
|
|
@@ -201,9 +332,12 @@ process.stdin.on('end', async () => {
|
|
|
201
332
|
function installClaudeCodeHooks(apiKey) {
|
|
202
333
|
const hooksDir = resolve(".claude", "hooks");
|
|
203
334
|
mkdirSync(hooksDir, { recursive: true });
|
|
204
|
-
const
|
|
205
|
-
writeFileSync(
|
|
206
|
-
console.error(` Created ${
|
|
335
|
+
const guardPath = join(hooksDir, "guard.mjs");
|
|
336
|
+
writeFileSync(guardPath, GUARD_SCRIPT);
|
|
337
|
+
console.error(` Created ${guardPath}`);
|
|
338
|
+
const auditPath = join(hooksDir, "audit.mjs");
|
|
339
|
+
writeFileSync(auditPath, AUDIT_SCRIPT);
|
|
340
|
+
console.error(` Created ${auditPath}`);
|
|
207
341
|
const settingsPath = resolve(".claude", "settings.json");
|
|
208
342
|
let settings = {};
|
|
209
343
|
if (existsSync(settingsPath)) {
|
|
@@ -213,6 +347,18 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
213
347
|
}
|
|
214
348
|
}
|
|
215
349
|
settings.hooks = {
|
|
350
|
+
PreToolUse: [
|
|
351
|
+
{
|
|
352
|
+
matcher: ".*",
|
|
353
|
+
hooks: [
|
|
354
|
+
{
|
|
355
|
+
type: "command",
|
|
356
|
+
command: "node .claude/hooks/guard.mjs",
|
|
357
|
+
timeout: 5
|
|
358
|
+
}
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
],
|
|
216
362
|
PostToolUse: [
|
|
217
363
|
{
|
|
218
364
|
matcher: ".*",
|
|
@@ -234,7 +380,8 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
234
380
|
console.error(` Created ${settingsPath}`);
|
|
235
381
|
console.error("");
|
|
236
382
|
console.error(" Claude Code hooks installed!");
|
|
237
|
-
console.error("
|
|
383
|
+
console.error(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
|
|
384
|
+
console.error(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
|
|
238
385
|
}
|
|
239
386
|
function ensureEnvFile() {
|
|
240
387
|
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.4",
|
|
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": {
|