@solongate/proxy 0.31.0 → 0.32.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/dist/index.js +58 -26
- package/dist/init.js +18 -0
- package/dist/lib.js +16 -2
- package/hooks/audit.mjs +38 -6
- package/hooks/guard.mjs +82 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -459,8 +459,8 @@ var init_cli_utils = __esm({
|
|
|
459
459
|
|
|
460
460
|
// src/init.ts
|
|
461
461
|
var init_exports = {};
|
|
462
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as
|
|
463
|
-
import { resolve as resolve3, join, dirname as dirname2 } from "path";
|
|
462
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
463
|
+
import { resolve as resolve3, join as join2, dirname as dirname2 } from "path";
|
|
464
464
|
import { homedir } from "os";
|
|
465
465
|
import { fileURLToPath } from "url";
|
|
466
466
|
import { execFileSync } from "child_process";
|
|
@@ -636,7 +636,7 @@ EXAMPLES
|
|
|
636
636
|
console.log(help);
|
|
637
637
|
}
|
|
638
638
|
function readHookScript(filename) {
|
|
639
|
-
return readFileSync4(
|
|
639
|
+
return readFileSync4(join2(HOOKS_DIR, filename), "utf-8");
|
|
640
640
|
}
|
|
641
641
|
function unlockProtectedDirs() {
|
|
642
642
|
const dirs = [".solongate", ".claude", ".cursor", ".gemini", ".antigravity", ".openclaw", ".perplexity"];
|
|
@@ -691,12 +691,12 @@ function unlockProtectedDirs() {
|
|
|
691
691
|
function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
692
692
|
unlockProtectedDirs();
|
|
693
693
|
const hooksDir = resolve3(".solongate", "hooks");
|
|
694
|
-
|
|
694
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
695
695
|
const hookFiles = ["guard.mjs", "audit.mjs", "stop.mjs"];
|
|
696
696
|
let hooksUpdated = 0;
|
|
697
697
|
let hooksSkipped = 0;
|
|
698
698
|
for (const filename of hookFiles) {
|
|
699
|
-
const hookPath =
|
|
699
|
+
const hookPath = join2(hooksDir, filename);
|
|
700
700
|
const latest = readHookScript(filename);
|
|
701
701
|
let needsWrite = true;
|
|
702
702
|
if (existsSync4(hookPath)) {
|
|
@@ -728,7 +728,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
728
728
|
const skippedNames = [];
|
|
729
729
|
for (const client of clients) {
|
|
730
730
|
const clientDir = resolve3(client.dir);
|
|
731
|
-
|
|
731
|
+
mkdirSync3(clientDir, { recursive: true });
|
|
732
732
|
const guardCmd = `node .solongate/hooks/guard.mjs ${client.agentId} "${client.agentName}"`;
|
|
733
733
|
const auditCmd = `node .solongate/hooks/audit.mjs ${client.agentId} "${client.agentName}"`;
|
|
734
734
|
const stopCmd = `node .solongate/hooks/stop.mjs ${client.agentId} "${client.agentName}"`;
|
|
@@ -763,7 +763,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
763
763
|
}
|
|
764
764
|
}
|
|
765
765
|
function installStandardHookConfig(clientDir, clientName, guardCmd, auditCmd, stopCmd) {
|
|
766
|
-
const settingsPath =
|
|
766
|
+
const settingsPath = join2(clientDir, "settings.json");
|
|
767
767
|
const hookSettings = {
|
|
768
768
|
PreToolUse: [
|
|
769
769
|
{ matcher: "", hooks: [{ type: "command", command: guardCmd }] }
|
|
@@ -796,12 +796,30 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
796
796
|
],
|
|
797
797
|
postToolUse: [
|
|
798
798
|
{ matcher: "", command: auditCmd }
|
|
799
|
+
],
|
|
800
|
+
beforeShellExecution: [
|
|
801
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
802
|
+
],
|
|
803
|
+
afterShellExecution: [
|
|
804
|
+
{ matcher: "", command: auditCmd }
|
|
805
|
+
],
|
|
806
|
+
beforeReadFile: [
|
|
807
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
808
|
+
],
|
|
809
|
+
afterFileEdit: [
|
|
810
|
+
{ matcher: "", command: auditCmd }
|
|
811
|
+
],
|
|
812
|
+
beforeMCPExecution: [
|
|
813
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
814
|
+
],
|
|
815
|
+
afterMCPExecution: [
|
|
816
|
+
{ matcher: "", command: auditCmd }
|
|
799
817
|
]
|
|
800
818
|
};
|
|
801
819
|
const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
|
|
802
|
-
const userCursorDir =
|
|
820
|
+
const userCursorDir = join2(homedir(), ".cursor");
|
|
803
821
|
if (existsSync4(userCursorDir)) {
|
|
804
|
-
const userHooksPath =
|
|
822
|
+
const userHooksPath = join2(userCursorDir, "hooks.json");
|
|
805
823
|
const existingUserHooks = existsSync4(userHooksPath) ? readFileSync4(userHooksPath, "utf-8") : "";
|
|
806
824
|
if (hooksContent !== existingUserHooks) {
|
|
807
825
|
writeFileSync2(userHooksPath, hooksContent);
|
|
@@ -809,7 +827,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
809
827
|
changed = true;
|
|
810
828
|
}
|
|
811
829
|
}
|
|
812
|
-
const hooksPath =
|
|
830
|
+
const hooksPath = join2(clientDir, "hooks.json");
|
|
813
831
|
const existingHooks = existsSync4(hooksPath) ? readFileSync4(hooksPath, "utf-8") : "";
|
|
814
832
|
if (hooksContent !== existingHooks) {
|
|
815
833
|
writeFileSync2(hooksPath, hooksContent);
|
|
@@ -831,7 +849,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
831
849
|
}
|
|
832
850
|
cursorMcpConfig.mcpServers[name] = { ...server, args };
|
|
833
851
|
}
|
|
834
|
-
const mcpPath =
|
|
852
|
+
const mcpPath = join2(clientDir, "mcp.json");
|
|
835
853
|
const mcpStr = JSON.stringify(cursorMcpConfig, null, 2) + "\n";
|
|
836
854
|
const existingMcp = existsSync4(mcpPath) ? readFileSync4(mcpPath, "utf-8") : "";
|
|
837
855
|
if (mcpStr !== existingMcp) {
|
|
@@ -840,7 +858,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
840
858
|
changed = true;
|
|
841
859
|
}
|
|
842
860
|
}
|
|
843
|
-
const staleSettings =
|
|
861
|
+
const staleSettings = join2(clientDir, "settings.json");
|
|
844
862
|
if (existsSync4(staleSettings)) {
|
|
845
863
|
try {
|
|
846
864
|
const content = JSON.parse(readFileSync4(staleSettings, "utf-8"));
|
|
@@ -855,7 +873,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
855
873
|
return changed ? "installed" : "skipped";
|
|
856
874
|
}
|
|
857
875
|
function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcpConfig) {
|
|
858
|
-
const settingsPath =
|
|
876
|
+
const settingsPath = join2(clientDir, "settings.json");
|
|
859
877
|
let existing = {};
|
|
860
878
|
try {
|
|
861
879
|
existing = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
@@ -1209,7 +1227,7 @@ var init_init = __esm({
|
|
|
1209
1227
|
"mcp.json",
|
|
1210
1228
|
".claude/mcp.json"
|
|
1211
1229
|
];
|
|
1212
|
-
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [
|
|
1230
|
+
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join2(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join2(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join2(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
|
|
1213
1231
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1214
1232
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1215
1233
|
spinnerInterval = null;
|
|
@@ -1562,8 +1580,8 @@ var init_inject = __esm({
|
|
|
1562
1580
|
|
|
1563
1581
|
// src/create.ts
|
|
1564
1582
|
var create_exports = {};
|
|
1565
|
-
import { mkdirSync as
|
|
1566
|
-
import { resolve as resolve5, join as
|
|
1583
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
|
|
1584
|
+
import { resolve as resolve5, join as join3 } from "path";
|
|
1567
1585
|
import { execSync as execSync2 } from "child_process";
|
|
1568
1586
|
function withSpinner(message, fn) {
|
|
1569
1587
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
@@ -1643,7 +1661,7 @@ EXAMPLES
|
|
|
1643
1661
|
}
|
|
1644
1662
|
function createProject(dir, name, _policy) {
|
|
1645
1663
|
writeFileSync4(
|
|
1646
|
-
|
|
1664
|
+
join3(dir, "package.json"),
|
|
1647
1665
|
JSON.stringify(
|
|
1648
1666
|
{
|
|
1649
1667
|
name,
|
|
@@ -1673,7 +1691,7 @@ function createProject(dir, name, _policy) {
|
|
|
1673
1691
|
) + "\n"
|
|
1674
1692
|
);
|
|
1675
1693
|
writeFileSync4(
|
|
1676
|
-
|
|
1694
|
+
join3(dir, "tsconfig.json"),
|
|
1677
1695
|
JSON.stringify(
|
|
1678
1696
|
{
|
|
1679
1697
|
compilerOptions: {
|
|
@@ -1693,9 +1711,9 @@ function createProject(dir, name, _policy) {
|
|
|
1693
1711
|
2
|
|
1694
1712
|
) + "\n"
|
|
1695
1713
|
);
|
|
1696
|
-
|
|
1714
|
+
mkdirSync4(join3(dir, "src"), { recursive: true });
|
|
1697
1715
|
writeFileSync4(
|
|
1698
|
-
|
|
1716
|
+
join3(dir, "src", "index.ts"),
|
|
1699
1717
|
`#!/usr/bin/env node
|
|
1700
1718
|
|
|
1701
1719
|
console.log = (...args: unknown[]) => {
|
|
@@ -1740,7 +1758,7 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1740
1758
|
`
|
|
1741
1759
|
);
|
|
1742
1760
|
writeFileSync4(
|
|
1743
|
-
|
|
1761
|
+
join3(dir, ".mcp.json"),
|
|
1744
1762
|
JSON.stringify(
|
|
1745
1763
|
{
|
|
1746
1764
|
mcpServers: {
|
|
@@ -1758,12 +1776,12 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1758
1776
|
) + "\n"
|
|
1759
1777
|
);
|
|
1760
1778
|
writeFileSync4(
|
|
1761
|
-
|
|
1779
|
+
join3(dir, ".env"),
|
|
1762
1780
|
`SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
|
|
1763
1781
|
`
|
|
1764
1782
|
);
|
|
1765
1783
|
writeFileSync4(
|
|
1766
|
-
|
|
1784
|
+
join3(dir, ".gitignore"),
|
|
1767
1785
|
`node_modules/
|
|
1768
1786
|
dist/
|
|
1769
1787
|
*.solongate-backup
|
|
@@ -1782,7 +1800,7 @@ async function main3() {
|
|
|
1782
1800
|
process.exit(1);
|
|
1783
1801
|
}
|
|
1784
1802
|
withSpinner(`Setting up ${opts.name}...`, () => {
|
|
1785
|
-
|
|
1803
|
+
mkdirSync4(dir, { recursive: true });
|
|
1786
1804
|
createProject(dir, opts.name, opts.policy);
|
|
1787
1805
|
});
|
|
1788
1806
|
if (!opts.noInstall) {
|
|
@@ -2193,8 +2211,8 @@ import {
|
|
|
2193
2211
|
ListResourceTemplatesRequestSchema
|
|
2194
2212
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2195
2213
|
import { createServer as createHttpServer } from "http";
|
|
2196
|
-
import { resolve as resolve2 } from "path";
|
|
2197
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
2214
|
+
import { resolve as resolve2, join } from "path";
|
|
2215
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
2198
2216
|
|
|
2199
2217
|
// ../core/dist/index.js
|
|
2200
2218
|
import { z } from "zod";
|
|
@@ -6249,6 +6267,20 @@ var SolonGateProxy = class {
|
|
|
6249
6267
|
if (this.server) {
|
|
6250
6268
|
const clientVersion = this.server.getClientVersion();
|
|
6251
6269
|
log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
|
|
6270
|
+
try {
|
|
6271
|
+
const debugInfo = {
|
|
6272
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6273
|
+
clientInfo: clientVersion,
|
|
6274
|
+
agentNameFlag: this.config.agentName ?? null,
|
|
6275
|
+
resolvedAgentId: this.agentId,
|
|
6276
|
+
resolvedAgentName: this.agentName,
|
|
6277
|
+
pid: process.pid
|
|
6278
|
+
};
|
|
6279
|
+
const debugDir = resolve2(".solongate");
|
|
6280
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
6281
|
+
appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
|
|
6282
|
+
} catch {
|
|
6283
|
+
}
|
|
6252
6284
|
if (clientVersion?.name && !this.config.agentName) {
|
|
6253
6285
|
const normalized = this.normalizeAgentName(clientVersion.name);
|
|
6254
6286
|
this.agentId = normalized.id;
|
package/dist/init.js
CHANGED
|
@@ -382,6 +382,24 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
382
382
|
],
|
|
383
383
|
postToolUse: [
|
|
384
384
|
{ matcher: "", command: auditCmd }
|
|
385
|
+
],
|
|
386
|
+
beforeShellExecution: [
|
|
387
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
388
|
+
],
|
|
389
|
+
afterShellExecution: [
|
|
390
|
+
{ matcher: "", command: auditCmd }
|
|
391
|
+
],
|
|
392
|
+
beforeReadFile: [
|
|
393
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
394
|
+
],
|
|
395
|
+
afterFileEdit: [
|
|
396
|
+
{ matcher: "", command: auditCmd }
|
|
397
|
+
],
|
|
398
|
+
beforeMCPExecution: [
|
|
399
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
400
|
+
],
|
|
401
|
+
afterMCPExecution: [
|
|
402
|
+
{ matcher: "", command: auditCmd }
|
|
385
403
|
]
|
|
386
404
|
};
|
|
387
405
|
const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
|
package/dist/lib.js
CHANGED
|
@@ -3612,8 +3612,8 @@ import {
|
|
|
3612
3612
|
ListResourceTemplatesRequestSchema
|
|
3613
3613
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
3614
3614
|
import { createServer as createHttpServer } from "http";
|
|
3615
|
-
import { resolve as resolve2 } from "path";
|
|
3616
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
3615
|
+
import { resolve as resolve2, join } from "path";
|
|
3616
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
3617
3617
|
|
|
3618
3618
|
// src/config.ts
|
|
3619
3619
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -4460,6 +4460,20 @@ var SolonGateProxy = class {
|
|
|
4460
4460
|
if (this.server) {
|
|
4461
4461
|
const clientVersion = this.server.getClientVersion();
|
|
4462
4462
|
log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
|
|
4463
|
+
try {
|
|
4464
|
+
const debugInfo = {
|
|
4465
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4466
|
+
clientInfo: clientVersion,
|
|
4467
|
+
agentNameFlag: this.config.agentName ?? null,
|
|
4468
|
+
resolvedAgentId: this.agentId,
|
|
4469
|
+
resolvedAgentName: this.agentName,
|
|
4470
|
+
pid: process.pid
|
|
4471
|
+
};
|
|
4472
|
+
const debugDir = resolve2(".solongate");
|
|
4473
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
4474
|
+
appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
|
|
4475
|
+
} catch {
|
|
4476
|
+
}
|
|
4463
4477
|
if (clientVersion?.name && !this.config.agentName) {
|
|
4464
4478
|
const normalized = this.normalizeAgentName(clientVersion.name);
|
|
4465
4479
|
this.agentId = normalized.id;
|
package/hooks/audit.mjs
CHANGED
|
@@ -38,8 +38,12 @@ process.stdin.on('end', async () => {
|
|
|
38
38
|
try {
|
|
39
39
|
const data = JSON.parse(input);
|
|
40
40
|
|
|
41
|
-
// Debug:
|
|
42
|
-
try {
|
|
41
|
+
// Debug: append raw stdin to file for agent detection troubleshooting
|
|
42
|
+
try {
|
|
43
|
+
const debugLine = JSON.stringify({ ts: new Date().toISOString(), argv: process.argv.slice(2), cursor_version: data.cursor_version || null, tool_name: data.tool_name || data.toolName, agent_id: AGENT_ID, hook_event_name: data.hook_event_name || null }) + '\n';
|
|
44
|
+
const { appendFileSync: afs } = await import('node:fs');
|
|
45
|
+
afs(resolve('.solongate', '.debug-audit-log'), debugLine);
|
|
46
|
+
} catch {}
|
|
43
47
|
|
|
44
48
|
// Auto-detect agent from stdin data (overrides CLI args if detected)
|
|
45
49
|
if (data.cursor_version) {
|
|
@@ -50,9 +54,35 @@ process.stdin.on('end', async () => {
|
|
|
50
54
|
AGENT_NAME = 'Gemini CLI';
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const hookEvent = data.hook_event_name || '';
|
|
58
|
+
|
|
59
|
+
// Map Cursor-specific hook events to standard tool_name/tool_input
|
|
60
|
+
let toolName = data.tool_name || data.toolName || '';
|
|
61
|
+
let toolInput = data.tool_input || data.toolInput || data.params || {};
|
|
62
|
+
|
|
63
|
+
if (!toolName && hookEvent) {
|
|
64
|
+
switch (hookEvent) {
|
|
65
|
+
case 'afterShellExecution':
|
|
66
|
+
toolName = 'Shell';
|
|
67
|
+
toolInput = { command: data.command || '' };
|
|
68
|
+
break;
|
|
69
|
+
case 'afterFileEdit':
|
|
70
|
+
toolName = 'Edit';
|
|
71
|
+
toolInput = { file_path: data.file_path || '' };
|
|
72
|
+
break;
|
|
73
|
+
case 'afterMCPExecution':
|
|
74
|
+
toolName = data.tool_name || data.toolName || `mcp:${data.server_name || 'unknown'}`;
|
|
75
|
+
toolInput = data.arguments || {};
|
|
76
|
+
break;
|
|
77
|
+
case 'postToolUse':
|
|
78
|
+
case 'postToolUseFailure':
|
|
79
|
+
toolName = data.tool_name || data.toolName || 'unknown';
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!toolName) toolName = 'unknown';
|
|
56
86
|
|
|
57
87
|
if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
|
|
58
88
|
process.exit(0);
|
|
@@ -71,14 +101,16 @@ process.stdin.on('end', async () => {
|
|
|
71
101
|
}
|
|
72
102
|
} catch {}
|
|
73
103
|
|
|
74
|
-
// Cursor uses
|
|
104
|
+
// Cursor uses tool_output, Claude uses tool_response
|
|
75
105
|
const toolResponse = data.tool_response || data.toolResponse || {};
|
|
106
|
+
const toolOutput = data.tool_output || data.toolOutput || '';
|
|
76
107
|
const resultJson = data.result_json ? (typeof data.result_json === 'string' ? data.result_json : JSON.stringify(data.result_json)) : '';
|
|
77
108
|
|
|
78
109
|
const hasError = guardDenied ||
|
|
79
110
|
toolResponse.error ||
|
|
80
111
|
toolResponse.exitCode > 0 ||
|
|
81
112
|
toolResponse.isError ||
|
|
113
|
+
(toolOutput && typeof toolOutput === 'string' && toolOutput.includes('"error"')) ||
|
|
82
114
|
(resultJson && resultJson.includes('"error"'));
|
|
83
115
|
|
|
84
116
|
const argsSummary = {};
|
package/hooks/guard.mjs
CHANGED
|
@@ -46,17 +46,33 @@ let AGENT_ID = process.argv[2] || 'claude-code';
|
|
|
46
46
|
let AGENT_NAME = process.argv[3] || 'Claude Code';
|
|
47
47
|
|
|
48
48
|
// ── Per-tool block/allow output ──
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
49
|
+
// Response format depends on BOTH the agent AND the hook event type:
|
|
50
|
+
// Claude Code: exit 2 + stderr = BLOCK, exit 0 = ALLOW
|
|
51
|
+
// Cursor preToolUse: {"permission": "deny/allow", "user_message": "..."}
|
|
52
|
+
// Cursor before*: {"continue": false/true, "userMessage": "..."}
|
|
53
|
+
// Gemini CLI: {"decision": "deny/allow", "reason": "..."}
|
|
54
|
+
let HOOK_EVENT = ''; // set from stdin hook_event_name
|
|
55
|
+
const CURSOR_BEFORE_EVENTS = new Set([
|
|
56
|
+
'beforeShellExecution', 'beforeReadFile', 'beforeMCPExecution',
|
|
57
|
+
]);
|
|
58
|
+
|
|
52
59
|
function blockTool(reason) {
|
|
53
60
|
if (AGENT_ID === 'cursor') {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
if (CURSOR_BEFORE_EVENTS.has(HOOK_EVENT)) {
|
|
62
|
+
// Cursor before* events use continue: false
|
|
63
|
+
process.stdout.write(JSON.stringify({
|
|
64
|
+
continue: false,
|
|
65
|
+
userMessage: `[SolonGate] ${reason}`,
|
|
66
|
+
agentMessage: `BLOCKED by SolonGate security policy: ${reason}`,
|
|
67
|
+
}));
|
|
68
|
+
} else {
|
|
69
|
+
// Cursor preToolUse uses permission: deny
|
|
70
|
+
process.stdout.write(JSON.stringify({
|
|
71
|
+
permission: 'deny',
|
|
72
|
+
user_message: `[SolonGate] ${reason}`,
|
|
73
|
+
agent_message: `BLOCKED by SolonGate security policy: ${reason}`,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
60
76
|
process.exit(0);
|
|
61
77
|
} else if (AGENT_ID === 'gemini-cli') {
|
|
62
78
|
process.stdout.write(JSON.stringify({
|
|
@@ -73,7 +89,11 @@ function blockTool(reason) {
|
|
|
73
89
|
|
|
74
90
|
function allowTool() {
|
|
75
91
|
if (AGENT_ID === 'cursor') {
|
|
76
|
-
|
|
92
|
+
if (CURSOR_BEFORE_EVENTS.has(HOOK_EVENT)) {
|
|
93
|
+
process.stdout.write(JSON.stringify({ continue: true }));
|
|
94
|
+
} else {
|
|
95
|
+
process.stdout.write(JSON.stringify({ permission: 'allow' }));
|
|
96
|
+
}
|
|
77
97
|
} else if (AGENT_ID === 'gemini-cli') {
|
|
78
98
|
process.stdout.write(JSON.stringify({ decision: 'allow' }));
|
|
79
99
|
}
|
|
@@ -384,14 +404,62 @@ process.stdin.on('end', async () => {
|
|
|
384
404
|
AGENT_NAME = 'Gemini CLI';
|
|
385
405
|
}
|
|
386
406
|
|
|
387
|
-
//
|
|
407
|
+
// Track hook event name for response format selection
|
|
408
|
+
HOOK_EVENT = raw.hook_event_name || '';
|
|
409
|
+
|
|
410
|
+
// Debug: append guard invocation to debug log
|
|
411
|
+
try {
|
|
412
|
+
const { appendFileSync: afs, mkdirSync: mds } = await import('node:fs');
|
|
413
|
+
mds(resolve('.solongate'), { recursive: true });
|
|
414
|
+
const debugLine = JSON.stringify({ ts: new Date().toISOString(), hook: 'guard', argv: process.argv.slice(2), cursor_version: raw.cursor_version || null, tool_name: raw.tool_name || raw.toolName || raw.command, agent_id: AGENT_ID, hook_event_name: HOOK_EVENT }) + '\n';
|
|
415
|
+
afs(resolve('.solongate', '.debug-guard-log'), debugLine);
|
|
416
|
+
} catch {}
|
|
417
|
+
|
|
418
|
+
// Map Cursor-specific hook events to standard tool_name/tool_input format
|
|
419
|
+
// beforeShellExecution: {command, cwd} → tool_name=Shell, tool_input={command}
|
|
420
|
+
// beforeReadFile: {file_path} → tool_name=Read, tool_input={file_path}
|
|
421
|
+
// afterFileEdit: {file_path, new_content} → tool_name=Edit, tool_input={file_path}
|
|
422
|
+
// beforeMCPExecution: {server_name, tool_name, arguments} → tool_name=<mcp_tool>, tool_input=arguments
|
|
423
|
+
let mappedToolName = raw.tool_name || raw.toolName || '';
|
|
424
|
+
let mappedToolInput = raw.tool_input || raw.toolInput || raw.params || {};
|
|
425
|
+
|
|
426
|
+
if (!mappedToolName && HOOK_EVENT) {
|
|
427
|
+
switch (HOOK_EVENT) {
|
|
428
|
+
case 'beforeShellExecution':
|
|
429
|
+
mappedToolName = 'Shell';
|
|
430
|
+
mappedToolInput = { command: raw.command || '', cwd: raw.cwd || '' };
|
|
431
|
+
break;
|
|
432
|
+
case 'afterShellExecution':
|
|
433
|
+
mappedToolName = 'Shell';
|
|
434
|
+
mappedToolInput = { command: raw.command || '', cwd: raw.cwd || '' };
|
|
435
|
+
break;
|
|
436
|
+
case 'beforeReadFile':
|
|
437
|
+
mappedToolName = 'Read';
|
|
438
|
+
mappedToolInput = { file_path: raw.file_path || '' };
|
|
439
|
+
break;
|
|
440
|
+
case 'afterFileEdit':
|
|
441
|
+
mappedToolName = 'Edit';
|
|
442
|
+
mappedToolInput = { file_path: raw.file_path || '', new_content: raw.new_content || '' };
|
|
443
|
+
break;
|
|
444
|
+
case 'beforeMCPExecution':
|
|
445
|
+
mappedToolName = raw.tool_name || raw.toolName || `mcp:${raw.server_name || 'unknown'}`;
|
|
446
|
+
mappedToolInput = raw.arguments || raw.tool_input || {};
|
|
447
|
+
break;
|
|
448
|
+
case 'afterMCPExecution':
|
|
449
|
+
mappedToolName = raw.tool_name || raw.toolName || `mcp:${raw.server_name || 'unknown'}`;
|
|
450
|
+
mappedToolInput = raw.arguments || raw.tool_input || {};
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Normalize field names across tools
|
|
388
456
|
const data = {
|
|
389
457
|
...raw,
|
|
390
|
-
tool_name:
|
|
391
|
-
tool_input:
|
|
458
|
+
tool_name: mappedToolName,
|
|
459
|
+
tool_input: mappedToolInput,
|
|
392
460
|
tool_response: raw.tool_response || raw.toolResponse || {},
|
|
393
461
|
cwd: raw.cwd || process.cwd(),
|
|
394
|
-
session_id: raw.session_id || raw.sessionId || '',
|
|
462
|
+
session_id: raw.session_id || raw.sessionId || raw.conversation_id || '',
|
|
395
463
|
};
|
|
396
464
|
const args = data.tool_input;
|
|
397
465
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "AI tool security proxy — protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|