@solongate/proxy 0.15.3 → 0.15.4
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/hooks/guard.mjs +67 -1
- package/package.json +1 -1
package/hooks/guard.mjs
CHANGED
|
@@ -290,12 +290,57 @@ process.stdin.on('end', async () => {
|
|
|
290
290
|
const args = data.tool_input || {};
|
|
291
291
|
|
|
292
292
|
// ── Self-protection: block access to hook files and settings ──
|
|
293
|
-
|
|
293
|
+
// Hardcoded, no bypass possible — runs before policy/PI config
|
|
294
294
|
const protectedPaths = [
|
|
295
295
|
'.solongate', '.claude', '.cursor', '.gemini', '.antigravity', '.openclaw', '.perplexity',
|
|
296
296
|
'policy.json', '.mcp.json',
|
|
297
297
|
];
|
|
298
|
+
|
|
299
|
+
// Strip shell quotes/escapes: .sol'on'gate → .solongate, .sol"on"gate → .solongate
|
|
300
|
+
function stripShellQuotes(s) {
|
|
301
|
+
return s.replace(/\\(.)/g, '$1').replace(/'/g, '').replace(/"/g, '');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Check if a glob/wildcard pattern could match any protected path
|
|
305
|
+
// e.g. ".antig*" matches ".antigravity", "/path/.sol*" matches ".solongate"
|
|
306
|
+
function globMatchesProtected(s) {
|
|
307
|
+
if (!s.includes('*') && !s.includes('?')) return null;
|
|
308
|
+
// Extract all path segments and the full string to test
|
|
309
|
+
const segments = s.split('/').filter(Boolean);
|
|
310
|
+
const candidates = [s, ...segments];
|
|
311
|
+
for (const candidate of candidates) {
|
|
312
|
+
if (!candidate.includes('*') && !candidate.includes('?')) continue;
|
|
313
|
+
for (const p of protectedPaths) {
|
|
314
|
+
// Build regex from glob: * → .*, ? → .
|
|
315
|
+
const escaped = candidate.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
316
|
+
try {
|
|
317
|
+
if (new RegExp('^' + escaped + '$', 'i').test(p)) return p;
|
|
318
|
+
} catch { /* invalid regex, skip */ }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Normalize: lowercase, forward slashes, strip shell quotes
|
|
325
|
+
const rawStrings = scanStrings(args).map(s => s.replace(/\\/g, '/').toLowerCase());
|
|
326
|
+
const allStrings = [];
|
|
327
|
+
for (const s of rawStrings) {
|
|
328
|
+
allStrings.push(s);
|
|
329
|
+
// Also add quote-stripped version
|
|
330
|
+
const stripped = stripShellQuotes(s);
|
|
331
|
+
if (stripped !== s) allStrings.push(stripped);
|
|
332
|
+
// Split by spaces (for commands like "rm -rf .sol'on'gate .cl*")
|
|
333
|
+
for (const tok of s.split(/\s+/)) {
|
|
334
|
+
if (tok !== s) {
|
|
335
|
+
allStrings.push(tok);
|
|
336
|
+
const strippedTok = stripShellQuotes(tok);
|
|
337
|
+
if (strippedTok !== tok) allStrings.push(strippedTok);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
298
342
|
for (const s of allStrings) {
|
|
343
|
+
// Direct match
|
|
299
344
|
for (const p of protectedPaths) {
|
|
300
345
|
if (s.includes(p)) {
|
|
301
346
|
const msg = 'SOLONGATE: Access to protected file "' + p + '" is blocked';
|
|
@@ -317,6 +362,27 @@ process.stdin.on('end', async () => {
|
|
|
317
362
|
process.exit(2);
|
|
318
363
|
}
|
|
319
364
|
}
|
|
365
|
+
// Wildcard/glob match
|
|
366
|
+
const globHit = globMatchesProtected(s);
|
|
367
|
+
if (globHit) {
|
|
368
|
+
const msg = 'SOLONGATE: Wildcard pattern "' + s + '" matches protected path "' + globHit + '" — blocked';
|
|
369
|
+
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
370
|
+
try {
|
|
371
|
+
await fetch(API_URL + '/api/v1/audit-logs', {
|
|
372
|
+
method: 'POST',
|
|
373
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
374
|
+
body: JSON.stringify({
|
|
375
|
+
tool: data.tool_name || '', arguments: args,
|
|
376
|
+
decision: 'DENY', reason: msg,
|
|
377
|
+
source: 'claude-code-guard',
|
|
378
|
+
}),
|
|
379
|
+
signal: AbortSignal.timeout(3000),
|
|
380
|
+
});
|
|
381
|
+
} catch {}
|
|
382
|
+
}
|
|
383
|
+
process.stderr.write(msg);
|
|
384
|
+
process.exit(2);
|
|
385
|
+
}
|
|
320
386
|
}
|
|
321
387
|
|
|
322
388
|
// ── Fetch PI config from Cloud ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.4",
|
|
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": {
|