@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.
Files changed (2) hide show
  1. package/hooks/guard.mjs +78 -1
  2. 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
- const allStrings = scanStrings(args).map(s => s.replace(/\\/g, '/').toLowerCase());
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",
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": {