@solongate/proxy 0.15.3 → 0.15.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/hooks/guard.mjs +78 -1
- package/package.json +1 -1
package/hooks/guard.mjs
CHANGED
|
@@ -290,12 +290,68 @@ 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
|
+
// Simple prefix match: ".antig*" → prefix ".antig", check if any protected path starts with it
|
|
314
|
+
const starIdx = candidate.indexOf('*');
|
|
315
|
+
const qIdx = candidate.indexOf('?');
|
|
316
|
+
const firstWild = starIdx === -1 ? qIdx : qIdx === -1 ? starIdx : Math.min(starIdx, qIdx);
|
|
317
|
+
const prefix = candidate.slice(0, firstWild).toLowerCase();
|
|
318
|
+
if (prefix.length > 0) {
|
|
319
|
+
for (const p of protectedPaths) {
|
|
320
|
+
if (p.startsWith(prefix)) return p;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Also try regex for complex patterns like ".cl?ude"
|
|
324
|
+
try {
|
|
325
|
+
const escaped = candidate.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
326
|
+
const re = new RegExp('^' + escaped + '$', 'i');
|
|
327
|
+
for (const p of protectedPaths) {
|
|
328
|
+
if (re.test(p)) return p;
|
|
329
|
+
}
|
|
330
|
+
} catch { /* invalid regex, skip */ }
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Normalize: lowercase, forward slashes, strip shell quotes
|
|
336
|
+
const rawStrings = scanStrings(args).map(s => s.replace(/\\/g, '/').toLowerCase());
|
|
337
|
+
const allStrings = [];
|
|
338
|
+
for (const s of rawStrings) {
|
|
339
|
+
allStrings.push(s);
|
|
340
|
+
// Also add quote-stripped version
|
|
341
|
+
const stripped = stripShellQuotes(s);
|
|
342
|
+
if (stripped !== s) allStrings.push(stripped);
|
|
343
|
+
// Split by spaces (for commands like "rm -rf .sol'on'gate .cl*")
|
|
344
|
+
for (const tok of s.split(/\s+/)) {
|
|
345
|
+
if (tok !== s) {
|
|
346
|
+
allStrings.push(tok);
|
|
347
|
+
const strippedTok = stripShellQuotes(tok);
|
|
348
|
+
if (strippedTok !== tok) allStrings.push(strippedTok);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
298
353
|
for (const s of allStrings) {
|
|
354
|
+
// Direct match
|
|
299
355
|
for (const p of protectedPaths) {
|
|
300
356
|
if (s.includes(p)) {
|
|
301
357
|
const msg = 'SOLONGATE: Access to protected file "' + p + '" is blocked';
|
|
@@ -317,6 +373,27 @@ process.stdin.on('end', async () => {
|
|
|
317
373
|
process.exit(2);
|
|
318
374
|
}
|
|
319
375
|
}
|
|
376
|
+
// Wildcard/glob match
|
|
377
|
+
const globHit = globMatchesProtected(s);
|
|
378
|
+
if (globHit) {
|
|
379
|
+
const msg = 'SOLONGATE: Wildcard pattern "' + s + '" matches protected path "' + globHit + '" — blocked';
|
|
380
|
+
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
381
|
+
try {
|
|
382
|
+
await fetch(API_URL + '/api/v1/audit-logs', {
|
|
383
|
+
method: 'POST',
|
|
384
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
385
|
+
body: JSON.stringify({
|
|
386
|
+
tool: data.tool_name || '', arguments: args,
|
|
387
|
+
decision: 'DENY', reason: msg,
|
|
388
|
+
source: 'claude-code-guard',
|
|
389
|
+
}),
|
|
390
|
+
signal: AbortSignal.timeout(3000),
|
|
391
|
+
});
|
|
392
|
+
} catch {}
|
|
393
|
+
}
|
|
394
|
+
process.stderr.write(msg);
|
|
395
|
+
process.exit(2);
|
|
396
|
+
}
|
|
320
397
|
}
|
|
321
398
|
|
|
322
399
|
// ── 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.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": {
|