@solongate/proxy 0.37.0 → 0.39.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 CHANGED
@@ -5707,6 +5707,27 @@ var AiJudge = class _AiJudge {
5707
5707
  }
5708
5708
  this.consecutiveFailures = 0;
5709
5709
  }
5710
+ const argStr = JSON.stringify(args).toLowerCase();
5711
+ const hasShellTricks = /\$[\({]|`|<\(|>\(|\beval\b|\bexec\b|\bsource\b|\bxargs\b/.test(argStr);
5712
+ const hasWildcard = /[*?]/.test(argStr);
5713
+ let couldMatchProtected = hasShellTricks || hasWildcard;
5714
+ if (!couldMatchProtected) {
5715
+ const allProtected = [...this.protectedFiles, ...this.protectedPaths];
5716
+ for (const p of allProtected) {
5717
+ const core = p.replace(/[*?[\]{}]/g, "").replace(/^\.+/, "").toLowerCase();
5718
+ if (core && core.length >= 2 && argStr.includes(core)) {
5719
+ couldMatchProtected = true;
5720
+ break;
5721
+ }
5722
+ }
5723
+ }
5724
+ if (!couldMatchProtected) {
5725
+ return {
5726
+ decision: "ALLOW",
5727
+ reason: "No protected file/path referenced in arguments",
5728
+ confidence: 1
5729
+ };
5730
+ }
5710
5731
  const sanitizedArgs = this.sanitizeArgs(args);
5711
5732
  const userMessage = JSON.stringify({
5712
5733
  tool: toolName,
package/dist/lib.js CHANGED
@@ -4006,6 +4006,27 @@ var AiJudge = class _AiJudge {
4006
4006
  }
4007
4007
  this.consecutiveFailures = 0;
4008
4008
  }
4009
+ const argStr = JSON.stringify(args).toLowerCase();
4010
+ const hasShellTricks = /\$[\({]|`|<\(|>\(|\beval\b|\bexec\b|\bsource\b|\bxargs\b/.test(argStr);
4011
+ const hasWildcard = /[*?]/.test(argStr);
4012
+ let couldMatchProtected = hasShellTricks || hasWildcard;
4013
+ if (!couldMatchProtected) {
4014
+ const allProtected = [...this.protectedFiles, ...this.protectedPaths];
4015
+ for (const p of allProtected) {
4016
+ const core = p.replace(/[*?[\]{}]/g, "").replace(/^\.+/, "").toLowerCase();
4017
+ if (core && core.length >= 2 && argStr.includes(core)) {
4018
+ couldMatchProtected = true;
4019
+ break;
4020
+ }
4021
+ }
4022
+ }
4023
+ if (!couldMatchProtected) {
4024
+ return {
4025
+ decision: "ALLOW",
4026
+ reason: "No protected file/path referenced in arguments",
4027
+ confidence: 1
4028
+ };
4029
+ }
4009
4030
  const sanitizedArgs = this.sanitizeArgs(args);
4010
4031
  const userMessage = JSON.stringify({
4011
4032
  tool: toolName,
package/hooks/guard.mjs CHANGED
@@ -1134,7 +1134,9 @@ process.stdin.on('end', async () => {
1134
1134
 
1135
1135
  // ── AI Judge: semantic intent analysis (runs when policy ALLOWs) ──
1136
1136
  if (!reason) {
1137
- const GROQ_KEY = process.env.GROQ_API_KEY || dotenv.GROQ_API_KEY || '';
1137
+ let GROQ_KEY = process.env.GROQ_API_KEY || dotenv.GROQ_API_KEY || '';
1138
+ // Skip placeholder values
1139
+ if (GROQ_KEY && (GROQ_KEY.includes('your_') || GROQ_KEY.includes('_here') || GROQ_KEY.length < 10)) GROQ_KEY = '';
1138
1140
  let aiJudgeEnabled = false;
1139
1141
  let aiJudgeModel = 'llama-3.1-8b-instant';
1140
1142
  let aiJudgeEndpoint = 'https://api.groq.com/openai';
@@ -1192,45 +1194,63 @@ process.stdin.on('end', async () => {
1192
1194
  }
1193
1195
  }
1194
1196
 
1197
+ // Pre-filter: skip AI Judge if tool args clearly don't touch any protected file/path.
1198
+ // Only call LLM when there's a potential match or obfuscation attempt.
1199
+ const argStr = JSON.stringify(args).toLowerCase();
1200
+ const hasShellTricks = /\$[\({]|`|<\(|>\(|\beval\b|\bexec\b|\bsource\b|\bxargs\b/.test(argStr);
1201
+ const hasWildcard = /[*?]/.test(argStr);
1202
+ let couldMatchProtected = hasShellTricks || hasWildcard;
1203
+ if (!couldMatchProtected) {
1204
+ // Check if any protected file/path name (without glob chars) appears in args
1205
+ const allProtected = [...protectedFiles, ...protectedPathsList];
1206
+ for (const p of allProtected) {
1207
+ // Strip glob chars to get the core name: "*.env*" → "env", ".solongate" → "solongate"
1208
+ const core = p.replace(/[*?[\]{}]/g, '').replace(/^\.+/, '').toLowerCase();
1209
+ if (core && core.length >= 2 && argStr.includes(core)) {
1210
+ couldMatchProtected = true;
1211
+ break;
1212
+ }
1213
+ }
1214
+ }
1215
+
1216
+ // If args clearly don't reference any protected pattern, skip LLM — instant ALLOW
1217
+ if (!couldMatchProtected) {
1218
+ // No need to call AI Judge — nothing to protect here
1219
+ } else {
1220
+
1195
1221
  const judgePayload = JSON.stringify({
1196
1222
  tool: toolName,
1197
1223
  arguments: args,
1198
1224
  protected_files: protectedFiles,
1199
1225
  protected_paths: protectedPathsList,
1200
- denied_actions: ['file deletion', 'data exfiltration', 'remote code execution', 'environment variable leak', 'security control bypass'],
1201
1226
  });
1202
1227
 
1203
- const systemPrompt = `You are a security judge for an AI coding tool. You evaluate tool calls and decide if they should be ALLOWED or DENIED.
1228
+ const systemPrompt = `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.
1204
1229
 
1205
1230
  You will receive a JSON object with:
1206
- - "tool": the tool name being called (e.g., Bash, Write, Read, Edit)
1231
+ - "tool": the tool name being called
1207
1232
  - "arguments": the tool's arguments
1208
- - "protected_files": EXACT list of files that must NEVER be accessed. ONLY these specific files are protected nothing else.
1209
- - "protected_paths": EXACT list of directories that must NEVER be accessed. ONLY these specific paths are protected — nothing else.
1210
- - "denied_actions": list of actions that are never allowed
1211
-
1212
- 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.
1213
-
1214
- DENY if the tool call could, directly or indirectly, access a file from the protected_files list — even through:
1215
- - Shell glob patterns (e.g., "cred*" could match "credentials.json" IF credentials.json is in protected_files)
1216
- - Command substitution ($(...), backticks)
1217
- - Process substitution (<(cat file)) check inside <(...) for protected files
1218
- - Variable interpolation (e.g., f=".en"; cat \${f}v builds ".env" — DENY only if .env is in protected_files)
1219
- - Input redirection (< file)
1220
- - Multi-stage operations: tar/cp a protected file then read the copy DENY the entire chain
1221
- - Any utility that reads file content (cat, head, tail, less, perl, awk, sed, xxd, od, strings, dd, etc.)
1222
-
1223
- Also DENY if:
1224
- - The command sends data to external URLs (curl -d, wget --post)
1225
- - The command leaks environment variables (printenv, env, process.env)
1226
- - The command executes remotely downloaded code (curl|bash)
1227
-
1228
- ALLOW if:
1229
- - The file is NOT in protected_files — even if cat, head, etc. is used. Reading non-protected files is normal.
1230
- - The action is a normal development operation (ls, git status, npm build, cat app.js, etc.)
1231
- - The action does not touch any protected file or path
1232
-
1233
- 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. "cat package.json" is ALLOWED if package.json is not in protected_files. Do NOT over-block.
1233
+ - "protected_files": the EXACT and COMPLETE list of protected files from the user's policy
1234
+ - "protected_paths": the EXACT and COMPLETE list of protected directories from the user's policy
1235
+
1236
+ RULES:
1237
+ 1. DENY ONLY if the tool call could access a file or path that is in protected_files or protected_paths.
1238
+ 2. ALLOW everything else. You must NOT invent your own security rules.
1239
+ 3. If a file is NOT in protected_files, it is NOT protected even if the filename looks sensitive.
1240
+ 4. "cat test.txt" is ALLOWED if test.txt is not in protected_files.
1241
+ 5. "curl https://example.com" is ALLOWED unless it sends protected file content.
1242
+ 6. "printenv" is ALLOWED unless the policy explicitly protects it.
1243
+
1244
+ BYPASS DETECTION DENY if the command accesses a protected file through:
1245
+ - Shell glob patterns: "cat cred*" could match "credentials.json" IF it is in protected_files
1246
+ - Command substitution: "cat $(echo .env)" builds ".env"
1247
+ - Variable interpolation: f=".en"; cat \${f}v builds ".env"
1248
+ - Process substitution: <(cat .env)
1249
+ - Multi-stage: cp protected_file /tmp/x && cat /tmp/x
1250
+ - Input redirection: < .env
1251
+ - Any file-reading utility (cat, head, tail, less, perl, awk, sed, xxd, etc.)
1252
+
1253
+ 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.
1234
1254
 
1235
1255
  Respond with ONLY valid JSON: {"decision": "ALLOW" or "DENY", "reason": "brief explanation", "confidence": 0.0 to 1.0}`;
1236
1256
 
@@ -1258,17 +1278,18 @@ Respond with ONLY valid JSON: {"decision": "ALLOW" or "DENY", "reason": "brief e
1258
1278
  const jsonMatch = content.match(/\{[\s\S]*\}/);
1259
1279
  if (jsonMatch) {
1260
1280
  const verdict = JSON.parse(jsonMatch[0]);
1261
- if (verdict.decision === 'DENY') {
1281
+ if (verdict.decision === 'DENY' && verdict.confidence >= 0.7) {
1262
1282
  reason = '[SolonGate AI Judge] Blocked: ' + (verdict.reason || 'Semantic analysis detected a policy violation');
1263
1283
  }
1284
+ // Low-confidence DENY or ALLOW → skip (don't block)
1264
1285
  }
1265
- } else {
1266
- // Fail-closed: LLM error → DENY
1267
- reason = '[SolonGate AI Judge] Blocked: Groq API error (fail-closed)';
1268
1286
  }
1269
- } catch (err) {
1270
- // Fail-closed: timeout or parse error DENY
1271
- reason = '[SolonGate AI Judge] Blocked: ' + (err.message || 'error') + ' (fail-closed)';
1287
+ // Auth/config errors (401, 403) → skip AI Judge, don't DENY
1288
+ // Other LLM errors skip too (policy engine already evaluated)
1289
+
1290
+ } // end else (couldMatchProtected)
1291
+ } catch {
1292
+ // Timeout or parse error → skip AI Judge (policy engine already passed)
1272
1293
  }
1273
1294
  }
1274
1295
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.37.0",
3
+ "version": "0.39.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": {