@solongate/proxy 0.28.4 → 0.28.6

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 CHANGED
@@ -678,33 +678,16 @@ function installHooks(selectedTools = []) {
678
678
  const auditPath = join(hooksDir, "audit.mjs");
679
679
  writeFileSync2(auditPath, readHookScript("audit.mjs"));
680
680
  console.log(` Created ${auditPath}`);
681
- const hookSettings = {
682
- hooks: {
683
- PreToolUse: [
684
- {
685
- matcher: "",
686
- hooks: [
687
- { type: "command", command: "node .solongate/hooks/guard.mjs" }
688
- ]
689
- }
690
- ],
691
- PostToolUse: [
692
- {
693
- matcher: "",
694
- hooks: [
695
- { type: "command", command: "node .solongate/hooks/audit.mjs" }
696
- ]
697
- }
698
- ]
699
- }
700
- };
681
+ const stopPath = join(hooksDir, "stop.mjs");
682
+ writeFileSync2(stopPath, readHookScript("stop.mjs"));
683
+ console.log(` Created ${stopPath}`);
701
684
  const allClients = [
702
- { name: "Claude Code", dir: ".claude", key: "claude" },
703
- { name: "Cursor", dir: ".cursor", key: "cursor" },
704
- { name: "Gemini CLI", dir: ".gemini", key: "gemini" },
705
- { name: "Antigravity", dir: ".antigravity", key: "antigravity" },
706
- { name: "OpenClaw", dir: ".openclaw", key: "openclaw" },
707
- { name: "Perplexity", dir: ".perplexity", key: "perplexity" }
685
+ { name: "Claude Code", dir: ".claude", key: "claude", agentId: "claude-code", agentName: "Claude Code" },
686
+ { name: "Cursor", dir: ".cursor", key: "cursor", agentId: "cursor", agentName: "Cursor" },
687
+ { name: "Gemini CLI", dir: ".gemini", key: "gemini", agentId: "gemini-cli", agentName: "Gemini CLI" },
688
+ { name: "Antigravity", dir: ".antigravity", key: "antigravity", agentId: "antigravity", agentName: "Antigravity" },
689
+ { name: "OpenClaw", dir: ".openclaw", key: "openclaw", agentId: "openclaw", agentName: "OpenClaw" },
690
+ { name: "Perplexity", dir: ".perplexity", key: "perplexity", agentId: "perplexity", agentName: "Perplexity" }
708
691
  ];
709
692
  const clients = selectedTools.length > 0 ? allClients.filter((c3) => selectedTools.includes(c3.key)) : allClients;
710
693
  const activatedNames = [];
@@ -712,12 +695,26 @@ function installHooks(selectedTools = []) {
712
695
  const clientDir = resolve3(client.dir);
713
696
  mkdirSync2(clientDir, { recursive: true });
714
697
  const settingsPath = join(clientDir, "settings.json");
698
+ const guardCmd = `node .solongate/hooks/guard.mjs ${client.agentId} "${client.agentName}"`;
699
+ const auditCmd = `node .solongate/hooks/audit.mjs ${client.agentId} "${client.agentName}"`;
700
+ const stopCmd = `node .solongate/hooks/stop.mjs ${client.agentId} "${client.agentName}"`;
701
+ const hookSettings = {
702
+ PreToolUse: [
703
+ { matcher: "", hooks: [{ type: "command", command: guardCmd }] }
704
+ ],
705
+ PostToolUse: [
706
+ { matcher: "", hooks: [{ type: "command", command: auditCmd }] }
707
+ ],
708
+ Stop: [
709
+ { matcher: "", hooks: [{ type: "command", command: stopCmd }] }
710
+ ]
711
+ };
715
712
  let existing = {};
716
713
  try {
717
714
  existing = JSON.parse(readFileSync4(settingsPath, "utf-8"));
718
715
  } catch {
719
716
  }
720
- const merged = { ...existing, hooks: hookSettings.hooks };
717
+ const merged = { ...existing, hooks: hookSettings };
721
718
  writeFileSync2(settingsPath, JSON.stringify(merged, null, 2) + "\n");
722
719
  console.log(` Created ${settingsPath}`);
723
720
  activatedNames.push(client.name);
@@ -726,6 +723,7 @@ function installHooks(selectedTools = []) {
726
723
  console.log(" Hooks installed:");
727
724
  console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
728
725
  console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
726
+ console.log(" stop.mjs \u2192 tracks text-only responses (no tool calls)");
729
727
  console.log(` Activated for: ${activatedNames.join(", ")}`);
730
728
  }
731
729
  function ensureEnvFile() {
package/dist/init.js CHANGED
@@ -261,33 +261,16 @@ function installHooks(selectedTools = []) {
261
261
  const auditPath = join(hooksDir, "audit.mjs");
262
262
  writeFileSync(auditPath, readHookScript("audit.mjs"));
263
263
  console.log(` Created ${auditPath}`);
264
- const hookSettings = {
265
- hooks: {
266
- PreToolUse: [
267
- {
268
- matcher: "",
269
- hooks: [
270
- { type: "command", command: "node .solongate/hooks/guard.mjs" }
271
- ]
272
- }
273
- ],
274
- PostToolUse: [
275
- {
276
- matcher: "",
277
- hooks: [
278
- { type: "command", command: "node .solongate/hooks/audit.mjs" }
279
- ]
280
- }
281
- ]
282
- }
283
- };
264
+ const stopPath = join(hooksDir, "stop.mjs");
265
+ writeFileSync(stopPath, readHookScript("stop.mjs"));
266
+ console.log(` Created ${stopPath}`);
284
267
  const allClients = [
285
- { name: "Claude Code", dir: ".claude", key: "claude" },
286
- { name: "Cursor", dir: ".cursor", key: "cursor" },
287
- { name: "Gemini CLI", dir: ".gemini", key: "gemini" },
288
- { name: "Antigravity", dir: ".antigravity", key: "antigravity" },
289
- { name: "OpenClaw", dir: ".openclaw", key: "openclaw" },
290
- { name: "Perplexity", dir: ".perplexity", key: "perplexity" }
268
+ { name: "Claude Code", dir: ".claude", key: "claude", agentId: "claude-code", agentName: "Claude Code" },
269
+ { name: "Cursor", dir: ".cursor", key: "cursor", agentId: "cursor", agentName: "Cursor" },
270
+ { name: "Gemini CLI", dir: ".gemini", key: "gemini", agentId: "gemini-cli", agentName: "Gemini CLI" },
271
+ { name: "Antigravity", dir: ".antigravity", key: "antigravity", agentId: "antigravity", agentName: "Antigravity" },
272
+ { name: "OpenClaw", dir: ".openclaw", key: "openclaw", agentId: "openclaw", agentName: "OpenClaw" },
273
+ { name: "Perplexity", dir: ".perplexity", key: "perplexity", agentId: "perplexity", agentName: "Perplexity" }
291
274
  ];
292
275
  const clients = selectedTools.length > 0 ? allClients.filter((c2) => selectedTools.includes(c2.key)) : allClients;
293
276
  const activatedNames = [];
@@ -295,12 +278,26 @@ function installHooks(selectedTools = []) {
295
278
  const clientDir = resolve(client.dir);
296
279
  mkdirSync(clientDir, { recursive: true });
297
280
  const settingsPath = join(clientDir, "settings.json");
281
+ const guardCmd = `node .solongate/hooks/guard.mjs ${client.agentId} "${client.agentName}"`;
282
+ const auditCmd = `node .solongate/hooks/audit.mjs ${client.agentId} "${client.agentName}"`;
283
+ const stopCmd = `node .solongate/hooks/stop.mjs ${client.agentId} "${client.agentName}"`;
284
+ const hookSettings = {
285
+ PreToolUse: [
286
+ { matcher: "", hooks: [{ type: "command", command: guardCmd }] }
287
+ ],
288
+ PostToolUse: [
289
+ { matcher: "", hooks: [{ type: "command", command: auditCmd }] }
290
+ ],
291
+ Stop: [
292
+ { matcher: "", hooks: [{ type: "command", command: stopCmd }] }
293
+ ]
294
+ };
298
295
  let existing = {};
299
296
  try {
300
297
  existing = JSON.parse(readFileSync(settingsPath, "utf-8"));
301
298
  } catch {
302
299
  }
303
- const merged = { ...existing, hooks: hookSettings.hooks };
300
+ const merged = { ...existing, hooks: hookSettings };
304
301
  writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
305
302
  console.log(` Created ${settingsPath}`);
306
303
  activatedNames.push(client.name);
@@ -309,6 +306,7 @@ function installHooks(selectedTools = []) {
309
306
  console.log(" Hooks installed:");
310
307
  console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
311
308
  console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
309
+ console.log(" stop.mjs \u2192 tracks text-only responses (no tool calls)");
312
310
  console.log(` Activated for: ${activatedNames.join(", ")}`);
313
311
  }
314
312
  function ensureEnvFile() {
package/hooks/audit.mjs CHANGED
@@ -4,8 +4,8 @@
4
4
  * Logs tool execution results to SolonGate Cloud.
5
5
  * Auto-installed by: npx @solongate/proxy init
6
6
  */
7
- import { readFileSync, existsSync } from 'node:fs';
8
- import { resolve } from 'node:path';
7
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
8
+ import { resolve, join } from 'node:path';
9
9
 
10
10
  function loadEnvKey(dir) {
11
11
  try {
@@ -25,6 +25,10 @@ const dotenv = loadEnvKey(process.cwd());
25
25
  const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
26
26
  const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
27
27
 
28
+ // Agent identity from CLI args: node audit.mjs <agent_id> <agent_name>
29
+ const AGENT_ID = process.argv[2] || 'claude-code';
30
+ const AGENT_NAME = process.argv[3] || 'Claude Code';
31
+
28
32
  if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
29
33
 
30
34
  let input = '';
@@ -50,6 +54,13 @@ process.stdin.on('end', async () => {
50
54
  : v;
51
55
  }
52
56
 
57
+ // Write flag so stop.mjs knows tool calls happened (skip text-only ALLOW)
58
+ try {
59
+ const flagDir = resolve('.solongate');
60
+ mkdirSync(flagDir, { recursive: true });
61
+ writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
62
+ } catch {}
63
+
53
64
  await fetch(`${API_URL}/api/v1/audit-logs`, {
54
65
  method: 'POST',
55
66
  headers: {
@@ -63,8 +74,8 @@ process.stdin.on('end', async () => {
63
74
  reason: hasError ? 'tool returned error' : 'allowed',
64
75
  source: 'claude-code-hook',
65
76
  evaluationTimeMs: 0,
66
- agent_id: 'claude-code',
67
- agent_name: 'Claude Code',
77
+ agent_id: AGENT_ID,
78
+ agent_name: AGENT_NAME,
68
79
  }),
69
80
  signal: AbortSignal.timeout(5000),
70
81
  });
package/hooks/guard.mjs CHANGED
@@ -4,11 +4,11 @@
4
4
  * Reads policy.json and blocks tool calls that violate constraints.
5
5
  * Also runs prompt injection detection (Stage 1 rules) on tool arguments.
6
6
  * Exit code 2 = BLOCK, exit code 0 = ALLOW.
7
- * Logs ALL decisions (ALLOW + DENY) to SolonGate Cloud.
7
+ * Logs DENY decisions to SolonGate Cloud. ALLOWs are logged by audit.mjs.
8
8
  * Auto-installed by: npx @solongate/proxy init
9
9
  */
10
- import { readFileSync, existsSync, statSync } from 'node:fs';
11
- import { resolve } from 'node:path';
10
+ import { readFileSync, existsSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import { resolve, join } from 'node:path';
12
12
 
13
13
  // Safe file read with size limit (1MB max) to prevent DoS via large files
14
14
  const MAX_FILE_READ = 1024 * 1024; // 1MB
@@ -40,6 +40,19 @@ const dotenv = loadEnvKey(hookCwdEarly);
40
40
  const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
41
41
  const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
42
42
 
43
+ // Agent identity from CLI args: node guard.mjs <agent_id> <agent_name>
44
+ const AGENT_ID = process.argv[2] || 'claude-code';
45
+ const AGENT_NAME = process.argv[3] || 'Claude Code';
46
+
47
+ // Write flag file so stop.mjs knows a tool call (DENY) happened and doesn't log extra ALLOW
48
+ function writeDenyFlag() {
49
+ try {
50
+ const flagDir = resolve('.solongate');
51
+ mkdirSync(flagDir, { recursive: true });
52
+ writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
53
+ } catch {}
54
+ }
55
+
43
56
  // ── Prompt Injection Detection (Stage 1: Rule-Based) ──
44
57
  const PI_CATEGORIES = [
45
58
  {
@@ -346,12 +359,13 @@ process.stdin.on('end', async () => {
346
359
  tool: data.tool_name || '', arguments: args,
347
360
  decision: 'DENY', reason,
348
361
  source: 'claude-code-guard',
349
- agent_id: 'claude-code', agent_name: 'Claude Code',
362
+ agent_id: AGENT_ID, agent_name: AGENT_NAME,
350
363
  }),
351
364
  signal: AbortSignal.timeout(3000),
352
365
  });
353
366
  } catch {}
354
367
  }
368
+ writeDenyFlag();
355
369
  process.stderr.write(reason);
356
370
  process.exit(2);
357
371
  }
@@ -978,7 +992,7 @@ process.stdin.on('end', async () => {
978
992
  decision: isLogOnly ? 'ALLOW' : 'DENY',
979
993
  reason: msg,
980
994
  source: 'claude-code-guard',
981
- agent_id: 'claude-code', agent_name: 'Claude Code',
995
+ agent_id: AGENT_ID, agent_name: AGENT_NAME,
982
996
  pi_detected: true,
983
997
  pi_trust_score: piResult.trustScore,
984
998
  pi_blocked: !isLogOnly,
@@ -1015,6 +1029,7 @@ process.stdin.on('end', async () => {
1015
1029
  process.stderr.write(msg);
1016
1030
  // Fall through to policy evaluation (don't exit)
1017
1031
  } else {
1032
+ writeDenyFlag();
1018
1033
  process.stderr.write(msg);
1019
1034
  process.exit(2);
1020
1035
  }
@@ -1039,7 +1054,7 @@ process.stdin.on('end', async () => {
1039
1054
  decision: 'ALLOW',
1040
1055
  reason: 'Prompt injection detected but below threshold (trust: ' + (piResult.trustScore * 100).toFixed(0) + '%)',
1041
1056
  source: 'claude-code-guard',
1042
- agent_id: 'claude-code', agent_name: 'Claude Code',
1057
+ agent_id: AGENT_ID, agent_name: AGENT_NAME,
1043
1058
  pi_detected: true,
1044
1059
  pi_trust_score: piResult.trustScore,
1045
1060
  pi_blocked: false,
@@ -1188,7 +1203,7 @@ Respond with ONLY valid JSON: {"decision": "ALLOW" or "DENY", "reason": "brief e
1188
1203
  tool: toolName, arguments: args,
1189
1204
  decision: 'DENY', reason,
1190
1205
  source: 'claude-code-guard',
1191
- agent_id: 'claude-code', agent_name: 'Claude Code',
1206
+ agent_id: AGENT_ID, agent_name: AGENT_NAME,
1192
1207
  };
1193
1208
  if (piResult) {
1194
1209
  logEntry.pi_detected = true;
@@ -1205,6 +1220,7 @@ Respond with ONLY valid JSON: {"decision": "ALLOW" or "DENY", "reason": "brief e
1205
1220
  });
1206
1221
  } catch {}
1207
1222
  }
1223
+ writeDenyFlag();
1208
1224
  process.stderr.write(reason);
1209
1225
  process.exit(2);
1210
1226
  }
package/hooks/stop.mjs ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SolonGate Stop Hook (Stop event)
4
+ * Logs a single ALLOW when Claude responds WITHOUT any tool calls.
5
+ * If tool calls were made, audit.mjs already logged them — this hook skips.
6
+ * Auto-installed by: npx @solongate/proxy init
7
+ */
8
+ import { readFileSync, existsSync, unlinkSync, writeFileSync } from 'node:fs';
9
+ import { resolve, join } from 'node:path';
10
+
11
+ function loadEnvKey(dir) {
12
+ try {
13
+ const envPath = resolve(dir, '.env');
14
+ if (!existsSync(envPath)) return {};
15
+ const lines = readFileSync(envPath, 'utf-8').split('\n');
16
+ const env = {};
17
+ for (const line of lines) {
18
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
19
+ if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
20
+ }
21
+ return env;
22
+ } catch { return {}; }
23
+ }
24
+
25
+ const dotenv = loadEnvKey(process.cwd());
26
+ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
27
+ const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
28
+
29
+ // Agent identity from CLI args: node stop.mjs <agent_id> <agent_name>
30
+ const AGENT_ID = process.argv[2] || 'claude-code';
31
+ const AGENT_NAME = process.argv[3] || 'Claude Code';
32
+
33
+ if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
34
+
35
+ // Flag file: audit.mjs writes this when a tool call was logged in this turn.
36
+ // If the flag exists, tool calls were made → skip (already logged).
37
+ // If the flag doesn't exist, no tool calls → log 1 ALLOW for the text response.
38
+ const flagDir = resolve('.solongate');
39
+ const flagFile = join(flagDir, '.last-tool-call');
40
+
41
+ let input = '';
42
+ process.stdin.on('data', c => input += c);
43
+ process.stdin.on('end', async () => {
44
+ try {
45
+ // Check if tool calls were made in this turn
46
+ if (existsSync(flagFile)) {
47
+ // Tool calls happened → audit.mjs already logged them. Clean up flag.
48
+ try { unlinkSync(flagFile); } catch {}
49
+ process.exit(0);
50
+ }
51
+
52
+ // No tool calls → log 1 ALLOW for the text-only response
53
+ await fetch(`${API_URL}/api/v1/audit-logs`, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Authorization': `Bearer ${API_KEY}`,
57
+ 'Content-Type': 'application/json',
58
+ },
59
+ body: JSON.stringify({
60
+ tool: '_response',
61
+ arguments: {},
62
+ decision: 'ALLOW',
63
+ reason: 'text response (no tool calls)',
64
+ source: 'claude-code-hook',
65
+ evaluationTimeMs: 0,
66
+ agent_id: AGENT_ID,
67
+ agent_name: AGENT_NAME,
68
+ }),
69
+ signal: AbortSignal.timeout(5000),
70
+ });
71
+ } catch {
72
+ // Silent
73
+ }
74
+ process.exit(0);
75
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.28.4",
3
+ "version": "0.28.6",
4
4
  "description": "MCP security proxy — protect any MCP server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
5
5
  "type": "module",
6
6
  "bin": {