@solongate/proxy 0.17.3 → 0.19.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/hooks/guard.mjs +81 -42
- package/package.json +1 -1
package/hooks/guard.mjs
CHANGED
|
@@ -452,61 +452,49 @@ process.stdin.on('end', async () => {
|
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
-
// ── Layer 7:
|
|
456
|
-
//
|
|
455
|
+
// ── Layer 7: Block ALL inline code execution & dangerous patterns ──
|
|
456
|
+
// Runtime string construction (atob, Buffer.from, fromCharCode, array.join)
|
|
457
|
+
// makes static analysis impossible. Blanket-block these patterns.
|
|
457
458
|
const fullCmd = rawStrings.join(' ');
|
|
458
459
|
|
|
459
|
-
// 7a. Inline interpreter execution
|
|
460
|
-
//
|
|
461
|
-
const
|
|
462
|
-
/\bnode\s+(?:-e|--eval)\
|
|
463
|
-
/\bnode\s
|
|
464
|
-
/\bpython[23]?\s+-c\
|
|
465
|
-
/\bperl\s+-e\
|
|
466
|
-
/\bruby\s+-e\
|
|
460
|
+
// 7a. Inline interpreter execution — TOTAL BLOCK (no content scan needed)
|
|
461
|
+
// These can construct ANY string at runtime, bypassing all static analysis
|
|
462
|
+
const blockedInterpreters = [
|
|
463
|
+
[/\bnode\s+(?:-e|--eval)\b/i, 'node -e/--eval'],
|
|
464
|
+
[/\bnode\s+-p\b/i, 'node -p'],
|
|
465
|
+
[/\bpython[23]?\s+-c\b/i, 'python -c'],
|
|
466
|
+
[/\bperl\s+-e\b/i, 'perl -e'],
|
|
467
|
+
[/\bruby\s+-e\b/i, 'ruby -e'],
|
|
468
|
+
[/\bpowershell(?:\.exe)?\s+.*-(?:EncodedCommand|e|ec)\b/i, 'powershell -EncodedCommand'],
|
|
469
|
+
[/\bpwsh(?:\.exe)?\s+.*-(?:EncodedCommand|e|ec)\b/i, 'pwsh -EncodedCommand'],
|
|
470
|
+
[/\bpowershell(?:\.exe)?\s+-c(?:ommand)?\b/i, 'powershell -Command'],
|
|
471
|
+
[/\bpwsh(?:\.exe)?\s+-c(?:ommand)?\b/i, 'pwsh -Command'],
|
|
467
472
|
];
|
|
468
|
-
for (const pat of
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
for (const p of protectedPaths) {
|
|
472
|
-
if (code.includes(p)) {
|
|
473
|
-
await blockSelfProtection('SOLONGATE: Interpreter code targets "' + p + '" — blocked');
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Also check the normalized version
|
|
477
|
-
const normCode = normalizeShell(code);
|
|
478
|
-
for (const p of protectedPaths) {
|
|
479
|
-
if (normCode.includes(p)) {
|
|
480
|
-
await blockSelfProtection('SOLONGATE: Interpreter code targets "' + p + '" — blocked');
|
|
481
|
-
}
|
|
482
|
-
}
|
|
473
|
+
for (const [pat, name] of blockedInterpreters) {
|
|
474
|
+
if (pat.test(fullCmd)) {
|
|
475
|
+
await blockSelfProtection('SOLONGATE: Inline code execution blocked (' + name + ')');
|
|
483
476
|
}
|
|
484
477
|
}
|
|
485
478
|
|
|
486
|
-
// 7b.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
479
|
+
// 7b. Pipe-to-interpreter — TOTAL BLOCK
|
|
480
|
+
// Any content piped to an interpreter can construct arbitrary commands at runtime
|
|
481
|
+
const pipeToInterpreter = /\|\s*(?:node|bash|sh|python[23]?|perl|ruby|php)\b/i;
|
|
482
|
+
if (pipeToInterpreter.test(fullCmd)) {
|
|
483
|
+
await blockSelfProtection('SOLONGATE: Pipe to interpreter blocked — runtime bypass risk');
|
|
490
484
|
}
|
|
491
485
|
|
|
492
|
-
// 7c.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
await blockSelfProtection('SOLONGATE: Temp script execution detected — blocked');
|
|
486
|
+
// 7c. Base64 decode in ANY context — block when piped to anything
|
|
487
|
+
if (/\bbase64\s+(?:-d|--decode)\b/i.test(fullCmd) && /\|/i.test(fullCmd)) {
|
|
488
|
+
await blockSelfProtection('SOLONGATE: base64 decode in pipe chain — blocked');
|
|
496
489
|
}
|
|
497
490
|
|
|
498
|
-
// 7d.
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
const prefix = p.slice(0, 4); // e.g. ".sol", ".cla"
|
|
502
|
-
if (fullCmd.includes(prefix)) {
|
|
503
|
-
await blockSelfProtection('SOLONGATE: Process substitution near protected path "' + p + '" — blocked');
|
|
504
|
-
}
|
|
505
|
-
}
|
|
491
|
+
// 7d. Temp/arbitrary script file execution
|
|
492
|
+
if (/\b(?:bash|sh)\s+(?:\/tmp\/|\/var\/tmp\/|~\/|\/dev\/)/i.test(fullCmd)) {
|
|
493
|
+
await blockSelfProtection('SOLONGATE: Script execution from temp path — blocked');
|
|
506
494
|
}
|
|
507
495
|
|
|
508
496
|
// 7e. xargs with destructive operations
|
|
509
|
-
if (/\bxargs\b.*\b(?:rm|mv|cp|rmdir|unlink)\b/i.test(fullCmd)) {
|
|
497
|
+
if (/\bxargs\b.*\b(?:rm|mv|cp|rmdir|unlink|del)\b/i.test(fullCmd)) {
|
|
510
498
|
for (const p of protectedPaths) {
|
|
511
499
|
if (fullCmd.includes(p.slice(0, 4))) {
|
|
512
500
|
await blockSelfProtection('SOLONGATE: xargs with destructive op near "' + p + '" — blocked');
|
|
@@ -514,6 +502,57 @@ process.stdin.on('end', async () => {
|
|
|
514
502
|
}
|
|
515
503
|
}
|
|
516
504
|
|
|
505
|
+
// 7f. cmd.exe /c with encoded/constructed commands
|
|
506
|
+
if (/\bcmd(?:\.exe)?\s+\/c\b/i.test(fullCmd)) {
|
|
507
|
+
for (const p of protectedPaths) {
|
|
508
|
+
if (fullCmd.includes(p) || fullCmd.includes(p.slice(0, 4))) {
|
|
509
|
+
await blockSelfProtection('SOLONGATE: cmd.exe /c near protected path — blocked');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// 7g. Script file execution — scan file content for discovery+destruction combo
|
|
515
|
+
// Catches: bash script.sh / node script.mjs where the script uses readdirSync + rmSync
|
|
516
|
+
const scriptExecMatch = fullCmd.match(/\b(?:bash|sh|node|python[23]?|perl|ruby)\s+([^\s;&|]+)/i);
|
|
517
|
+
if (scriptExecMatch) {
|
|
518
|
+
const scriptPath = scriptExecMatch[1];
|
|
519
|
+
try {
|
|
520
|
+
const hookCwdForScript = data.cwd || process.cwd();
|
|
521
|
+
const absPath = scriptPath.startsWith('/') || scriptPath.includes(':')
|
|
522
|
+
? scriptPath
|
|
523
|
+
: resolve(hookCwdForScript, scriptPath);
|
|
524
|
+
if (existsSync(absPath)) {
|
|
525
|
+
const scriptContent = readFileSync(absPath, 'utf-8').toLowerCase();
|
|
526
|
+
// Check for discovery+destruction combo
|
|
527
|
+
const hasDiscovery = /\breaddirsync\b|\breaddir\b|\bos\.listdir\b|\bscandir\b|\bglob(?:sync)?\b|\bls\s+-[adl]|\bls\s+\.\b|\bopendir\b|\bdir\.entries\b|\bwalkdir\b|\bls\b.*\.\[/.test(scriptContent);
|
|
528
|
+
const hasDestruction = /\brmsync\b|\brm\s+-rf\b|\bunlinksync\b|\brmdirsync\b|\bunlink\s*\(|\brimraf\b|\bremovesync\b|\bremove_tree\b|\bshutil\.rmtree\b|\bwritefilesync\b|\bexecsync\b.*\brm\b|\bchild_process\b|\bfs\.\s*(?:rm|unlink|rmdir|write)/.test(scriptContent);
|
|
529
|
+
if (hasDiscovery && hasDestruction) {
|
|
530
|
+
await blockSelfProtection('SOLONGATE: Script contains directory discovery + destructive ops — blocked');
|
|
531
|
+
}
|
|
532
|
+
// Also check for protected path names in script content (existing check, now centralized)
|
|
533
|
+
for (const p of protectedPaths) {
|
|
534
|
+
if (scriptContent.includes(p)) {
|
|
535
|
+
await blockSelfProtection('SOLONGATE: Script references protected path "' + p + '" — blocked');
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
} catch {} // File read error — skip
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 7h. Write tool content scanning — detect discovery+destruction in file content being written
|
|
543
|
+
// Catches: Write tool creating a script that uses readdirSync('.') + rmSync
|
|
544
|
+
const toolName_ = data.tool_name || '';
|
|
545
|
+
if (toolName_.toLowerCase() === 'write' || toolName_.toLowerCase() === 'edit') {
|
|
546
|
+
const fileContent = (args.content || args.new_string || '').toLowerCase();
|
|
547
|
+
if (fileContent.length > 0) {
|
|
548
|
+
const hasDiscovery = /\breaddirsync\b|\breaddir\b|\bos\.listdir\b|\bscandir\b|\bglob(?:sync)?\b|\bls\s+-[adl]|\bls\s+\.\b|\bopendir\b|\bdir\.entries\b|\bwalkdir\b|\bls\b.*\.\[/.test(fileContent);
|
|
549
|
+
const hasDestruction = /\brmsync\b|\brm\s+-rf\b|\bunlinksync\b|\brmdirsync\b|\bunlink\b|\brimraf\b|\bremovesync\b|\bremove_tree\b|\bshutil\.rmtree\b|\bwritefilesync\b|\bexecsync\b.*\brm\b|\bchild_process\b.*\brm\b|\bfs\.\s*(?:rm|unlink|rmdir)/.test(fileContent);
|
|
550
|
+
if (hasDiscovery && hasDestruction) {
|
|
551
|
+
await blockSelfProtection('SOLONGATE: File content contains discovery + destructive ops — write blocked');
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
517
556
|
// ── Fetch PI config from Cloud ──
|
|
518
557
|
let piCfg = { piEnabled: true, piThreshold: 0.5, piMode: 'block', piWhitelist: [], piToolConfig: {}, piCustomPatterns: [], piWebhookUrl: null };
|
|
519
558
|
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
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": {
|