@solongate/proxy 0.16.0 → 0.17.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 +58 -4
- package/dist/init.js +54 -0
- package/hooks/guard.mjs +62 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -306,6 +306,7 @@ var init_exports = {};
|
|
|
306
306
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
307
307
|
import { resolve as resolve2, join, dirname as dirname2 } from "path";
|
|
308
308
|
import { fileURLToPath } from "url";
|
|
309
|
+
import { execSync } from "child_process";
|
|
309
310
|
import { createInterface } from "readline";
|
|
310
311
|
function findConfigFile(explicitPath, createIfMissing = false) {
|
|
311
312
|
if (explicitPath) {
|
|
@@ -461,7 +462,28 @@ EXAMPLES
|
|
|
461
462
|
function readHookScript(filename) {
|
|
462
463
|
return readFileSync3(join(HOOKS_DIR, filename), "utf-8");
|
|
463
464
|
}
|
|
465
|
+
function unlockProtectedDirs() {
|
|
466
|
+
const dirs = [".solongate", ".claude", ".cursor", ".gemini", ".antigravity", ".openclaw", ".perplexity"];
|
|
467
|
+
for (const dir of dirs) {
|
|
468
|
+
const fullDir = resolve2(dir);
|
|
469
|
+
if (!existsSync3(fullDir)) continue;
|
|
470
|
+
try {
|
|
471
|
+
if (process.platform === "win32") {
|
|
472
|
+
execSync(`icacls "${fullDir}" /remove:d *S-1-1-0 /T /Q`, { stdio: "ignore" });
|
|
473
|
+
execSync(`attrib -R /S /D "${fullDir}"`, { stdio: "ignore" });
|
|
474
|
+
} else {
|
|
475
|
+
try {
|
|
476
|
+
execSync(`chattr -i -R "${fullDir}"`, { stdio: "ignore" });
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
execSync(`chmod -R u+w "${fullDir}"`, { stdio: "ignore" });
|
|
480
|
+
}
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
464
485
|
function installHooks(selectedTools = []) {
|
|
486
|
+
unlockProtectedDirs();
|
|
465
487
|
const hooksDir = resolve2(".solongate", "hooks");
|
|
466
488
|
mkdirSync2(hooksDir, { recursive: true });
|
|
467
489
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
@@ -514,10 +536,42 @@ function installHooks(selectedTools = []) {
|
|
|
514
536
|
console.log(` Created ${settingsPath}`);
|
|
515
537
|
activatedNames.push(client.name);
|
|
516
538
|
}
|
|
539
|
+
const protectedDirs = [".solongate", ...clients.map((c3) => c3.dir)];
|
|
540
|
+
try {
|
|
541
|
+
if (process.platform === "win32") {
|
|
542
|
+
for (const dir of protectedDirs) {
|
|
543
|
+
const fullDir = resolve2(dir);
|
|
544
|
+
if (existsSync3(fullDir)) {
|
|
545
|
+
try {
|
|
546
|
+
execSync(`icacls "${fullDir}" /deny *S-1-1-0:(OI)(CI)(W,D) /T /Q`, { stdio: "ignore" });
|
|
547
|
+
execSync(`attrib +R /S /D "${fullDir}"`, { stdio: "ignore" });
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
for (const dir of protectedDirs) {
|
|
554
|
+
const fullDir = resolve2(dir);
|
|
555
|
+
if (existsSync3(fullDir)) {
|
|
556
|
+
try {
|
|
557
|
+
execSync(`chmod -R a-w "${fullDir}"`, { stdio: "ignore" });
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
execSync(`chattr +i -R "${fullDir}"`, { stdio: "ignore" });
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
console.log(" OS-level DENY protection applied (icacls/chmod)");
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
517
570
|
console.log("");
|
|
518
571
|
console.log(" Hooks installed:");
|
|
519
572
|
console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
|
|
520
573
|
console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
|
|
574
|
+
console.log(" File system \u2192 read-only (OS-level protection)");
|
|
521
575
|
console.log(` Activated for: ${activatedNames.join(", ")}`);
|
|
522
576
|
}
|
|
523
577
|
function ensureEnvFile() {
|
|
@@ -804,7 +858,7 @@ var init_init = __esm({
|
|
|
804
858
|
var inject_exports = {};
|
|
805
859
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, copyFileSync } from "fs";
|
|
806
860
|
import { resolve as resolve3 } from "path";
|
|
807
|
-
import { execSync } from "child_process";
|
|
861
|
+
import { execSync as execSync2 } from "child_process";
|
|
808
862
|
function parseInjectArgs(argv) {
|
|
809
863
|
const args = argv.slice(2);
|
|
810
864
|
const opts = {
|
|
@@ -951,7 +1005,7 @@ function installSdk() {
|
|
|
951
1005
|
const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
|
|
952
1006
|
log3(` Installing @solongate/sdk via ${pm}...`);
|
|
953
1007
|
try {
|
|
954
|
-
|
|
1008
|
+
execSync2(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
955
1009
|
return true;
|
|
956
1010
|
} catch (err) {
|
|
957
1011
|
log3(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1176,7 +1230,7 @@ var init_inject = __esm({
|
|
|
1176
1230
|
var create_exports = {};
|
|
1177
1231
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
1178
1232
|
import { resolve as resolve4, join as join2 } from "path";
|
|
1179
|
-
import { execSync as
|
|
1233
|
+
import { execSync as execSync3 } from "child_process";
|
|
1180
1234
|
function log4(msg) {
|
|
1181
1235
|
process.stderr.write(msg + "\n");
|
|
1182
1236
|
}
|
|
@@ -1420,7 +1474,7 @@ async function main3() {
|
|
|
1420
1474
|
});
|
|
1421
1475
|
if (!opts.noInstall) {
|
|
1422
1476
|
withSpinner("Installing dependencies...", () => {
|
|
1423
|
-
|
|
1477
|
+
execSync3("npm install", { cwd: dir, stdio: "pipe" });
|
|
1424
1478
|
});
|
|
1425
1479
|
}
|
|
1426
1480
|
log4("");
|
package/dist/init.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
5
5
|
import { resolve, join, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { execSync } from "child_process";
|
|
7
8
|
import { createInterface } from "readline";
|
|
8
9
|
var SEARCH_PATHS = [
|
|
9
10
|
".mcp.json",
|
|
@@ -168,7 +169,28 @@ var HOOKS_DIR = resolve(__dirname, "..", "hooks");
|
|
|
168
169
|
function readHookScript(filename) {
|
|
169
170
|
return readFileSync(join(HOOKS_DIR, filename), "utf-8");
|
|
170
171
|
}
|
|
172
|
+
function unlockProtectedDirs() {
|
|
173
|
+
const dirs = [".solongate", ".claude", ".cursor", ".gemini", ".antigravity", ".openclaw", ".perplexity"];
|
|
174
|
+
for (const dir of dirs) {
|
|
175
|
+
const fullDir = resolve(dir);
|
|
176
|
+
if (!existsSync(fullDir)) continue;
|
|
177
|
+
try {
|
|
178
|
+
if (process.platform === "win32") {
|
|
179
|
+
execSync(`icacls "${fullDir}" /remove:d *S-1-1-0 /T /Q`, { stdio: "ignore" });
|
|
180
|
+
execSync(`attrib -R /S /D "${fullDir}"`, { stdio: "ignore" });
|
|
181
|
+
} else {
|
|
182
|
+
try {
|
|
183
|
+
execSync(`chattr -i -R "${fullDir}"`, { stdio: "ignore" });
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
execSync(`chmod -R u+w "${fullDir}"`, { stdio: "ignore" });
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
171
192
|
function installHooks(selectedTools = []) {
|
|
193
|
+
unlockProtectedDirs();
|
|
172
194
|
const hooksDir = resolve(".solongate", "hooks");
|
|
173
195
|
mkdirSync(hooksDir, { recursive: true });
|
|
174
196
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
@@ -221,10 +243,42 @@ function installHooks(selectedTools = []) {
|
|
|
221
243
|
console.log(` Created ${settingsPath}`);
|
|
222
244
|
activatedNames.push(client.name);
|
|
223
245
|
}
|
|
246
|
+
const protectedDirs = [".solongate", ...clients.map((c) => c.dir)];
|
|
247
|
+
try {
|
|
248
|
+
if (process.platform === "win32") {
|
|
249
|
+
for (const dir of protectedDirs) {
|
|
250
|
+
const fullDir = resolve(dir);
|
|
251
|
+
if (existsSync(fullDir)) {
|
|
252
|
+
try {
|
|
253
|
+
execSync(`icacls "${fullDir}" /deny *S-1-1-0:(OI)(CI)(W,D) /T /Q`, { stdio: "ignore" });
|
|
254
|
+
execSync(`attrib +R /S /D "${fullDir}"`, { stdio: "ignore" });
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
for (const dir of protectedDirs) {
|
|
261
|
+
const fullDir = resolve(dir);
|
|
262
|
+
if (existsSync(fullDir)) {
|
|
263
|
+
try {
|
|
264
|
+
execSync(`chmod -R a-w "${fullDir}"`, { stdio: "ignore" });
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
execSync(`chattr +i -R "${fullDir}"`, { stdio: "ignore" });
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
console.log(" OS-level DENY protection applied (icacls/chmod)");
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
224
277
|
console.log("");
|
|
225
278
|
console.log(" Hooks installed:");
|
|
226
279
|
console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
|
|
227
280
|
console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
|
|
281
|
+
console.log(" File system \u2192 read-only (OS-level protection)");
|
|
228
282
|
console.log(` Activated for: ${activatedNames.join(", ")}`);
|
|
229
283
|
}
|
|
230
284
|
function ensureEnvFile() {
|
package/hooks/guard.mjs
CHANGED
|
@@ -452,6 +452,68 @@ process.stdin.on('end', async () => {
|
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
// ── Layer 7: Dangerous execution pattern detection ──
|
|
456
|
+
// These can construct ANY string at runtime — block when touching protected dirs
|
|
457
|
+
const fullCmd = rawStrings.join(' ');
|
|
458
|
+
|
|
459
|
+
// 7a. Inline interpreter execution: node -e, python -c, perl -e, ruby -e
|
|
460
|
+
// Extract the -e/-c argument and scan it
|
|
461
|
+
const interpreterPatterns = [
|
|
462
|
+
/\bnode\s+(?:-e|--eval)\s+["']([^"']+)["']/gi,
|
|
463
|
+
/\bnode\s+(?:-e|--eval)\s+([^;&|"']+)/gi,
|
|
464
|
+
/\bpython[23]?\s+-c\s+["']([^"']+)["']/gi,
|
|
465
|
+
/\bperl\s+-e\s+["']([^"']+)["']/gi,
|
|
466
|
+
/\bruby\s+-e\s+["']([^"']+)["']/gi,
|
|
467
|
+
];
|
|
468
|
+
for (const pat of interpreterPatterns) {
|
|
469
|
+
for (const m of fullCmd.matchAll(pat)) {
|
|
470
|
+
const code = m[1].toLowerCase();
|
|
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
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// 7b. Base64 decode piped to execution — always block
|
|
487
|
+
if (/\bbase64\s+-d\b.*\|\s*(?:bash|sh|node|python|perl|ruby)\b/i.test(fullCmd) ||
|
|
488
|
+
/\bbase64\s+--decode\b.*\|\s*(?:bash|sh|node|python|perl|ruby)\b/i.test(fullCmd)) {
|
|
489
|
+
await blockSelfProtection('SOLONGATE: base64 decode piped to interpreter — blocked');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 7c. Temp script file execution: bash /path/file, sh /path/file
|
|
493
|
+
// If "bash <file>" or "sh <file>" and the file is not a well-known script
|
|
494
|
+
if (/\b(?:bash|sh)\s+(?:\/tmp\/|\/var\/tmp\/|~\/\.|\.\/[^.s])/i.test(fullCmd)) {
|
|
495
|
+
await blockSelfProtection('SOLONGATE: Temp script execution detected — blocked');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// 7d. Process substitution and here-strings that could construct protected paths
|
|
499
|
+
if (/>\s*\(\s*(?:rm|mv|cp|cat)\b/i.test(fullCmd) || /<<<.*(?:rm|mv|cp|cat)\b/i.test(fullCmd)) {
|
|
500
|
+
for (const p of protectedPaths) {
|
|
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
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 7e. xargs with destructive operations
|
|
509
|
+
if (/\bxargs\b.*\b(?:rm|mv|cp|rmdir|unlink)\b/i.test(fullCmd)) {
|
|
510
|
+
for (const p of protectedPaths) {
|
|
511
|
+
if (fullCmd.includes(p.slice(0, 4))) {
|
|
512
|
+
await blockSelfProtection('SOLONGATE: xargs with destructive op near "' + p + '" — blocked');
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
455
517
|
// ── Fetch PI config from Cloud ──
|
|
456
518
|
let piCfg = { piEnabled: true, piThreshold: 0.5, piMode: 'block', piWhitelist: [], piToolConfig: {}, piCustomPatterns: [], piWebhookUrl: null };
|
|
457
519
|
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.17.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": {
|