@solongate/proxy 0.35.0 → 0.37.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 +25 -51
- package/dist/init.js +3 -15
- package/dist/lib.js +22 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -536,7 +536,7 @@ function isAlreadyProtected(server) {
|
|
|
536
536
|
}
|
|
537
537
|
return false;
|
|
538
538
|
}
|
|
539
|
-
function wrapServer(serverName, server, policy, agentName
|
|
539
|
+
function wrapServer(serverName, server, policy, agentName) {
|
|
540
540
|
const env = { ...server.env ?? {} };
|
|
541
541
|
env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
|
|
542
542
|
const proxyArgs = ["-y", "@solongate/proxy@latest"];
|
|
@@ -546,9 +546,6 @@ function wrapServer(serverName, server, policy, agentName, aiJudge) {
|
|
|
546
546
|
if (agentName) {
|
|
547
547
|
proxyArgs.push("--agent-name", agentName);
|
|
548
548
|
}
|
|
549
|
-
if (aiJudge) {
|
|
550
|
-
proxyArgs.push("--ai-judge");
|
|
551
|
-
}
|
|
552
549
|
proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
|
|
553
550
|
return {
|
|
554
551
|
command: "npx",
|
|
@@ -569,8 +566,7 @@ function parseInitArgs(argv) {
|
|
|
569
566
|
const args = argv.slice(2);
|
|
570
567
|
const options = {
|
|
571
568
|
all: false,
|
|
572
|
-
tools: []
|
|
573
|
-
aiJudge: false
|
|
569
|
+
tools: []
|
|
574
570
|
};
|
|
575
571
|
for (let i = 0; i < args.length; i++) {
|
|
576
572
|
switch (args[i]) {
|
|
@@ -595,9 +591,6 @@ function parseInitArgs(argv) {
|
|
|
595
591
|
case "--openclaw":
|
|
596
592
|
options.tools.push("openclaw");
|
|
597
593
|
break;
|
|
598
|
-
case "--ai-judge":
|
|
599
|
-
options.aiJudge = true;
|
|
600
|
-
break;
|
|
601
594
|
case "--help":
|
|
602
595
|
case "-h":
|
|
603
596
|
printHelp();
|
|
@@ -625,13 +618,8 @@ AI TOOL HOOKS (default: all)
|
|
|
625
618
|
--gemini Install hooks for Gemini CLI
|
|
626
619
|
--openclaw Install hooks for OpenClaw
|
|
627
620
|
|
|
628
|
-
SECURITY LAYERS
|
|
629
|
-
--ai-judge Enable AI Judge (semantic intent analysis via LLM)
|
|
630
|
-
Requires GROQ_API_KEY in .env (free at https://console.groq.com/keys)
|
|
631
|
-
|
|
632
621
|
EXAMPLES
|
|
633
622
|
npx @solongate/proxy init --all # Protect everything, all tools
|
|
634
|
-
npx @solongate/proxy init --all --ai-judge # With AI Judge enabled
|
|
635
623
|
npx @solongate/proxy init --all --claude-code --gemini # Only Claude Code + Gemini hooks
|
|
636
624
|
npx @solongate/proxy init --all --policy policy.json # With custom policy
|
|
637
625
|
`;
|
|
@@ -1061,7 +1049,7 @@ async function main() {
|
|
|
1061
1049
|
const newConfig = { mcpServers: {} };
|
|
1062
1050
|
for (const name of serverNames) {
|
|
1063
1051
|
if (toProtect.includes(name)) {
|
|
1064
|
-
newConfig.mcpServers[name] = wrapServer(name, config.mcpServers[name], policyValue
|
|
1052
|
+
newConfig.mcpServers[name] = wrapServer(name, config.mcpServers[name], policyValue);
|
|
1065
1053
|
} else {
|
|
1066
1054
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
1067
1055
|
}
|
|
@@ -5652,39 +5640,34 @@ var PolicySyncManager = class {
|
|
|
5652
5640
|
};
|
|
5653
5641
|
|
|
5654
5642
|
// src/ai-judge.ts
|
|
5655
|
-
var SYSTEM_PROMPT = `You are a security judge for an AI coding tool.
|
|
5643
|
+
var SYSTEM_PROMPT = `You are a security judge for an AI coding tool. Your ONLY job is to check if a tool call accesses files or paths that are in the protected lists. Nothing else.
|
|
5656
5644
|
|
|
5657
5645
|
You will receive a JSON object with:
|
|
5658
5646
|
- "tool": the tool name being called
|
|
5659
5647
|
- "arguments": the tool's arguments
|
|
5660
|
-
- "protected_files": EXACT list of
|
|
5661
|
-
- "protected_paths": EXACT list of directories
|
|
5662
|
-
- "denied_actions": list of actions that are never allowed
|
|
5663
|
-
|
|
5664
|
-
IMPORTANT: You must ONLY protect files and paths that are EXPLICITLY listed in protected_files and protected_paths. If a file is NOT in the list, it is NOT protected and access should be ALLOWED. Do NOT invent or assume additional protected files.
|
|
5665
|
-
|
|
5666
|
-
DENY if the tool call could, directly or indirectly, access a file from the protected_files list \u2014 even through:
|
|
5667
|
-
- Shell glob patterns (e.g., "cred*" could match "credentials.json" IF credentials.json is in protected_files)
|
|
5668
|
-
- Command substitution ($(...), backticks)
|
|
5669
|
-
- Process substitution (<(cat file)) \u2014 check inside <(...) for protected files
|
|
5670
|
-
- Variable interpolation (e.g., f=".en"; cat \${f}v builds ".env" \u2014 DENY only if .env is in protected_files)
|
|
5671
|
-
- Input redirection (< file)
|
|
5672
|
-
- Multi-stage operations: tar/cp a protected file then read the copy \u2014 DENY the entire chain
|
|
5673
|
-
- Any utility that reads file content (cat, head, tail, less, perl, awk, sed, xxd, od, strings, dd, etc.)
|
|
5648
|
+
- "protected_files": the EXACT and COMPLETE list of protected files from the user's policy
|
|
5649
|
+
- "protected_paths": the EXACT and COMPLETE list of protected directories from the user's policy
|
|
5674
5650
|
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5651
|
+
RULES:
|
|
5652
|
+
1. DENY ONLY if the tool call could access a file or path that is in protected_files or protected_paths.
|
|
5653
|
+
2. ALLOW everything else. You must NOT invent your own security rules.
|
|
5654
|
+
3. If a file is NOT in protected_files, it is NOT protected \u2014 even if the filename looks sensitive.
|
|
5655
|
+
4. "cat test.txt" is ALLOWED if test.txt is not in protected_files.
|
|
5656
|
+
5. "curl https://example.com" is ALLOWED unless it sends protected file content.
|
|
5657
|
+
6. "printenv" is ALLOWED unless the policy explicitly protects it.
|
|
5679
5658
|
|
|
5680
|
-
|
|
5681
|
-
-
|
|
5682
|
-
-
|
|
5683
|
-
-
|
|
5659
|
+
BYPASS DETECTION \u2014 DENY if the command accesses a protected file through:
|
|
5660
|
+
- Shell glob patterns: "cat cred*" could match "credentials.json" IF it is in protected_files
|
|
5661
|
+
- Command substitution: "cat $(echo .env)" builds ".env"
|
|
5662
|
+
- Variable interpolation: f=".en"; cat \${f}v builds ".env"
|
|
5663
|
+
- Process substitution: <(cat .env)
|
|
5664
|
+
- Multi-stage: cp protected_file /tmp/x && cat /tmp/x
|
|
5665
|
+
- Input redirection: < .env
|
|
5666
|
+
- Any file-reading utility (cat, head, tail, less, perl, awk, sed, xxd, etc.)
|
|
5684
5667
|
|
|
5685
|
-
CRITICAL:
|
|
5668
|
+
CRITICAL: You have NO security opinions of your own. You ONLY enforce the protected_files and protected_paths lists. If something is not in those lists, it is ALLOWED. Do NOT over-block.
|
|
5686
5669
|
|
|
5687
|
-
Respond with ONLY valid JSON
|
|
5670
|
+
Respond with ONLY valid JSON:
|
|
5688
5671
|
{"decision": "ALLOW" or "DENY", "reason": "brief one-line explanation", "confidence": 0.0 to 1.0}`;
|
|
5689
5672
|
var AuthError = class extends Error {
|
|
5690
5673
|
constructor(message) {
|
|
@@ -5696,24 +5679,16 @@ var AiJudge = class _AiJudge {
|
|
|
5696
5679
|
config;
|
|
5697
5680
|
protectedFiles;
|
|
5698
5681
|
protectedPaths;
|
|
5699
|
-
deniedActions;
|
|
5700
5682
|
isOllamaEndpoint;
|
|
5701
5683
|
// Circuit breaker: after 3 consecutive failures in 60s, skip AI Judge
|
|
5702
5684
|
consecutiveFailures = 0;
|
|
5703
5685
|
lastFailureTime = 0;
|
|
5704
5686
|
static CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
5705
5687
|
static CIRCUIT_BREAKER_RESET_MS = 6e4;
|
|
5706
|
-
constructor(config, protectedFiles, protectedPaths
|
|
5707
|
-
"file deletion",
|
|
5708
|
-
"data exfiltration",
|
|
5709
|
-
"remote code execution",
|
|
5710
|
-
"environment variable leak",
|
|
5711
|
-
"security control bypass"
|
|
5712
|
-
]) {
|
|
5688
|
+
constructor(config, protectedFiles, protectedPaths) {
|
|
5713
5689
|
this.config = config;
|
|
5714
5690
|
this.protectedFiles = protectedFiles;
|
|
5715
5691
|
this.protectedPaths = protectedPaths;
|
|
5716
|
-
this.deniedActions = deniedActions;
|
|
5717
5692
|
this.isOllamaEndpoint = config.endpoint.includes("11434") || config.endpoint.includes("ollama");
|
|
5718
5693
|
}
|
|
5719
5694
|
/**
|
|
@@ -5737,8 +5712,7 @@ var AiJudge = class _AiJudge {
|
|
|
5737
5712
|
tool: toolName,
|
|
5738
5713
|
arguments: sanitizedArgs,
|
|
5739
5714
|
protected_files: this.protectedFiles,
|
|
5740
|
-
protected_paths: this.protectedPaths
|
|
5741
|
-
denied_actions: this.deniedActions
|
|
5715
|
+
protected_paths: this.protectedPaths
|
|
5742
5716
|
});
|
|
5743
5717
|
try {
|
|
5744
5718
|
const response = await this.callLLM(userMessage);
|
package/dist/init.js
CHANGED
|
@@ -117,7 +117,7 @@ function isAlreadyProtected(server) {
|
|
|
117
117
|
}
|
|
118
118
|
return false;
|
|
119
119
|
}
|
|
120
|
-
function wrapServer(serverName, server, policy, agentName
|
|
120
|
+
function wrapServer(serverName, server, policy, agentName) {
|
|
121
121
|
const env = { ...server.env ?? {} };
|
|
122
122
|
env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
|
|
123
123
|
const proxyArgs = ["-y", "@solongate/proxy@latest"];
|
|
@@ -127,9 +127,6 @@ function wrapServer(serverName, server, policy, agentName, aiJudge) {
|
|
|
127
127
|
if (agentName) {
|
|
128
128
|
proxyArgs.push("--agent-name", agentName);
|
|
129
129
|
}
|
|
130
|
-
if (aiJudge) {
|
|
131
|
-
proxyArgs.push("--ai-judge");
|
|
132
|
-
}
|
|
133
130
|
proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
|
|
134
131
|
return {
|
|
135
132
|
command: "npx",
|
|
@@ -150,8 +147,7 @@ function parseInitArgs(argv) {
|
|
|
150
147
|
const args = argv.slice(2);
|
|
151
148
|
const options = {
|
|
152
149
|
all: false,
|
|
153
|
-
tools: []
|
|
154
|
-
aiJudge: false
|
|
150
|
+
tools: []
|
|
155
151
|
};
|
|
156
152
|
for (let i = 0; i < args.length; i++) {
|
|
157
153
|
switch (args[i]) {
|
|
@@ -176,9 +172,6 @@ function parseInitArgs(argv) {
|
|
|
176
172
|
case "--openclaw":
|
|
177
173
|
options.tools.push("openclaw");
|
|
178
174
|
break;
|
|
179
|
-
case "--ai-judge":
|
|
180
|
-
options.aiJudge = true;
|
|
181
|
-
break;
|
|
182
175
|
case "--help":
|
|
183
176
|
case "-h":
|
|
184
177
|
printHelp();
|
|
@@ -206,13 +199,8 @@ AI TOOL HOOKS (default: all)
|
|
|
206
199
|
--gemini Install hooks for Gemini CLI
|
|
207
200
|
--openclaw Install hooks for OpenClaw
|
|
208
201
|
|
|
209
|
-
SECURITY LAYERS
|
|
210
|
-
--ai-judge Enable AI Judge (semantic intent analysis via LLM)
|
|
211
|
-
Requires GROQ_API_KEY in .env (free at https://console.groq.com/keys)
|
|
212
|
-
|
|
213
202
|
EXAMPLES
|
|
214
203
|
npx @solongate/proxy init --all # Protect everything, all tools
|
|
215
|
-
npx @solongate/proxy init --all --ai-judge # With AI Judge enabled
|
|
216
204
|
npx @solongate/proxy init --all --claude-code --gemini # Only Claude Code + Gemini hooks
|
|
217
205
|
npx @solongate/proxy init --all --policy policy.json # With custom policy
|
|
218
206
|
`;
|
|
@@ -644,7 +632,7 @@ async function main() {
|
|
|
644
632
|
const newConfig = { mcpServers: {} };
|
|
645
633
|
for (const name of serverNames) {
|
|
646
634
|
if (toProtect.includes(name)) {
|
|
647
|
-
newConfig.mcpServers[name] = wrapServer(name, config.mcpServers[name], policyValue
|
|
635
|
+
newConfig.mcpServers[name] = wrapServer(name, config.mcpServers[name], policyValue);
|
|
648
636
|
} else {
|
|
649
637
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
650
638
|
}
|
package/dist/lib.js
CHANGED
|
@@ -3939,39 +3939,34 @@ var PolicySyncManager = class {
|
|
|
3939
3939
|
};
|
|
3940
3940
|
|
|
3941
3941
|
// src/ai-judge.ts
|
|
3942
|
-
var SYSTEM_PROMPT = `You are a security judge for an AI coding tool.
|
|
3942
|
+
var SYSTEM_PROMPT = `You are a security judge for an AI coding tool. Your ONLY job is to check if a tool call accesses files or paths that are in the protected lists. Nothing else.
|
|
3943
3943
|
|
|
3944
3944
|
You will receive a JSON object with:
|
|
3945
3945
|
- "tool": the tool name being called
|
|
3946
3946
|
- "arguments": the tool's arguments
|
|
3947
|
-
- "protected_files": EXACT list of
|
|
3948
|
-
- "protected_paths": EXACT list of directories
|
|
3949
|
-
- "denied_actions": list of actions that are never allowed
|
|
3947
|
+
- "protected_files": the EXACT and COMPLETE list of protected files from the user's policy
|
|
3948
|
+
- "protected_paths": the EXACT and COMPLETE list of protected directories from the user's policy
|
|
3950
3949
|
|
|
3951
|
-
|
|
3950
|
+
RULES:
|
|
3951
|
+
1. DENY ONLY if the tool call could access a file or path that is in protected_files or protected_paths.
|
|
3952
|
+
2. ALLOW everything else. You must NOT invent your own security rules.
|
|
3953
|
+
3. If a file is NOT in protected_files, it is NOT protected \u2014 even if the filename looks sensitive.
|
|
3954
|
+
4. "cat test.txt" is ALLOWED if test.txt is not in protected_files.
|
|
3955
|
+
5. "curl https://example.com" is ALLOWED unless it sends protected file content.
|
|
3956
|
+
6. "printenv" is ALLOWED unless the policy explicitly protects it.
|
|
3952
3957
|
|
|
3953
|
-
DENY if the
|
|
3954
|
-
- Shell glob patterns
|
|
3955
|
-
- Command substitution
|
|
3956
|
-
-
|
|
3957
|
-
-
|
|
3958
|
-
-
|
|
3959
|
-
-
|
|
3960
|
-
- Any
|
|
3958
|
+
BYPASS DETECTION \u2014 DENY if the command accesses a protected file through:
|
|
3959
|
+
- Shell glob patterns: "cat cred*" could match "credentials.json" IF it is in protected_files
|
|
3960
|
+
- Command substitution: "cat $(echo .env)" builds ".env"
|
|
3961
|
+
- Variable interpolation: f=".en"; cat \${f}v builds ".env"
|
|
3962
|
+
- Process substitution: <(cat .env)
|
|
3963
|
+
- Multi-stage: cp protected_file /tmp/x && cat /tmp/x
|
|
3964
|
+
- Input redirection: < .env
|
|
3965
|
+
- Any file-reading utility (cat, head, tail, less, perl, awk, sed, xxd, etc.)
|
|
3961
3966
|
|
|
3962
|
-
|
|
3963
|
-
- The command sends data to external URLs (curl -d, wget --post)
|
|
3964
|
-
- The command leaks environment variables (printenv, env, process.env)
|
|
3965
|
-
- The command executes remotely downloaded code (curl|bash)
|
|
3967
|
+
CRITICAL: You have NO security opinions of your own. You ONLY enforce the protected_files and protected_paths lists. If something is not in those lists, it is ALLOWED. Do NOT over-block.
|
|
3966
3968
|
|
|
3967
|
-
|
|
3968
|
-
- The file is NOT in protected_files \u2014 even if cat, head, etc. is used. Reading non-protected files is normal.
|
|
3969
|
-
- The action is a normal development operation (ls, git status, npm build, cat app.js, etc.)
|
|
3970
|
-
- The action does not touch any protected file or path
|
|
3971
|
-
|
|
3972
|
-
CRITICAL: Only DENY access to files EXPLICITLY in the protected_files list. "cat app.js" is ALLOWED if app.js is not in protected_files. Do NOT over-block.
|
|
3973
|
-
|
|
3974
|
-
Respond with ONLY valid JSON, no markdown, no explanation outside the JSON:
|
|
3969
|
+
Respond with ONLY valid JSON:
|
|
3975
3970
|
{"decision": "ALLOW" or "DENY", "reason": "brief one-line explanation", "confidence": 0.0 to 1.0}`;
|
|
3976
3971
|
var AuthError = class extends Error {
|
|
3977
3972
|
constructor(message) {
|
|
@@ -3983,24 +3978,16 @@ var AiJudge = class _AiJudge {
|
|
|
3983
3978
|
config;
|
|
3984
3979
|
protectedFiles;
|
|
3985
3980
|
protectedPaths;
|
|
3986
|
-
deniedActions;
|
|
3987
3981
|
isOllamaEndpoint;
|
|
3988
3982
|
// Circuit breaker: after 3 consecutive failures in 60s, skip AI Judge
|
|
3989
3983
|
consecutiveFailures = 0;
|
|
3990
3984
|
lastFailureTime = 0;
|
|
3991
3985
|
static CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
3992
3986
|
static CIRCUIT_BREAKER_RESET_MS = 6e4;
|
|
3993
|
-
constructor(config, protectedFiles, protectedPaths
|
|
3994
|
-
"file deletion",
|
|
3995
|
-
"data exfiltration",
|
|
3996
|
-
"remote code execution",
|
|
3997
|
-
"environment variable leak",
|
|
3998
|
-
"security control bypass"
|
|
3999
|
-
]) {
|
|
3987
|
+
constructor(config, protectedFiles, protectedPaths) {
|
|
4000
3988
|
this.config = config;
|
|
4001
3989
|
this.protectedFiles = protectedFiles;
|
|
4002
3990
|
this.protectedPaths = protectedPaths;
|
|
4003
|
-
this.deniedActions = deniedActions;
|
|
4004
3991
|
this.isOllamaEndpoint = config.endpoint.includes("11434") || config.endpoint.includes("ollama");
|
|
4005
3992
|
}
|
|
4006
3993
|
/**
|
|
@@ -4024,8 +4011,7 @@ var AiJudge = class _AiJudge {
|
|
|
4024
4011
|
tool: toolName,
|
|
4025
4012
|
arguments: sanitizedArgs,
|
|
4026
4013
|
protected_files: this.protectedFiles,
|
|
4027
|
-
protected_paths: this.protectedPaths
|
|
4028
|
-
denied_actions: this.deniedActions
|
|
4014
|
+
protected_paths: this.protectedPaths
|
|
4029
4015
|
});
|
|
4030
4016
|
try {
|
|
4031
4017
|
const response = await this.callLLM(userMessage);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.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": {
|