@solongate/proxy 0.28.4 → 0.28.5
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 +12 -0
- package/dist/init.js +12 -0
- package/hooks/audit.mjs +9 -2
- package/hooks/guard.mjs +15 -3
- package/hooks/stop.mjs +71 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -678,6 +678,9 @@ 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 stopPath = join(hooksDir, "stop.mjs");
|
|
682
|
+
writeFileSync2(stopPath, readHookScript("stop.mjs"));
|
|
683
|
+
console.log(` Created ${stopPath}`);
|
|
681
684
|
const hookSettings = {
|
|
682
685
|
hooks: {
|
|
683
686
|
PreToolUse: [
|
|
@@ -695,6 +698,14 @@ function installHooks(selectedTools = []) {
|
|
|
695
698
|
{ type: "command", command: "node .solongate/hooks/audit.mjs" }
|
|
696
699
|
]
|
|
697
700
|
}
|
|
701
|
+
],
|
|
702
|
+
Stop: [
|
|
703
|
+
{
|
|
704
|
+
matcher: "",
|
|
705
|
+
hooks: [
|
|
706
|
+
{ type: "command", command: "node .solongate/hooks/stop.mjs" }
|
|
707
|
+
]
|
|
708
|
+
}
|
|
698
709
|
]
|
|
699
710
|
}
|
|
700
711
|
};
|
|
@@ -726,6 +737,7 @@ function installHooks(selectedTools = []) {
|
|
|
726
737
|
console.log(" Hooks installed:");
|
|
727
738
|
console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
|
|
728
739
|
console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
|
|
740
|
+
console.log(" stop.mjs \u2192 tracks text-only responses (no tool calls)");
|
|
729
741
|
console.log(` Activated for: ${activatedNames.join(", ")}`);
|
|
730
742
|
}
|
|
731
743
|
function ensureEnvFile() {
|
package/dist/init.js
CHANGED
|
@@ -261,6 +261,9 @@ 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 stopPath = join(hooksDir, "stop.mjs");
|
|
265
|
+
writeFileSync(stopPath, readHookScript("stop.mjs"));
|
|
266
|
+
console.log(` Created ${stopPath}`);
|
|
264
267
|
const hookSettings = {
|
|
265
268
|
hooks: {
|
|
266
269
|
PreToolUse: [
|
|
@@ -278,6 +281,14 @@ function installHooks(selectedTools = []) {
|
|
|
278
281
|
{ type: "command", command: "node .solongate/hooks/audit.mjs" }
|
|
279
282
|
]
|
|
280
283
|
}
|
|
284
|
+
],
|
|
285
|
+
Stop: [
|
|
286
|
+
{
|
|
287
|
+
matcher: "",
|
|
288
|
+
hooks: [
|
|
289
|
+
{ type: "command", command: "node .solongate/hooks/stop.mjs" }
|
|
290
|
+
]
|
|
291
|
+
}
|
|
281
292
|
]
|
|
282
293
|
}
|
|
283
294
|
};
|
|
@@ -309,6 +320,7 @@ function installHooks(selectedTools = []) {
|
|
|
309
320
|
console.log(" Hooks installed:");
|
|
310
321
|
console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
|
|
311
322
|
console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
|
|
323
|
+
console.log(" stop.mjs \u2192 tracks text-only responses (no tool calls)");
|
|
312
324
|
console.log(` Activated for: ${activatedNames.join(", ")}`);
|
|
313
325
|
}
|
|
314
326
|
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 {
|
|
@@ -50,6 +50,13 @@ process.stdin.on('end', async () => {
|
|
|
50
50
|
: v;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Write flag so stop.mjs knows tool calls happened (skip text-only ALLOW)
|
|
54
|
+
try {
|
|
55
|
+
const flagDir = resolve('.solongate');
|
|
56
|
+
mkdirSync(flagDir, { recursive: true });
|
|
57
|
+
writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
|
|
58
|
+
} catch {}
|
|
59
|
+
|
|
53
60
|
await fetch(`${API_URL}/api/v1/audit-logs`, {
|
|
54
61
|
method: 'POST',
|
|
55
62
|
headers: {
|
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
|
|
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,15 @@ 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
|
+
// Write flag file so stop.mjs knows a tool call (DENY) happened and doesn't log extra ALLOW
|
|
44
|
+
function writeDenyFlag() {
|
|
45
|
+
try {
|
|
46
|
+
const flagDir = resolve('.solongate');
|
|
47
|
+
mkdirSync(flagDir, { recursive: true });
|
|
48
|
+
writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
// ── Prompt Injection Detection (Stage 1: Rule-Based) ──
|
|
44
53
|
const PI_CATEGORIES = [
|
|
45
54
|
{
|
|
@@ -352,6 +361,7 @@ process.stdin.on('end', async () => {
|
|
|
352
361
|
});
|
|
353
362
|
} catch {}
|
|
354
363
|
}
|
|
364
|
+
writeDenyFlag();
|
|
355
365
|
process.stderr.write(reason);
|
|
356
366
|
process.exit(2);
|
|
357
367
|
}
|
|
@@ -1015,6 +1025,7 @@ process.stdin.on('end', async () => {
|
|
|
1015
1025
|
process.stderr.write(msg);
|
|
1016
1026
|
// Fall through to policy evaluation (don't exit)
|
|
1017
1027
|
} else {
|
|
1028
|
+
writeDenyFlag();
|
|
1018
1029
|
process.stderr.write(msg);
|
|
1019
1030
|
process.exit(2);
|
|
1020
1031
|
}
|
|
@@ -1205,6 +1216,7 @@ Respond with ONLY valid JSON: {"decision": "ALLOW" or "DENY", "reason": "brief e
|
|
|
1205
1216
|
});
|
|
1206
1217
|
} catch {}
|
|
1207
1218
|
}
|
|
1219
|
+
writeDenyFlag();
|
|
1208
1220
|
process.stderr.write(reason);
|
|
1209
1221
|
process.exit(2);
|
|
1210
1222
|
}
|
package/hooks/stop.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
|
|
30
|
+
|
|
31
|
+
// Flag file: audit.mjs writes this when a tool call was logged in this turn.
|
|
32
|
+
// If the flag exists, tool calls were made → skip (already logged).
|
|
33
|
+
// If the flag doesn't exist, no tool calls → log 1 ALLOW for the text response.
|
|
34
|
+
const flagDir = resolve('.solongate');
|
|
35
|
+
const flagFile = join(flagDir, '.last-tool-call');
|
|
36
|
+
|
|
37
|
+
let input = '';
|
|
38
|
+
process.stdin.on('data', c => input += c);
|
|
39
|
+
process.stdin.on('end', async () => {
|
|
40
|
+
try {
|
|
41
|
+
// Check if tool calls were made in this turn
|
|
42
|
+
if (existsSync(flagFile)) {
|
|
43
|
+
// Tool calls happened → audit.mjs already logged them. Clean up flag.
|
|
44
|
+
try { unlinkSync(flagFile); } catch {}
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// No tool calls → log 1 ALLOW for the text-only response
|
|
49
|
+
await fetch(`${API_URL}/api/v1/audit-logs`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
53
|
+
'Content-Type': 'application/json',
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
tool: '_response',
|
|
57
|
+
arguments: {},
|
|
58
|
+
decision: 'ALLOW',
|
|
59
|
+
reason: 'text response (no tool calls)',
|
|
60
|
+
source: 'claude-code-hook',
|
|
61
|
+
evaluationTimeMs: 0,
|
|
62
|
+
agent_id: 'claude-code',
|
|
63
|
+
agent_name: 'Claude Code',
|
|
64
|
+
}),
|
|
65
|
+
signal: AbortSignal.timeout(5000),
|
|
66
|
+
});
|
|
67
|
+
} catch {
|
|
68
|
+
// Silent
|
|
69
|
+
}
|
|
70
|
+
process.exit(0);
|
|
71
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.5",
|
|
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": {
|