@solongate/proxy 0.26.1 → 0.26.3
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/create.js +15 -11
- package/dist/index.js +822 -747
- package/dist/init.js +46 -33
- package/dist/inject.js +48 -39
- package/dist/pull-push.js +13 -10
- package/hooks/guard.mjs +1175 -1175
- package/package.json +70 -70
package/dist/index.js
CHANGED
|
@@ -5,13 +5,15 @@ var __esm = (fn, res) => function __init() {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/config.ts
|
|
8
|
-
import { readFileSync, existsSync
|
|
8
|
+
import { readFileSync, existsSync } from "fs";
|
|
9
|
+
import { appendFile } from "fs/promises";
|
|
9
10
|
import { resolve } from "path";
|
|
10
11
|
async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
11
12
|
let resolvedId = policyId;
|
|
12
13
|
if (!resolvedId) {
|
|
13
14
|
const listRes = await fetch(`${apiUrl}/api/v1/policies`, {
|
|
14
|
-
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
15
|
+
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
16
|
+
signal: AbortSignal.timeout(1e4)
|
|
15
17
|
});
|
|
16
18
|
if (!listRes.ok) {
|
|
17
19
|
const body = await listRes.text().catch(() => "");
|
|
@@ -26,7 +28,8 @@ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
|
26
28
|
}
|
|
27
29
|
const url = `${apiUrl}/api/v1/policies/${resolvedId}`;
|
|
28
30
|
const res = await fetch(url, {
|
|
29
|
-
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
31
|
+
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
32
|
+
signal: AbortSignal.timeout(1e4)
|
|
30
33
|
});
|
|
31
34
|
if (!res.ok) {
|
|
32
35
|
const body = await res.text().catch(() => "");
|
|
@@ -73,7 +76,10 @@ async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
|
73
76
|
`);
|
|
74
77
|
try {
|
|
75
78
|
const line = JSON.stringify({ ...entry, timestamp: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
76
|
-
|
|
79
|
+
appendFile(AUDIT_LOG_BACKUP_PATH, line, "utf-8").catch((err) => {
|
|
80
|
+
process.stderr.write(`[SolonGate] Audit backup write error: ${err instanceof Error ? err.message : String(err)}
|
|
81
|
+
`);
|
|
82
|
+
});
|
|
77
83
|
} catch (err) {
|
|
78
84
|
process.stderr.write(`[SolonGate] Audit backup write error: ${err instanceof Error ? err.message : String(err)}
|
|
79
85
|
`);
|
|
@@ -327,7 +333,7 @@ function parseArgs(argv) {
|
|
|
327
333
|
transport: upstreamTransport ?? "stdio",
|
|
328
334
|
command,
|
|
329
335
|
args: commandArgs,
|
|
330
|
-
env: {
|
|
336
|
+
env: { PATH: process.env.PATH ?? "", HOME: process.env.HOME ?? "", USERPROFILE: process.env.USERPROFILE ?? "" }
|
|
331
337
|
},
|
|
332
338
|
policy: loadPolicy(policySource ?? "policy.json"),
|
|
333
339
|
name,
|
|
@@ -348,10 +354,11 @@ function resolvePolicyPath(source) {
|
|
|
348
354
|
if (existsSync(filePath)) return filePath;
|
|
349
355
|
return null;
|
|
350
356
|
}
|
|
351
|
-
var AUDIT_MAX_RETRIES, AUDIT_LOG_BACKUP_PATH, DEFAULT_POLICY;
|
|
357
|
+
var DEFAULT_API_URL, AUDIT_MAX_RETRIES, AUDIT_LOG_BACKUP_PATH, DEFAULT_POLICY;
|
|
352
358
|
var init_config = __esm({
|
|
353
359
|
"src/config.ts"() {
|
|
354
360
|
"use strict";
|
|
361
|
+
DEFAULT_API_URL = "https://api.solongate.com";
|
|
355
362
|
AUDIT_MAX_RETRIES = 3;
|
|
356
363
|
AUDIT_LOG_BACKUP_PATH = resolve(".solongate-audit-backup.jsonl");
|
|
357
364
|
DEFAULT_POLICY = {
|
|
@@ -366,23 +373,71 @@ var init_config = __esm({
|
|
|
366
373
|
}
|
|
367
374
|
});
|
|
368
375
|
|
|
376
|
+
// src/cli-utils.ts
|
|
377
|
+
function log3(msg) {
|
|
378
|
+
process.stderr.write(msg + "\n");
|
|
379
|
+
}
|
|
380
|
+
function printBanner(subtitle) {
|
|
381
|
+
log3("");
|
|
382
|
+
for (let i = 0; i < BANNER_FULL.length; i++) {
|
|
383
|
+
log3(`${c.bold}${BANNER_COLORS[i]}${BANNER_FULL[i]}${c.reset}`);
|
|
384
|
+
}
|
|
385
|
+
log3("");
|
|
386
|
+
log3(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
|
|
387
|
+
log3("");
|
|
388
|
+
}
|
|
389
|
+
var c, BANNER_FULL, BANNER_COLORS;
|
|
390
|
+
var init_cli_utils = __esm({
|
|
391
|
+
"src/cli-utils.ts"() {
|
|
392
|
+
"use strict";
|
|
393
|
+
c = {
|
|
394
|
+
reset: "\x1B[0m",
|
|
395
|
+
bold: "\x1B[1m",
|
|
396
|
+
dim: "\x1B[2m",
|
|
397
|
+
italic: "\x1B[3m",
|
|
398
|
+
white: "\x1B[97m",
|
|
399
|
+
gray: "\x1B[90m",
|
|
400
|
+
blue1: "\x1B[38;2;20;50;160m",
|
|
401
|
+
blue2: "\x1B[38;2;40;80;190m",
|
|
402
|
+
blue3: "\x1B[38;2;60;110;215m",
|
|
403
|
+
blue4: "\x1B[38;2;90;140;230m",
|
|
404
|
+
blue5: "\x1B[38;2;130;170;240m",
|
|
405
|
+
blue6: "\x1B[38;2;170;200;250m",
|
|
406
|
+
green: "\x1B[38;2;80;200;120m",
|
|
407
|
+
red: "\x1B[38;2;220;80;80m",
|
|
408
|
+
cyan: "\x1B[38;2;100;200;220m",
|
|
409
|
+
yellow: "\x1B[38;2;220;200;80m",
|
|
410
|
+
bgBlue: "\x1B[48;2;20;50;160m"
|
|
411
|
+
};
|
|
412
|
+
BANNER_FULL = [
|
|
413
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
414
|
+
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
415
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
416
|
+
" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
417
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
418
|
+
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
419
|
+
];
|
|
420
|
+
BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
369
424
|
// src/init.ts
|
|
370
425
|
var init_exports = {};
|
|
371
|
-
import { readFileSync as
|
|
372
|
-
import { resolve as
|
|
426
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
427
|
+
import { resolve as resolve3, join, dirname as dirname2 } from "path";
|
|
373
428
|
import { fileURLToPath } from "url";
|
|
374
|
-
import {
|
|
429
|
+
import { execFileSync } from "child_process";
|
|
375
430
|
import { createInterface } from "readline";
|
|
376
431
|
function findConfigFile(explicitPath, createIfMissing = false) {
|
|
377
432
|
if (explicitPath) {
|
|
378
|
-
if (
|
|
379
|
-
return { path:
|
|
433
|
+
if (existsSync4(explicitPath)) {
|
|
434
|
+
return { path: resolve3(explicitPath), type: "mcp" };
|
|
380
435
|
}
|
|
381
436
|
return null;
|
|
382
437
|
}
|
|
383
438
|
for (const searchPath of SEARCH_PATHS) {
|
|
384
|
-
const full =
|
|
385
|
-
if (
|
|
439
|
+
const full = resolve3(searchPath);
|
|
440
|
+
if (existsSync4(full)) return { path: full, type: "mcp" };
|
|
386
441
|
}
|
|
387
442
|
if (createIfMissing) {
|
|
388
443
|
const starterConfig = {
|
|
@@ -397,19 +452,19 @@ function findConfigFile(explicitPath, createIfMissing = false) {
|
|
|
397
452
|
}
|
|
398
453
|
}
|
|
399
454
|
};
|
|
400
|
-
const starterPath =
|
|
455
|
+
const starterPath = resolve3(".mcp.json");
|
|
401
456
|
writeFileSync2(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
402
457
|
console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
|
|
403
458
|
console.log("");
|
|
404
459
|
return { path: starterPath, type: "mcp", created: true };
|
|
405
460
|
}
|
|
406
461
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
407
|
-
if (
|
|
462
|
+
if (existsSync4(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
408
463
|
}
|
|
409
464
|
return null;
|
|
410
465
|
}
|
|
411
466
|
function readConfig(filePath) {
|
|
412
|
-
const content =
|
|
467
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
413
468
|
const parsed = JSON.parse(content);
|
|
414
469
|
if (parsed.mcpServers) return parsed;
|
|
415
470
|
throw new Error(`Unrecognized config format in ${filePath}`);
|
|
@@ -525,53 +580,53 @@ EXAMPLES
|
|
|
525
580
|
console.log(help);
|
|
526
581
|
}
|
|
527
582
|
function readHookScript(filename) {
|
|
528
|
-
return
|
|
583
|
+
return readFileSync4(join(HOOKS_DIR, filename), "utf-8");
|
|
529
584
|
}
|
|
530
585
|
function unlockProtectedDirs() {
|
|
531
586
|
const dirs = [".solongate", ".claude", ".cursor", ".gemini", ".antigravity", ".openclaw", ".perplexity"];
|
|
532
587
|
for (const dir of dirs) {
|
|
533
|
-
const fullDir =
|
|
534
|
-
if (!
|
|
588
|
+
const fullDir = resolve3(dir);
|
|
589
|
+
if (!existsSync4(fullDir)) continue;
|
|
535
590
|
try {
|
|
536
591
|
if (process.platform === "win32") {
|
|
537
592
|
try {
|
|
538
|
-
|
|
593
|
+
execFileSync("icacls", [fullDir, "/remove:d", "*S-1-1-0", "/T", "/Q"], { stdio: "ignore" });
|
|
539
594
|
} catch {
|
|
540
595
|
}
|
|
541
596
|
try {
|
|
542
|
-
|
|
597
|
+
execFileSync("attrib", ["-R", "/S", "/D", fullDir], { stdio: "ignore" });
|
|
543
598
|
} catch {
|
|
544
599
|
}
|
|
545
600
|
} else {
|
|
546
601
|
try {
|
|
547
|
-
|
|
602
|
+
execFileSync("chattr", ["-i", "-R", fullDir], { stdio: "ignore" });
|
|
548
603
|
} catch {
|
|
549
604
|
}
|
|
550
|
-
|
|
605
|
+
execFileSync("chmod", ["-R", "u+w", fullDir], { stdio: "ignore" });
|
|
551
606
|
}
|
|
552
607
|
} catch {
|
|
553
608
|
}
|
|
554
609
|
}
|
|
555
610
|
const protectedFiles = [".env", ".gitignore", ".mcp.json", "policy.json"];
|
|
556
611
|
for (const file of protectedFiles) {
|
|
557
|
-
const fullPath =
|
|
558
|
-
if (!
|
|
612
|
+
const fullPath = resolve3(file);
|
|
613
|
+
if (!existsSync4(fullPath)) continue;
|
|
559
614
|
try {
|
|
560
615
|
if (process.platform === "win32") {
|
|
561
616
|
try {
|
|
562
|
-
|
|
617
|
+
execFileSync("icacls", [fullPath, "/remove:d", "*S-1-1-0", "/Q"], { stdio: "ignore" });
|
|
563
618
|
} catch {
|
|
564
619
|
}
|
|
565
620
|
try {
|
|
566
|
-
|
|
621
|
+
execFileSync("attrib", ["-R", fullPath], { stdio: "ignore" });
|
|
567
622
|
} catch {
|
|
568
623
|
}
|
|
569
624
|
} else {
|
|
570
625
|
try {
|
|
571
|
-
|
|
626
|
+
execFileSync("chattr", ["-i", fullPath], { stdio: "ignore" });
|
|
572
627
|
} catch {
|
|
573
628
|
}
|
|
574
|
-
|
|
629
|
+
execFileSync("chmod", ["u+w", fullPath], { stdio: "ignore" });
|
|
575
630
|
}
|
|
576
631
|
} catch {
|
|
577
632
|
}
|
|
@@ -579,7 +634,7 @@ function unlockProtectedDirs() {
|
|
|
579
634
|
}
|
|
580
635
|
function installHooks(selectedTools = []) {
|
|
581
636
|
unlockProtectedDirs();
|
|
582
|
-
const hooksDir =
|
|
637
|
+
const hooksDir = resolve3(".solongate", "hooks");
|
|
583
638
|
mkdirSync2(hooksDir, { recursive: true });
|
|
584
639
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
585
640
|
writeFileSync2(guardPath, readHookScript("guard.mjs"));
|
|
@@ -618,12 +673,12 @@ function installHooks(selectedTools = []) {
|
|
|
618
673
|
const clients = selectedTools.length > 0 ? allClients.filter((c3) => selectedTools.includes(c3.key)) : allClients;
|
|
619
674
|
const activatedNames = [];
|
|
620
675
|
for (const client of clients) {
|
|
621
|
-
const clientDir =
|
|
676
|
+
const clientDir = resolve3(client.dir);
|
|
622
677
|
mkdirSync2(clientDir, { recursive: true });
|
|
623
678
|
const settingsPath = join(clientDir, "settings.json");
|
|
624
679
|
let existing = {};
|
|
625
680
|
try {
|
|
626
|
-
existing = JSON.parse(
|
|
681
|
+
existing = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
627
682
|
} catch {
|
|
628
683
|
}
|
|
629
684
|
const merged = { ...existing, hooks: hookSettings.hooks };
|
|
@@ -638,8 +693,8 @@ function installHooks(selectedTools = []) {
|
|
|
638
693
|
console.log(` Activated for: ${activatedNames.join(", ")}`);
|
|
639
694
|
}
|
|
640
695
|
function ensureEnvFile() {
|
|
641
|
-
const envPath =
|
|
642
|
-
if (!
|
|
696
|
+
const envPath = resolve3(".env");
|
|
697
|
+
if (!existsSync4(envPath)) {
|
|
643
698
|
const envContent = `# SolonGate Configuration
|
|
644
699
|
# IMPORTANT: Never commit this file to git!
|
|
645
700
|
|
|
@@ -655,9 +710,9 @@ GROQ_API_KEY=gsk_your_groq_key_here
|
|
|
655
710
|
console.log(` \u2192 Set your API key in .env (get one at https://dashboard.solongate.com)`);
|
|
656
711
|
console.log("");
|
|
657
712
|
}
|
|
658
|
-
const gitignorePath =
|
|
659
|
-
if (
|
|
660
|
-
let gitignore =
|
|
713
|
+
const gitignorePath = resolve3(".gitignore");
|
|
714
|
+
if (existsSync4(gitignorePath)) {
|
|
715
|
+
let gitignore = readFileSync4(gitignorePath, "utf-8");
|
|
661
716
|
let updated = false;
|
|
662
717
|
if (!gitignore.includes(".env")) {
|
|
663
718
|
gitignore = gitignore.trimEnd() + "\n.env\n.env.local\n";
|
|
@@ -678,26 +733,7 @@ GROQ_API_KEY=gsk_your_groq_key_here
|
|
|
678
733
|
}
|
|
679
734
|
async function main() {
|
|
680
735
|
const options = parseInitArgs(process.argv);
|
|
681
|
-
const
|
|
682
|
-
reset: "\x1B[0m",
|
|
683
|
-
bold: "\x1B[1m",
|
|
684
|
-
dim: "\x1B[2m",
|
|
685
|
-
italic: "\x1B[3m",
|
|
686
|
-
blue1: "\x1B[38;2;20;50;160m",
|
|
687
|
-
blue2: "\x1B[38;2;40;80;190m",
|
|
688
|
-
blue3: "\x1B[38;2;60;110;215m",
|
|
689
|
-
blue4: "\x1B[38;2;90;140;230m",
|
|
690
|
-
blue5: "\x1B[38;2;130;170;240m",
|
|
691
|
-
blue6: "\x1B[38;2;170;200;250m"
|
|
692
|
-
};
|
|
693
|
-
const fullBanner = [
|
|
694
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
695
|
-
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
696
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
697
|
-
" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
698
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
699
|
-
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
700
|
-
];
|
|
736
|
+
const fullBanner = BANNER_FULL;
|
|
701
737
|
const mediumBanner = [
|
|
702
738
|
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
703
739
|
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551",
|
|
@@ -724,13 +760,13 @@ async function main() {
|
|
|
724
760
|
];
|
|
725
761
|
const cols = process.stdout.columns || 80;
|
|
726
762
|
const bannerLines = cols >= 82 ? fullBanner : cols >= 50 ? mediumBanner : smallBanner;
|
|
727
|
-
const bannerColors =
|
|
763
|
+
const bannerColors = BANNER_COLORS;
|
|
728
764
|
console.log("");
|
|
729
765
|
for (let i = 0; i < bannerLines.length; i++) {
|
|
730
|
-
console.log(`${
|
|
766
|
+
console.log(`${c.bold}${bannerColors[i % bannerColors.length]}${bannerLines[i]}${c.reset}`);
|
|
731
767
|
}
|
|
732
768
|
console.log("");
|
|
733
|
-
console.log(` ${
|
|
769
|
+
console.log(` ${c.dim}${c.italic}Init Setup${c.reset}`);
|
|
734
770
|
console.log("");
|
|
735
771
|
await sleep(400);
|
|
736
772
|
const configInfo = findConfigFile(options.configPath, true);
|
|
@@ -796,9 +832,9 @@ async function main() {
|
|
|
796
832
|
console.log("");
|
|
797
833
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
798
834
|
if (!apiKey) {
|
|
799
|
-
const envPath =
|
|
800
|
-
if (
|
|
801
|
-
const envContent =
|
|
835
|
+
const envPath = resolve3(".env");
|
|
836
|
+
if (existsSync4(envPath)) {
|
|
837
|
+
const envContent = readFileSync4(envPath, "utf-8");
|
|
802
838
|
const match = envContent.match(/^SOLONGATE_API_KEY=(sg_(?:live|test)_\w+)/m);
|
|
803
839
|
if (match) apiKey = match[1];
|
|
804
840
|
}
|
|
@@ -823,8 +859,8 @@ async function main() {
|
|
|
823
859
|
}
|
|
824
860
|
let policyValue;
|
|
825
861
|
if (options.policy) {
|
|
826
|
-
const policyPath =
|
|
827
|
-
if (
|
|
862
|
+
const policyPath = resolve3(options.policy);
|
|
863
|
+
if (existsSync4(policyPath)) {
|
|
828
864
|
policyValue = `./${options.policy}`;
|
|
829
865
|
console.log(` Policy: ${policyPath}`);
|
|
830
866
|
} else {
|
|
@@ -832,8 +868,8 @@ async function main() {
|
|
|
832
868
|
process.exit(1);
|
|
833
869
|
}
|
|
834
870
|
} else {
|
|
835
|
-
const defaultPolicy =
|
|
836
|
-
if (
|
|
871
|
+
const defaultPolicy = resolve3("policy.json");
|
|
872
|
+
if (existsSync4(defaultPolicy)) {
|
|
837
873
|
policyValue = "./policy.json";
|
|
838
874
|
console.log(` Policy: ${defaultPolicy} (auto-detected)`);
|
|
839
875
|
} else {
|
|
@@ -856,7 +892,7 @@ async function main() {
|
|
|
856
892
|
}
|
|
857
893
|
await sleep(400);
|
|
858
894
|
if (configInfo.type === "claude-desktop") {
|
|
859
|
-
const original = JSON.parse(
|
|
895
|
+
const original = JSON.parse(readFileSync4(configInfo.path, "utf-8"));
|
|
860
896
|
original.mcpServers = newConfig.mcpServers;
|
|
861
897
|
writeFileSync2(configInfo.path, JSON.stringify(original, null, 2) + "\n");
|
|
862
898
|
} else {
|
|
@@ -904,6 +940,7 @@ var SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, sleep, __dirname, HOOKS_DIR;
|
|
|
904
940
|
var init_init = __esm({
|
|
905
941
|
"src/init.ts"() {
|
|
906
942
|
"use strict";
|
|
943
|
+
init_cli_utils();
|
|
907
944
|
SEARCH_PATHS = [
|
|
908
945
|
".mcp.json",
|
|
909
946
|
"mcp.json",
|
|
@@ -912,7 +949,7 @@ var init_init = __esm({
|
|
|
912
949
|
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
|
|
913
950
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
914
951
|
__dirname = dirname2(fileURLToPath(import.meta.url));
|
|
915
|
-
HOOKS_DIR =
|
|
952
|
+
HOOKS_DIR = resolve3(__dirname, "..", "hooks");
|
|
916
953
|
main().catch((err) => {
|
|
917
954
|
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
918
955
|
process.exit(1);
|
|
@@ -922,9 +959,9 @@ var init_init = __esm({
|
|
|
922
959
|
|
|
923
960
|
// src/inject.ts
|
|
924
961
|
var inject_exports = {};
|
|
925
|
-
import { readFileSync as
|
|
926
|
-
import { resolve as
|
|
927
|
-
import { execSync
|
|
962
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, copyFileSync } from "fs";
|
|
963
|
+
import { resolve as resolve4 } from "path";
|
|
964
|
+
import { execSync } from "child_process";
|
|
928
965
|
function parseInjectArgs(argv) {
|
|
929
966
|
const args = argv.slice(2);
|
|
930
967
|
const opts = {
|
|
@@ -979,31 +1016,10 @@ WHAT IT DOES
|
|
|
979
1016
|
All tool() calls are automatically protected by SolonGate.
|
|
980
1017
|
`);
|
|
981
1018
|
}
|
|
982
|
-
function log3(msg) {
|
|
983
|
-
process.stderr.write(msg + "\n");
|
|
984
|
-
}
|
|
985
|
-
function printBanner(subtitle) {
|
|
986
|
-
const lines = [
|
|
987
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
988
|
-
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
989
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
990
|
-
" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
991
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
992
|
-
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
993
|
-
];
|
|
994
|
-
const colors = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
995
|
-
log3("");
|
|
996
|
-
for (let i = 0; i < lines.length; i++) {
|
|
997
|
-
log3(`${c.bold}${colors[i]}${lines[i]}${c.reset}`);
|
|
998
|
-
}
|
|
999
|
-
log3("");
|
|
1000
|
-
log3(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
|
|
1001
|
-
log3("");
|
|
1002
|
-
}
|
|
1003
1019
|
function detectProject() {
|
|
1004
|
-
if (!
|
|
1020
|
+
if (!existsSync5(resolve4("package.json"))) return false;
|
|
1005
1021
|
try {
|
|
1006
|
-
const pkg = JSON.parse(
|
|
1022
|
+
const pkg = JSON.parse(readFileSync5(resolve4("package.json"), "utf-8"));
|
|
1007
1023
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1008
1024
|
return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
|
|
1009
1025
|
} catch {
|
|
@@ -1012,18 +1028,18 @@ function detectProject() {
|
|
|
1012
1028
|
}
|
|
1013
1029
|
function findTsEntryFile() {
|
|
1014
1030
|
try {
|
|
1015
|
-
const pkg = JSON.parse(
|
|
1031
|
+
const pkg = JSON.parse(readFileSync5(resolve4("package.json"), "utf-8"));
|
|
1016
1032
|
if (pkg.bin) {
|
|
1017
1033
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
|
|
1018
1034
|
if (typeof binPath === "string") {
|
|
1019
1035
|
const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
1020
|
-
if (
|
|
1021
|
-
if (
|
|
1036
|
+
if (existsSync5(resolve4(srcPath))) return resolve4(srcPath);
|
|
1037
|
+
if (existsSync5(resolve4(binPath))) return resolve4(binPath);
|
|
1022
1038
|
}
|
|
1023
1039
|
}
|
|
1024
1040
|
if (pkg.main) {
|
|
1025
1041
|
const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
1026
|
-
if (
|
|
1042
|
+
if (existsSync5(resolve4(srcPath))) return resolve4(srcPath);
|
|
1027
1043
|
}
|
|
1028
1044
|
} catch {
|
|
1029
1045
|
}
|
|
@@ -1036,10 +1052,10 @@ function findTsEntryFile() {
|
|
|
1036
1052
|
"main.ts"
|
|
1037
1053
|
];
|
|
1038
1054
|
for (const c3 of candidates) {
|
|
1039
|
-
const full =
|
|
1040
|
-
if (
|
|
1055
|
+
const full = resolve4(c3);
|
|
1056
|
+
if (existsSync5(full)) {
|
|
1041
1057
|
try {
|
|
1042
|
-
const content =
|
|
1058
|
+
const content = readFileSync5(full, "utf-8");
|
|
1043
1059
|
if (content.includes("McpServer") || content.includes("McpServer")) {
|
|
1044
1060
|
return full;
|
|
1045
1061
|
}
|
|
@@ -1048,18 +1064,18 @@ function findTsEntryFile() {
|
|
|
1048
1064
|
}
|
|
1049
1065
|
}
|
|
1050
1066
|
for (const c3 of candidates) {
|
|
1051
|
-
if (
|
|
1067
|
+
if (existsSync5(resolve4(c3))) return resolve4(c3);
|
|
1052
1068
|
}
|
|
1053
1069
|
return null;
|
|
1054
1070
|
}
|
|
1055
1071
|
function detectPackageManager() {
|
|
1056
|
-
if (
|
|
1057
|
-
if (
|
|
1072
|
+
if (existsSync5(resolve4("pnpm-lock.yaml"))) return "pnpm";
|
|
1073
|
+
if (existsSync5(resolve4("yarn.lock"))) return "yarn";
|
|
1058
1074
|
return "npm";
|
|
1059
1075
|
}
|
|
1060
1076
|
function installSdk() {
|
|
1061
1077
|
try {
|
|
1062
|
-
const pkg = JSON.parse(
|
|
1078
|
+
const pkg = JSON.parse(readFileSync5(resolve4("package.json"), "utf-8"));
|
|
1063
1079
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1064
1080
|
if (allDeps["@solongate/sdk"]) {
|
|
1065
1081
|
log3(" @solongate/sdk already installed");
|
|
@@ -1071,7 +1087,7 @@ function installSdk() {
|
|
|
1071
1087
|
const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
|
|
1072
1088
|
log3(` Installing @solongate/sdk via ${pm}...`);
|
|
1073
1089
|
try {
|
|
1074
|
-
|
|
1090
|
+
execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
1075
1091
|
return true;
|
|
1076
1092
|
} catch (err) {
|
|
1077
1093
|
log3(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1080,7 +1096,7 @@ function installSdk() {
|
|
|
1080
1096
|
}
|
|
1081
1097
|
}
|
|
1082
1098
|
function injectTypeScript(filePath) {
|
|
1083
|
-
const original =
|
|
1099
|
+
const original = readFileSync5(filePath, "utf-8");
|
|
1084
1100
|
const changes = [];
|
|
1085
1101
|
let modified = original;
|
|
1086
1102
|
if (modified.includes("SecureMcpServer")) {
|
|
@@ -1196,8 +1212,8 @@ async function main2() {
|
|
|
1196
1212
|
process.exit(1);
|
|
1197
1213
|
}
|
|
1198
1214
|
log3(" Language: TypeScript");
|
|
1199
|
-
const entryFile = opts.file ?
|
|
1200
|
-
if (!entryFile || !
|
|
1215
|
+
const entryFile = opts.file ? resolve4(opts.file) : findTsEntryFile();
|
|
1216
|
+
if (!entryFile || !existsSync5(entryFile)) {
|
|
1201
1217
|
log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
|
|
1202
1218
|
log3("");
|
|
1203
1219
|
log3(" Specify it manually: --file <path>");
|
|
@@ -1210,7 +1226,7 @@ async function main2() {
|
|
|
1210
1226
|
log3("");
|
|
1211
1227
|
const backupPath = entryFile + ".solongate-backup";
|
|
1212
1228
|
if (opts.restore) {
|
|
1213
|
-
if (!
|
|
1229
|
+
if (!existsSync5(backupPath)) {
|
|
1214
1230
|
log3(" No backup found. Nothing to restore.");
|
|
1215
1231
|
process.exit(1);
|
|
1216
1232
|
}
|
|
@@ -1246,7 +1262,7 @@ async function main2() {
|
|
|
1246
1262
|
log3(" To apply: npx @solongate/proxy inject");
|
|
1247
1263
|
process.exit(0);
|
|
1248
1264
|
}
|
|
1249
|
-
if (!
|
|
1265
|
+
if (!existsSync5(backupPath)) {
|
|
1250
1266
|
copyFileSync(entryFile, backupPath);
|
|
1251
1267
|
log3("");
|
|
1252
1268
|
log3(` Backup: ${backupPath}`);
|
|
@@ -1267,24 +1283,10 @@ async function main2() {
|
|
|
1267
1283
|
log3(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1268
1284
|
log3("");
|
|
1269
1285
|
}
|
|
1270
|
-
var c;
|
|
1271
1286
|
var init_inject = __esm({
|
|
1272
1287
|
"src/inject.ts"() {
|
|
1273
1288
|
"use strict";
|
|
1274
|
-
|
|
1275
|
-
reset: "\x1B[0m",
|
|
1276
|
-
bold: "\x1B[1m",
|
|
1277
|
-
dim: "\x1B[2m",
|
|
1278
|
-
italic: "\x1B[3m",
|
|
1279
|
-
green: "\x1B[38;2;80;200;120m",
|
|
1280
|
-
red: "\x1B[38;2;220;80;80m",
|
|
1281
|
-
blue1: "\x1B[38;2;20;50;160m",
|
|
1282
|
-
blue2: "\x1B[38;2;40;80;190m",
|
|
1283
|
-
blue3: "\x1B[38;2;60;110;215m",
|
|
1284
|
-
blue4: "\x1B[38;2;90;140;230m",
|
|
1285
|
-
blue5: "\x1B[38;2;130;170;240m",
|
|
1286
|
-
blue6: "\x1B[38;2;170;200;250m"
|
|
1287
|
-
};
|
|
1289
|
+
init_cli_utils();
|
|
1288
1290
|
main2().catch((err) => {
|
|
1289
1291
|
log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1290
1292
|
process.exit(1);
|
|
@@ -1294,46 +1296,25 @@ var init_inject = __esm({
|
|
|
1294
1296
|
|
|
1295
1297
|
// src/create.ts
|
|
1296
1298
|
var create_exports = {};
|
|
1297
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as
|
|
1298
|
-
import { resolve as
|
|
1299
|
-
import { execSync as
|
|
1300
|
-
function log4(msg) {
|
|
1301
|
-
process.stderr.write(msg + "\n");
|
|
1302
|
-
}
|
|
1303
|
-
function printBanner2(subtitle) {
|
|
1304
|
-
const lines = [
|
|
1305
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
1306
|
-
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
1307
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
1308
|
-
" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
1309
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
1310
|
-
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
1311
|
-
];
|
|
1312
|
-
const colors = [c2.blue1, c2.blue2, c2.blue3, c2.blue4, c2.blue5, c2.blue6];
|
|
1313
|
-
log4("");
|
|
1314
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1315
|
-
log4(`${c2.bold}${colors[i]}${lines[i]}${c2.reset}`);
|
|
1316
|
-
}
|
|
1317
|
-
log4("");
|
|
1318
|
-
log4(` ${c2.dim}${c2.italic}${subtitle}${c2.reset}`);
|
|
1319
|
-
log4("");
|
|
1320
|
-
}
|
|
1299
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
|
|
1300
|
+
import { resolve as resolve5, join as join2 } from "path";
|
|
1301
|
+
import { execSync as execSync2 } from "child_process";
|
|
1321
1302
|
function withSpinner(message, fn) {
|
|
1322
1303
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
1323
1304
|
let i = 0;
|
|
1324
1305
|
const id = setInterval(() => {
|
|
1325
|
-
const color = i % 2 === 0 ?
|
|
1326
|
-
process.stderr.write(`\r ${color}${frames[i++ % frames.length]}${
|
|
1306
|
+
const color = i % 2 === 0 ? c.blue3 : c.blue5;
|
|
1307
|
+
process.stderr.write(`\r ${color}${frames[i++ % frames.length]}${c.reset} ${c.dim}${message}${c.reset}`);
|
|
1327
1308
|
}, 80);
|
|
1328
1309
|
try {
|
|
1329
1310
|
const result = fn();
|
|
1330
1311
|
clearInterval(id);
|
|
1331
|
-
process.stderr.write(`\r ${
|
|
1312
|
+
process.stderr.write(`\r ${c.green}\u2713${c.reset} ${message}${" ".repeat(20)}
|
|
1332
1313
|
`);
|
|
1333
1314
|
return result;
|
|
1334
1315
|
} catch (err) {
|
|
1335
1316
|
clearInterval(id);
|
|
1336
|
-
process.stderr.write(`\r ${
|
|
1317
|
+
process.stderr.write(`\r ${c.red}\u2717${c.reset} ${message} \u2014 failed${" ".repeat(10)}
|
|
1337
1318
|
`);
|
|
1338
1319
|
throw err;
|
|
1339
1320
|
}
|
|
@@ -1365,20 +1346,20 @@ function parseCreateArgs(argv) {
|
|
|
1365
1346
|
}
|
|
1366
1347
|
}
|
|
1367
1348
|
if (!opts.name) {
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1349
|
+
log3("");
|
|
1350
|
+
log3(" Error: Project name required.");
|
|
1351
|
+
log3("");
|
|
1352
|
+
log3(" Usage: npx @solongate/proxy create <name>");
|
|
1353
|
+
log3("");
|
|
1354
|
+
log3(" Examples:");
|
|
1355
|
+
log3(" npx @solongate/proxy create my-mcp-server");
|
|
1356
|
+
log3(" npx @solongate/proxy create weather-api");
|
|
1376
1357
|
process.exit(1);
|
|
1377
1358
|
}
|
|
1378
1359
|
return opts;
|
|
1379
1360
|
}
|
|
1380
1361
|
function printHelp3() {
|
|
1381
|
-
|
|
1362
|
+
log3(`
|
|
1382
1363
|
SolonGate Create \u2014 Scaffold a secure MCP server in seconds
|
|
1383
1364
|
|
|
1384
1365
|
USAGE
|
|
@@ -1528,10 +1509,10 @@ dist/
|
|
|
1528
1509
|
}
|
|
1529
1510
|
async function main3() {
|
|
1530
1511
|
const opts = parseCreateArgs(process.argv);
|
|
1531
|
-
const dir =
|
|
1532
|
-
|
|
1533
|
-
if (
|
|
1534
|
-
|
|
1512
|
+
const dir = resolve5(opts.name);
|
|
1513
|
+
printBanner("Create MCP Server");
|
|
1514
|
+
if (existsSync6(dir)) {
|
|
1515
|
+
log3(` ${c.red}Error:${c.reset} Directory "${opts.name}" already exists.`);
|
|
1535
1516
|
process.exit(1);
|
|
1536
1517
|
}
|
|
1537
1518
|
withSpinner(`Setting up ${opts.name}...`, () => {
|
|
@@ -1540,87 +1521,68 @@ async function main3() {
|
|
|
1540
1521
|
});
|
|
1541
1522
|
if (!opts.noInstall) {
|
|
1542
1523
|
withSpinner("Installing dependencies...", () => {
|
|
1543
|
-
|
|
1524
|
+
execSync2("npm install", { cwd: dir, stdio: "pipe" });
|
|
1544
1525
|
});
|
|
1545
1526
|
}
|
|
1546
|
-
|
|
1527
|
+
log3("");
|
|
1547
1528
|
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1548
1529
|
const W = 52;
|
|
1549
1530
|
const hr = "\u2500".repeat(W + 2);
|
|
1550
1531
|
const bLine = (text) => {
|
|
1551
1532
|
const visible = stripAnsi(text);
|
|
1552
1533
|
const padding = W - visible.length;
|
|
1553
|
-
|
|
1534
|
+
log3(` ${c.dim}\u2502${c.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c.dim}\u2502${c.reset}`);
|
|
1554
1535
|
};
|
|
1555
|
-
const bEmpty = () =>
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1536
|
+
const bEmpty = () => log3(` ${c.dim}\u2502${c.reset} ${" ".repeat(W)} ${c.dim}\u2502${c.reset}`);
|
|
1537
|
+
log3(` ${c.dim}\u256D${hr}\u256E${c.reset}`);
|
|
1538
|
+
log3(` ${c.dim}\u2502${c.reset} ${c.green}${c.bold}\u2714 MCP Server created successfully!${c.reset}${" ".repeat(W - 35)} ${c.dim}\u2502${c.reset}`);
|
|
1539
|
+
log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
|
|
1559
1540
|
bEmpty();
|
|
1560
|
-
bLine(`${
|
|
1541
|
+
bLine(`${c.yellow}Next steps:${c.reset}`);
|
|
1561
1542
|
bEmpty();
|
|
1562
|
-
bLine(` ${
|
|
1563
|
-
bLine(` ${
|
|
1564
|
-
bLine(` ${
|
|
1565
|
-
bLine(` ${
|
|
1543
|
+
bLine(` ${c.cyan}$${c.reset} cd ${c.bold}${opts.name}${c.reset}`);
|
|
1544
|
+
bLine(` ${c.cyan}$${c.reset} npm run build`);
|
|
1545
|
+
bLine(` ${c.cyan}$${c.reset} npm run dev ${c.dim}# dev mode${c.reset}`);
|
|
1546
|
+
bLine(` ${c.cyan}$${c.reset} npm start ${c.dim}# production${c.reset}`);
|
|
1566
1547
|
bEmpty();
|
|
1567
|
-
|
|
1548
|
+
log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
|
|
1568
1549
|
bEmpty();
|
|
1569
|
-
bLine(`${
|
|
1550
|
+
bLine(`${c.yellow}Set your API key:${c.reset}`);
|
|
1570
1551
|
bEmpty();
|
|
1571
|
-
bLine(` ${
|
|
1552
|
+
bLine(` ${c.cyan}$${c.reset} export SOLONGATE_API_KEY=${c.blue3}sg_live_xxx${c.reset}`);
|
|
1572
1553
|
bEmpty();
|
|
1573
|
-
bLine(`${
|
|
1554
|
+
bLine(`${c.dim}https://dashboard.solongate.com/api-keys/${c.reset}`);
|
|
1574
1555
|
bEmpty();
|
|
1575
|
-
|
|
1556
|
+
log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
|
|
1576
1557
|
bEmpty();
|
|
1577
|
-
bLine(`${
|
|
1558
|
+
bLine(`${c.yellow}Use with any MCP server:${c.reset}`);
|
|
1578
1559
|
bEmpty();
|
|
1579
|
-
bLine(` ${
|
|
1580
|
-
bLine(` ${
|
|
1581
|
-
bLine(` ${
|
|
1560
|
+
bLine(` ${c.cyan}$${c.reset} solongate-proxy -- npx @modelcontextprotocol/server-filesystem .`);
|
|
1561
|
+
bLine(` ${c.cyan}$${c.reset} solongate-proxy -- npx @playwright/mcp@latest`);
|
|
1562
|
+
bLine(` ${c.dim}Wraps any MCP server or AI tool with SolonGate protection${c.reset}`);
|
|
1582
1563
|
bEmpty();
|
|
1583
|
-
|
|
1564
|
+
log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
|
|
1584
1565
|
bEmpty();
|
|
1585
|
-
bLine(`${
|
|
1566
|
+
bLine(`${c.yellow}Use with Claude Code / Cursor / MCP client:${c.reset}`);
|
|
1586
1567
|
bEmpty();
|
|
1587
|
-
bLine(` ${
|
|
1588
|
-
bLine(` ${
|
|
1589
|
-
bLine(` ${
|
|
1568
|
+
bLine(` ${c.dim}1.${c.reset} Replace ${c.blue3}sg_live_YOUR_KEY_HERE${c.reset} in .mcp.json`);
|
|
1569
|
+
bLine(` ${c.dim}2.${c.reset} Open this folder in your MCP client`);
|
|
1570
|
+
bLine(` ${c.dim}3.${c.reset} .mcp.json is auto-detected on startup`);
|
|
1590
1571
|
bEmpty();
|
|
1591
|
-
bLine(`${
|
|
1572
|
+
bLine(`${c.yellow}Direct test (without MCP client):${c.reset}`);
|
|
1592
1573
|
bEmpty();
|
|
1593
|
-
bLine(` ${
|
|
1594
|
-
bLine(` ${
|
|
1574
|
+
bLine(` ${c.dim}1.${c.reset} Replace ${c.blue3}sg_live_YOUR_KEY_HERE${c.reset} in .env`);
|
|
1575
|
+
bLine(` ${c.dim}2.${c.reset} ${c.cyan}$${c.reset} npm run build && npm start`);
|
|
1595
1576
|
bEmpty();
|
|
1596
|
-
|
|
1597
|
-
|
|
1577
|
+
log3(` ${c.dim}\u2570${hr}\u256F${c.reset}`);
|
|
1578
|
+
log3("");
|
|
1598
1579
|
}
|
|
1599
|
-
var c2;
|
|
1600
1580
|
var init_create = __esm({
|
|
1601
1581
|
"src/create.ts"() {
|
|
1602
1582
|
"use strict";
|
|
1603
|
-
|
|
1604
|
-
reset: "\x1B[0m",
|
|
1605
|
-
bold: "\x1B[1m",
|
|
1606
|
-
dim: "\x1B[2m",
|
|
1607
|
-
italic: "\x1B[3m",
|
|
1608
|
-
white: "\x1B[97m",
|
|
1609
|
-
gray: "\x1B[90m",
|
|
1610
|
-
blue1: "\x1B[38;2;20;50;160m",
|
|
1611
|
-
blue2: "\x1B[38;2;40;80;190m",
|
|
1612
|
-
blue3: "\x1B[38;2;60;110;215m",
|
|
1613
|
-
blue4: "\x1B[38;2;90;140;230m",
|
|
1614
|
-
blue5: "\x1B[38;2;130;170;240m",
|
|
1615
|
-
blue6: "\x1B[38;2;170;200;250m",
|
|
1616
|
-
green: "\x1B[38;2;80;200;120m",
|
|
1617
|
-
red: "\x1B[38;2;220;80;80m",
|
|
1618
|
-
cyan: "\x1B[38;2;100;200;220m",
|
|
1619
|
-
yellow: "\x1B[38;2;220;200;80m",
|
|
1620
|
-
bgBlue: "\x1B[48;2;20;50;160m"
|
|
1621
|
-
};
|
|
1583
|
+
init_cli_utils();
|
|
1622
1584
|
main3().catch((err) => {
|
|
1623
|
-
|
|
1585
|
+
log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1624
1586
|
process.exit(1);
|
|
1625
1587
|
});
|
|
1626
1588
|
}
|
|
@@ -1628,14 +1590,14 @@ var init_create = __esm({
|
|
|
1628
1590
|
|
|
1629
1591
|
// src/pull-push.ts
|
|
1630
1592
|
var pull_push_exports = {};
|
|
1631
|
-
import { readFileSync as
|
|
1632
|
-
import { resolve as
|
|
1593
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
|
|
1594
|
+
import { resolve as resolve6 } from "path";
|
|
1633
1595
|
function loadEnv() {
|
|
1634
1596
|
if (process.env.SOLONGATE_API_KEY) return;
|
|
1635
|
-
const envPath =
|
|
1636
|
-
if (!
|
|
1597
|
+
const envPath = resolve6(".env");
|
|
1598
|
+
if (!existsSync7(envPath)) return;
|
|
1637
1599
|
try {
|
|
1638
|
-
const content =
|
|
1600
|
+
const content = readFileSync6(envPath, "utf-8");
|
|
1639
1601
|
for (const line of content.split("\n")) {
|
|
1640
1602
|
const trimmed = line.trim();
|
|
1641
1603
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -1677,20 +1639,20 @@ function parseCliArgs() {
|
|
|
1677
1639
|
}
|
|
1678
1640
|
}
|
|
1679
1641
|
if (!apiKey) {
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1642
|
+
log4(red("ERROR: API key not found."));
|
|
1643
|
+
log4("");
|
|
1644
|
+
log4("Set it in .env file:");
|
|
1645
|
+
log4(" SOLONGATE_API_KEY=sg_live_...");
|
|
1646
|
+
log4("");
|
|
1647
|
+
log4("Or pass via environment:");
|
|
1648
|
+
log4(` SOLONGATE_API_KEY=sg_live_... solongate-proxy ${command}`);
|
|
1687
1649
|
process.exit(1);
|
|
1688
1650
|
}
|
|
1689
1651
|
if (!apiKey.startsWith("sg_live_")) {
|
|
1690
|
-
|
|
1652
|
+
log4(red("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
|
|
1691
1653
|
process.exit(1);
|
|
1692
1654
|
}
|
|
1693
|
-
return { command, apiKey, file:
|
|
1655
|
+
return { command, apiKey, file: resolve6(file), policyId };
|
|
1694
1656
|
}
|
|
1695
1657
|
async function listPolicies(apiKey) {
|
|
1696
1658
|
const res = await fetch(`${API_URL}/api/v1/policies`, {
|
|
@@ -1703,18 +1665,18 @@ async function listPolicies(apiKey) {
|
|
|
1703
1665
|
async function list(apiKey, policyId) {
|
|
1704
1666
|
const policies = await listPolicies(apiKey);
|
|
1705
1667
|
if (policies.length === 0) {
|
|
1706
|
-
|
|
1707
|
-
|
|
1668
|
+
log4(yellow("No policies found. Create one in the dashboard first."));
|
|
1669
|
+
log4(dim(" https://dashboard.solongate.com/policies"));
|
|
1708
1670
|
return;
|
|
1709
1671
|
}
|
|
1710
1672
|
if (policyId) {
|
|
1711
1673
|
const match = policies.find((p) => p.id === policyId);
|
|
1712
1674
|
if (!match) {
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1675
|
+
log4(red(`Policy not found: ${policyId}`));
|
|
1676
|
+
log4("");
|
|
1677
|
+
log4("Available policies:");
|
|
1716
1678
|
for (const p of policies) {
|
|
1717
|
-
|
|
1679
|
+
log4(` ${dim("\u2022")} ${p.id}`);
|
|
1718
1680
|
}
|
|
1719
1681
|
process.exit(1);
|
|
1720
1682
|
}
|
|
@@ -1722,148 +1684,148 @@ async function list(apiKey, policyId) {
|
|
|
1722
1684
|
printPolicyDetail(full);
|
|
1723
1685
|
return;
|
|
1724
1686
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1687
|
+
log4("");
|
|
1688
|
+
log4(bold(` Policies (${policies.length})`));
|
|
1689
|
+
log4(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1690
|
+
log4("");
|
|
1691
|
+
const fullPolicies = await Promise.all(
|
|
1692
|
+
policies.map(
|
|
1693
|
+
(p) => fetchCloudPolicy(apiKey, API_URL, p.id).then((full) => ({ policy: p, rules: full.rules })).catch(() => ({ policy: p, rules: [] }))
|
|
1694
|
+
)
|
|
1695
|
+
);
|
|
1696
|
+
for (const { policy, rules } of fullPolicies) {
|
|
1697
|
+
printPolicySummary(policy, rules);
|
|
1736
1698
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1699
|
+
log4(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1700
|
+
log4("");
|
|
1701
|
+
log4(` ${dim("View details:")} solongate-proxy list --policy-id <ID>`);
|
|
1702
|
+
log4(` ${dim("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
|
|
1703
|
+
log4(` ${dim("Push policy:")} solongate-proxy push --policy-id <ID>`);
|
|
1704
|
+
log4("");
|
|
1743
1705
|
}
|
|
1744
1706
|
function printPolicySummary(p, rules) {
|
|
1745
1707
|
const ruleCount = rules.length;
|
|
1746
1708
|
const allowCount = rules.filter((r) => r.effect === "ALLOW").length;
|
|
1747
1709
|
const denyCount = rules.filter((r) => r.effect === "DENY").length;
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1710
|
+
log4(` ${cyan(p.id)}`);
|
|
1711
|
+
log4(` ${bold(p.name)} ${dim(`v${p.version ?? "?"}`)}`);
|
|
1712
|
+
log4(` ${dim("Rules:")} ${ruleCount} ${green(`${allowCount} ALLOW`)} ${red(`${denyCount} DENY`)}`);
|
|
1751
1713
|
if (p.created_at) {
|
|
1752
|
-
|
|
1714
|
+
log4(` ${dim("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
|
|
1753
1715
|
}
|
|
1754
|
-
|
|
1716
|
+
log4("");
|
|
1755
1717
|
}
|
|
1756
1718
|
function printPolicyDetail(policy) {
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1719
|
+
log4("");
|
|
1720
|
+
log4(bold(` ${policy.name}`));
|
|
1721
|
+
log4(` ${dim("ID:")} ${cyan(policy.id)} ${dim("Version:")} ${policy.version} ${dim("Rules:")} ${policy.rules.length}`);
|
|
1722
|
+
log4("");
|
|
1761
1723
|
if (policy.rules.length === 0) {
|
|
1762
|
-
|
|
1763
|
-
|
|
1724
|
+
log4(yellow(" No rules defined."));
|
|
1725
|
+
log4("");
|
|
1764
1726
|
return;
|
|
1765
1727
|
}
|
|
1766
|
-
|
|
1728
|
+
log4(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1767
1729
|
for (const rule of policy.rules) {
|
|
1768
1730
|
const effectColor = rule.effect === "ALLOW" ? green : red;
|
|
1769
|
-
|
|
1770
|
-
|
|
1731
|
+
log4("");
|
|
1732
|
+
log4(` ${effectColor(rule.effect.padEnd(5))} ${bold(rule.toolPattern)} ${dim(`P:${rule.priority}`)}`);
|
|
1771
1733
|
if (rule.description) {
|
|
1772
|
-
|
|
1734
|
+
log4(` ${dim(rule.description)}`);
|
|
1773
1735
|
}
|
|
1774
|
-
|
|
1736
|
+
log4(` ${dim(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
|
|
1775
1737
|
if (rule.pathConstraints) {
|
|
1776
1738
|
const pc = rule.pathConstraints;
|
|
1777
|
-
if (pc.rootDirectory)
|
|
1778
|
-
if (pc.allowed?.length)
|
|
1779
|
-
if (pc.denied?.length)
|
|
1739
|
+
if (pc.rootDirectory) log4(` ${magenta("ROOT")} ${pc.rootDirectory}`);
|
|
1740
|
+
if (pc.allowed?.length) log4(` ${green("PATHS")} ${pc.allowed.join(", ")}`);
|
|
1741
|
+
if (pc.denied?.length) log4(` ${red("DENY")} ${pc.denied.join(", ")}`);
|
|
1780
1742
|
}
|
|
1781
1743
|
if (rule.commandConstraints) {
|
|
1782
1744
|
const cc = rule.commandConstraints;
|
|
1783
|
-
if (cc.allowed?.length)
|
|
1784
|
-
if (cc.denied?.length)
|
|
1745
|
+
if (cc.allowed?.length) log4(` ${green("CMDS")} ${cc.allowed.join(", ")}`);
|
|
1746
|
+
if (cc.denied?.length) log4(` ${red("DENY")} ${cc.denied.join(", ")}`);
|
|
1785
1747
|
}
|
|
1786
1748
|
if (rule.filenameConstraints) {
|
|
1787
1749
|
const fc = rule.filenameConstraints;
|
|
1788
|
-
if (fc.allowed?.length)
|
|
1789
|
-
if (fc.denied?.length)
|
|
1750
|
+
if (fc.allowed?.length) log4(` ${green("FILES")} ${fc.allowed.join(", ")}`);
|
|
1751
|
+
if (fc.denied?.length) log4(` ${red("DENY")} ${fc.denied.join(", ")}`);
|
|
1790
1752
|
}
|
|
1791
1753
|
if (rule.urlConstraints) {
|
|
1792
1754
|
const uc = rule.urlConstraints;
|
|
1793
|
-
if (uc.allowed?.length)
|
|
1794
|
-
if (uc.denied?.length)
|
|
1755
|
+
if (uc.allowed?.length) log4(` ${green("URLS")} ${uc.allowed.join(", ")}`);
|
|
1756
|
+
if (uc.denied?.length) log4(` ${red("DENY")} ${uc.denied.join(", ")}`);
|
|
1795
1757
|
}
|
|
1796
1758
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1759
|
+
log4("");
|
|
1760
|
+
log4(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1761
|
+
log4("");
|
|
1800
1762
|
}
|
|
1801
1763
|
async function pull(apiKey, file, policyId) {
|
|
1802
1764
|
if (!policyId) {
|
|
1803
1765
|
const policies = await listPolicies(apiKey);
|
|
1804
1766
|
if (policies.length === 0) {
|
|
1805
|
-
|
|
1767
|
+
log4(red("No policies found. Create one in the dashboard first."));
|
|
1806
1768
|
process.exit(1);
|
|
1807
1769
|
}
|
|
1808
1770
|
if (policies.length === 1) {
|
|
1809
1771
|
policyId = policies[0].id;
|
|
1810
|
-
|
|
1772
|
+
log4(dim(`Auto-selecting only policy: ${policyId}`));
|
|
1811
1773
|
} else {
|
|
1812
|
-
|
|
1813
|
-
|
|
1774
|
+
log4(yellow(`Found ${policies.length} policies:`));
|
|
1775
|
+
log4("");
|
|
1814
1776
|
for (const p of policies) {
|
|
1815
|
-
|
|
1777
|
+
log4(` ${cyan(p.id)} ${p.name} ${dim(`v${p.version ?? "?"}`)}`);
|
|
1816
1778
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1779
|
+
log4("");
|
|
1780
|
+
log4("Use --policy-id <ID> to specify which one to pull.");
|
|
1819
1781
|
process.exit(1);
|
|
1820
1782
|
}
|
|
1821
1783
|
}
|
|
1822
|
-
|
|
1784
|
+
log4(`Pulling ${cyan(policyId)} from dashboard...`);
|
|
1823
1785
|
const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
1824
1786
|
const { id: _id, ...policyWithoutId } = policy;
|
|
1825
1787
|
const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
|
|
1826
1788
|
writeFileSync5(file, json, "utf-8");
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1789
|
+
log4("");
|
|
1790
|
+
log4(green(" Saved to: ") + file);
|
|
1791
|
+
log4(` ${dim("Name:")} ${policy.name}`);
|
|
1792
|
+
log4(` ${dim("Version:")} ${policy.version}`);
|
|
1793
|
+
log4(` ${dim("Rules:")} ${policy.rules.length}`);
|
|
1794
|
+
log4("");
|
|
1795
|
+
log4(dim("The policy file does not contain an ID."));
|
|
1796
|
+
log4(dim("Use --policy-id to specify the target when pushing/pulling."));
|
|
1797
|
+
log4("");
|
|
1836
1798
|
}
|
|
1837
1799
|
async function push(apiKey, file, policyId) {
|
|
1838
|
-
if (!
|
|
1839
|
-
|
|
1800
|
+
if (!existsSync7(file)) {
|
|
1801
|
+
log4(red(`ERROR: File not found: ${file}`));
|
|
1840
1802
|
process.exit(1);
|
|
1841
1803
|
}
|
|
1842
1804
|
if (!policyId) {
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1805
|
+
log4(red("ERROR: --policy-id is required for push."));
|
|
1806
|
+
log4("");
|
|
1807
|
+
log4("This determines which cloud policy to update.");
|
|
1808
|
+
log4("");
|
|
1809
|
+
log4("Usage:");
|
|
1810
|
+
log4(" solongate-proxy push --policy-id my-policy");
|
|
1811
|
+
log4(" solongate-proxy push --policy-id my-policy --file custom.json");
|
|
1812
|
+
log4("");
|
|
1813
|
+
log4("List your policies:");
|
|
1814
|
+
log4(" solongate-proxy list");
|
|
1853
1815
|
process.exit(1);
|
|
1854
1816
|
}
|
|
1855
|
-
const content =
|
|
1817
|
+
const content = readFileSync6(file, "utf-8");
|
|
1856
1818
|
let policy;
|
|
1857
1819
|
try {
|
|
1858
1820
|
policy = JSON.parse(content);
|
|
1859
1821
|
} catch {
|
|
1860
|
-
|
|
1822
|
+
log4(red(`ERROR: Invalid JSON in ${file}`));
|
|
1861
1823
|
process.exit(1);
|
|
1862
1824
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1825
|
+
log4(`Pushing to ${cyan(policyId)}...`);
|
|
1826
|
+
log4(` ${dim("File:")} ${file}`);
|
|
1827
|
+
log4(` ${dim("Name:")} ${policy.name || "Unnamed"}`);
|
|
1828
|
+
log4(` ${dim("Rules:")} ${(policy.rules || []).length}`);
|
|
1867
1829
|
const checkRes = await fetch(`${API_URL}/api/v1/policies/${policyId}`, {
|
|
1868
1830
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
1869
1831
|
});
|
|
@@ -1885,15 +1847,15 @@ async function push(apiKey, file, policyId) {
|
|
|
1885
1847
|
});
|
|
1886
1848
|
if (!res.ok) {
|
|
1887
1849
|
const body = await res.text().catch(() => "");
|
|
1888
|
-
|
|
1850
|
+
log4(red(`ERROR: Push failed (${res.status}): ${body}`));
|
|
1889
1851
|
process.exit(1);
|
|
1890
1852
|
}
|
|
1891
1853
|
const data = await res.json();
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1854
|
+
log4("");
|
|
1855
|
+
log4(green(` Pushed to cloud: v${data._version ?? "created"}`));
|
|
1856
|
+
log4(` ${dim("Policy ID:")} ${policyId}`);
|
|
1857
|
+
log4(` ${dim("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
|
|
1858
|
+
log4("");
|
|
1897
1859
|
}
|
|
1898
1860
|
async function main4() {
|
|
1899
1861
|
const { command, apiKey, file, policyId } = parseCliArgs();
|
|
@@ -1905,32 +1867,32 @@ async function main4() {
|
|
|
1905
1867
|
} else if (command === "list" || command === "ls") {
|
|
1906
1868
|
await list(apiKey, policyId);
|
|
1907
1869
|
} else {
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1870
|
+
log4(red(`Unknown command: ${command}`));
|
|
1871
|
+
log4("");
|
|
1872
|
+
log4(bold("Usage:"));
|
|
1873
|
+
log4(" solongate-proxy list List all policies");
|
|
1874
|
+
log4(" solongate-proxy list --policy-id <ID> Show policy details");
|
|
1875
|
+
log4(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
|
|
1876
|
+
log4(" solongate-proxy push --policy-id <ID> Push local file to cloud");
|
|
1877
|
+
log4("");
|
|
1878
|
+
log4(bold("Flags:"));
|
|
1879
|
+
log4(" --policy-id, --id <ID> Cloud policy ID (required for push)");
|
|
1880
|
+
log4(" --file, -f <path> Local file path (default: policy.json)");
|
|
1881
|
+
log4(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
|
|
1882
|
+
log4("");
|
|
1921
1883
|
process.exit(1);
|
|
1922
1884
|
}
|
|
1923
1885
|
} catch (err) {
|
|
1924
|
-
|
|
1886
|
+
log4(red(`ERROR: ${err instanceof Error ? err.message : String(err)}`));
|
|
1925
1887
|
process.exit(1);
|
|
1926
1888
|
}
|
|
1927
1889
|
}
|
|
1928
|
-
var
|
|
1890
|
+
var log4, dim, bold, green, red, yellow, cyan, magenta, API_URL;
|
|
1929
1891
|
var init_pull_push = __esm({
|
|
1930
1892
|
"src/pull-push.ts"() {
|
|
1931
1893
|
"use strict";
|
|
1932
1894
|
init_config();
|
|
1933
|
-
|
|
1895
|
+
log4 = (...args) => process.stderr.write(`${args.map(String).join(" ")}
|
|
1934
1896
|
`);
|
|
1935
1897
|
dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
1936
1898
|
bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
@@ -1965,6 +1927,8 @@ import {
|
|
|
1965
1927
|
ListResourceTemplatesRequestSchema
|
|
1966
1928
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1967
1929
|
import { createServer as createHttpServer } from "http";
|
|
1930
|
+
import { resolve as resolve2 } from "path";
|
|
1931
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1968
1932
|
|
|
1969
1933
|
// ../sdk-ts/dist/index.js
|
|
1970
1934
|
import { z } from "zod";
|
|
@@ -1979,21 +1943,6 @@ var __export = (target, all) => {
|
|
|
1979
1943
|
for (var name in all)
|
|
1980
1944
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
1981
1945
|
};
|
|
1982
|
-
var DEFAULT_ADVANCED_DETECTION_CONFIG;
|
|
1983
|
-
var init_types = __esm2({
|
|
1984
|
-
"src/prompt-injection/types.ts"() {
|
|
1985
|
-
DEFAULT_ADVANCED_DETECTION_CONFIG = {
|
|
1986
|
-
enabled: true,
|
|
1987
|
-
threshold: 0.5,
|
|
1988
|
-
weights: {
|
|
1989
|
-
rules: 0.3,
|
|
1990
|
-
embedding: 0.3,
|
|
1991
|
-
classifier: 0.4
|
|
1992
|
-
},
|
|
1993
|
-
onModelDownloadStart: void 0
|
|
1994
|
-
};
|
|
1995
|
-
}
|
|
1996
|
-
});
|
|
1997
1946
|
function runStage1Rules(input) {
|
|
1998
1947
|
const matchedCategories = [];
|
|
1999
1948
|
let maxWeight = 0;
|
|
@@ -2049,7 +1998,7 @@ var init_stage1_rules = __esm2({
|
|
|
2049
1998
|
patterns: [
|
|
2050
1999
|
/\bignore\s+(all\s+)?(previous|prior|above|earlier)\s+(instructions?|prompts?|rules?|directives?)\b/i,
|
|
2051
2000
|
/\bdisregard\s+(all\s+)?(previous|prior|above|earlier|your)\s+(instructions?|prompts?|rules?|guidelines?)\b/i,
|
|
2052
|
-
/\bforget\s+(all\s+)?(your|the|previous|prior)\
|
|
2001
|
+
/\bforget\s+(all\s+|everything\s+)?(your|the|previous|prior|above|earlier)\b/i,
|
|
2053
2002
|
/\boverride\s+(the\s+)?(system|previous|current)\s+(prompt|instructions?|rules?|settings?)\b/i,
|
|
2054
2003
|
/\bdo\s+not\s+follow\s+(your|the|any)\s+(instructions?|rules?|guidelines?)\b/i,
|
|
2055
2004
|
/\bcancel\s+(all\s+)?(prior|previous)\s+(directives?|instructions?)\b/i,
|
|
@@ -2108,19 +2057,34 @@ var init_stage1_rules = __esm2({
|
|
|
2108
2057
|
name: "multi_language",
|
|
2109
2058
|
weight: 0.7,
|
|
2110
2059
|
patterns: [
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2060
|
+
/ignor(iere|a|e[zs]?)\s+(alle|todas?|toutes?|tüm|все)/iu,
|
|
2061
|
+
/игнорируйте/iu,
|
|
2062
|
+
/yoksay/iu,
|
|
2063
|
+
/vorherigen?\s+Anweisungen/iu,
|
|
2064
|
+
/instrucciones\s+anteriores/iu,
|
|
2065
|
+
/instructions?\s+pr[eé]c[eé]dentes?/iu,
|
|
2066
|
+
/önceki\s+talimatlar/iu
|
|
2118
2067
|
]
|
|
2119
2068
|
}
|
|
2120
2069
|
];
|
|
2121
2070
|
ADDITIONAL_MATCH_BONUS = 0.05;
|
|
2122
2071
|
}
|
|
2123
2072
|
});
|
|
2073
|
+
var DEFAULT_ADVANCED_DETECTION_CONFIG;
|
|
2074
|
+
var init_types = __esm2({
|
|
2075
|
+
"src/prompt-injection/types.ts"() {
|
|
2076
|
+
DEFAULT_ADVANCED_DETECTION_CONFIG = {
|
|
2077
|
+
enabled: true,
|
|
2078
|
+
threshold: 0.5,
|
|
2079
|
+
weights: {
|
|
2080
|
+
rules: 0.3,
|
|
2081
|
+
embedding: 0.3,
|
|
2082
|
+
classifier: 0.4
|
|
2083
|
+
},
|
|
2084
|
+
onModelDownloadStart: void 0
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2124
2088
|
var ATTACK_VECTORS;
|
|
2125
2089
|
var init_attack_vectors = __esm2({
|
|
2126
2090
|
"src/prompt-injection/attack-vectors.ts"() {
|
|
@@ -2242,37 +2206,49 @@ async function getOrCreatePipeline(task, model, onDownloadStart) {
|
|
|
2242
2206
|
if (pipelineCache.has(cacheKey)) {
|
|
2243
2207
|
return pipelineCache.get(cacheKey);
|
|
2244
2208
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
const modelSizes = {
|
|
2248
|
-
"Xenova/all-MiniLM-L6-v2": 22,
|
|
2249
|
-
"Xenova/deberta-v3-base-prompt-injection-v2": 184
|
|
2250
|
-
};
|
|
2251
|
-
console.warn(
|
|
2252
|
-
`[SolonGate] Downloading model "${model}" (~${modelSizes[model] ?? "?"}MB) for prompt injection detection. This is a one-time download cached at ~/.cache/huggingface/hub/`
|
|
2253
|
-
);
|
|
2254
|
-
if (onDownloadStart) {
|
|
2255
|
-
onDownloadStart(model, modelSizes[model] ?? 0);
|
|
2256
|
-
}
|
|
2257
|
-
try {
|
|
2258
|
-
const pipe = await transformers.pipeline(task, model);
|
|
2259
|
-
pipelineCache.set(cacheKey, pipe);
|
|
2260
|
-
return pipe;
|
|
2261
|
-
} catch (err) {
|
|
2262
|
-
console.warn(`[SolonGate] Failed to load model "${model}":`, err);
|
|
2263
|
-
return null;
|
|
2209
|
+
if (pipelineInflight.has(cacheKey)) {
|
|
2210
|
+
return pipelineInflight.get(cacheKey);
|
|
2264
2211
|
}
|
|
2212
|
+
const promise = (async () => {
|
|
2213
|
+
const transformers = await getTransformers();
|
|
2214
|
+
if (!transformers) return null;
|
|
2215
|
+
const modelSizes = {
|
|
2216
|
+
"Xenova/all-MiniLM-L6-v2": 22,
|
|
2217
|
+
"Xenova/deberta-v3-base-prompt-injection-v2": 184
|
|
2218
|
+
};
|
|
2219
|
+
if (onDownloadStart) {
|
|
2220
|
+
onDownloadStart(model, modelSizes[model] ?? 0);
|
|
2221
|
+
} else {
|
|
2222
|
+
console.warn(
|
|
2223
|
+
`[SolonGate] Downloading model "${model}" (~${modelSizes[model] ?? "?"}MB) for prompt injection detection. This is a one-time download cached at ~/.cache/huggingface/hub/`
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
try {
|
|
2227
|
+
const pipe = await transformers.pipeline(task, model);
|
|
2228
|
+
pipelineCache.set(cacheKey, pipe);
|
|
2229
|
+
return pipe;
|
|
2230
|
+
} catch (err) {
|
|
2231
|
+
console.warn(`[SolonGate] Failed to load model "${model}":`, err);
|
|
2232
|
+
return null;
|
|
2233
|
+
} finally {
|
|
2234
|
+
pipelineInflight.delete(cacheKey);
|
|
2235
|
+
}
|
|
2236
|
+
})();
|
|
2237
|
+
pipelineInflight.set(cacheKey, promise);
|
|
2238
|
+
return promise;
|
|
2265
2239
|
}
|
|
2266
2240
|
var transformersModule;
|
|
2267
2241
|
var transformersChecked;
|
|
2268
2242
|
var loadingPromise;
|
|
2269
2243
|
var pipelineCache;
|
|
2244
|
+
var pipelineInflight;
|
|
2270
2245
|
var init_model_manager = __esm2({
|
|
2271
2246
|
"src/prompt-injection/model-manager.ts"() {
|
|
2272
2247
|
transformersModule = null;
|
|
2273
2248
|
transformersChecked = false;
|
|
2274
2249
|
loadingPromise = null;
|
|
2275
2250
|
pipelineCache = /* @__PURE__ */ new Map();
|
|
2251
|
+
pipelineInflight = /* @__PURE__ */ new Map();
|
|
2276
2252
|
}
|
|
2277
2253
|
});
|
|
2278
2254
|
function cosineSimilarity(a, b) {
|
|
@@ -2288,10 +2264,11 @@ function cosineSimilarity(a, b) {
|
|
|
2288
2264
|
return denom === 0 ? 0 : dotProduct / denom;
|
|
2289
2265
|
}
|
|
2290
2266
|
async function embed(pipe, texts) {
|
|
2267
|
+
const output = await pipe(texts, { pooling: "mean", normalize: true });
|
|
2268
|
+
const dim2 = output.dims?.[1] ?? output.data.length / texts.length;
|
|
2291
2269
|
const results = [];
|
|
2292
|
-
for (
|
|
2293
|
-
|
|
2294
|
-
results.push(new Float32Array(output.data));
|
|
2270
|
+
for (let i = 0; i < texts.length; i++) {
|
|
2271
|
+
results.push(new Float32Array(output.data.slice(i * dim2, (i + 1) * dim2)));
|
|
2295
2272
|
}
|
|
2296
2273
|
return results;
|
|
2297
2274
|
}
|
|
@@ -2652,6 +2629,7 @@ function createDeniedToolResult(reason) {
|
|
|
2652
2629
|
isError: true
|
|
2653
2630
|
};
|
|
2654
2631
|
}
|
|
2632
|
+
init_stage1_rules();
|
|
2655
2633
|
var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
|
|
2656
2634
|
pathTraversal: true,
|
|
2657
2635
|
shellInjection: true,
|
|
@@ -2716,61 +2694,14 @@ function detectHiddenDirective(value) {
|
|
|
2716
2694
|
}
|
|
2717
2695
|
return false;
|
|
2718
2696
|
}
|
|
2719
|
-
var
|
|
2720
|
-
/\u200B/,
|
|
2721
|
-
// Zero-width space
|
|
2722
|
-
/\u200C/,
|
|
2723
|
-
// Zero-width non-joiner
|
|
2724
|
-
/\u200D/,
|
|
2725
|
-
// Zero-width joiner
|
|
2726
|
-
/\u200E/,
|
|
2727
|
-
// Left-to-right mark
|
|
2728
|
-
/\u200F/,
|
|
2729
|
-
// Right-to-left mark
|
|
2730
|
-
/\u2060/,
|
|
2731
|
-
// Word joiner
|
|
2732
|
-
/\u2061/,
|
|
2733
|
-
// Function application
|
|
2734
|
-
/\u2062/,
|
|
2735
|
-
// Invisible times
|
|
2736
|
-
/\u2063/,
|
|
2737
|
-
// Invisible separator
|
|
2738
|
-
/\u2064/,
|
|
2739
|
-
// Invisible plus
|
|
2740
|
-
/\uFEFF/,
|
|
2741
|
-
// Zero-width no-break space (BOM)
|
|
2742
|
-
/\u202A/,
|
|
2743
|
-
// Left-to-right embedding
|
|
2744
|
-
/\u202B/,
|
|
2745
|
-
// Right-to-left embedding
|
|
2746
|
-
/\u202C/,
|
|
2747
|
-
// Pop directional formatting
|
|
2748
|
-
/\u202D/,
|
|
2749
|
-
// Left-to-right override
|
|
2750
|
-
/\u202E/,
|
|
2751
|
-
// Right-to-left override (text reversal attack)
|
|
2752
|
-
/\u2066/,
|
|
2753
|
-
// Left-to-right isolate
|
|
2754
|
-
/\u2067/,
|
|
2755
|
-
// Right-to-left isolate
|
|
2756
|
-
/\u2068/,
|
|
2757
|
-
// First strong isolate
|
|
2758
|
-
/\u2069/,
|
|
2759
|
-
// Pop directional isolate
|
|
2760
|
-
/[\uE000-\uF8FF]/,
|
|
2761
|
-
// Private Use Area
|
|
2762
|
-
/[\uDB80-\uDBFF][\uDC00-\uDFFF]/
|
|
2763
|
-
// Supplementary Private Use Area
|
|
2764
|
-
];
|
|
2697
|
+
var INVISIBLE_UNICODE_RE = /[\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF\uE000-\uF8FF]|[\uDB80-\uDBFF][\uDC00-\uDFFF]/g;
|
|
2765
2698
|
var INVISIBLE_CHAR_THRESHOLD = 3;
|
|
2766
2699
|
function detectInvisibleUnicode(value) {
|
|
2700
|
+
INVISIBLE_UNICODE_RE.lastIndex = 0;
|
|
2767
2701
|
let count = 0;
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
if (
|
|
2771
|
-
count += matches.length;
|
|
2772
|
-
if (count >= INVISIBLE_CHAR_THRESHOLD) return true;
|
|
2773
|
-
}
|
|
2702
|
+
while (INVISIBLE_UNICODE_RE.exec(value)) {
|
|
2703
|
+
count++;
|
|
2704
|
+
if (count >= INVISIBLE_CHAR_THRESHOLD) return true;
|
|
2774
2705
|
}
|
|
2775
2706
|
return false;
|
|
2776
2707
|
}
|
|
@@ -3026,9 +2957,52 @@ var COMMAND_HEURISTICS = [
|
|
|
3026
2957
|
// network commands
|
|
3027
2958
|
/^(rm|del|rmdir)\s+/i,
|
|
3028
2959
|
// destructive commands
|
|
3029
|
-
/^(cat|type|more|less)\s+.*[/\\]/i
|
|
2960
|
+
/^(cat|type|more|less)\s+.*[/\\]/i,
|
|
3030
2961
|
// file read commands with paths
|
|
2962
|
+
/^(eval|source)\s+/i,
|
|
2963
|
+
// eval/source wrappers
|
|
2964
|
+
/^(printenv|env|set)\b/i,
|
|
2965
|
+
// environment variable leak
|
|
2966
|
+
/^(cat|head|tail|more|less|strings|xxd|od|hexdump|bat)\s+/i
|
|
2967
|
+
// file read commands
|
|
2968
|
+
];
|
|
2969
|
+
var SUBSHELL_WRAPPERS = [
|
|
2970
|
+
/^(?:sh|bash|zsh|fish|dash|ksh)\s+-c\s+['"](.+?)['"]\s*$/i,
|
|
2971
|
+
/^(?:sh|bash|zsh|fish|dash|ksh)\s+-c\s+(.+)$/i,
|
|
2972
|
+
/^eval\s+['"](.+?)['"]\s*$/i,
|
|
2973
|
+
/^eval\s+(.+)$/i,
|
|
2974
|
+
/^(?:sh|bash|zsh|fish|dash|ksh)\s+<<\s*['"]?(\w+)['"]?\n([\s\S]+?)\n\1$/i
|
|
3031
2975
|
];
|
|
2976
|
+
var MAX_RECURSION_DEPTH = 8;
|
|
2977
|
+
function extractInnerCommands(command, depth = 0) {
|
|
2978
|
+
const results = [command];
|
|
2979
|
+
if (depth >= MAX_RECURSION_DEPTH) return results;
|
|
2980
|
+
const trimmed = command.trim();
|
|
2981
|
+
for (const pattern of SUBSHELL_WRAPPERS) {
|
|
2982
|
+
const match = trimmed.match(pattern);
|
|
2983
|
+
if (match) {
|
|
2984
|
+
const inner = (match[2] ?? match[1] ?? "").trim();
|
|
2985
|
+
if (inner) {
|
|
2986
|
+
results.push(inner);
|
|
2987
|
+
const nested = extractInnerCommands(inner, depth + 1);
|
|
2988
|
+
for (const n of nested) {
|
|
2989
|
+
if (n !== inner) results.push(n);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
break;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
const chainParts = trimmed.split(/\s*(?:&&|;)\s*/);
|
|
2996
|
+
if (chainParts.length > 1) {
|
|
2997
|
+
for (const part of chainParts) {
|
|
2998
|
+
const p = part.trim();
|
|
2999
|
+
if (p && p !== trimmed && !p.includes("=")) {
|
|
3000
|
+
results.push(p);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
return [...new Set(results)];
|
|
3005
|
+
}
|
|
3032
3006
|
function extractCommandArguments(args) {
|
|
3033
3007
|
const commands = [];
|
|
3034
3008
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3065,6 +3039,16 @@ function extractCommandArguments(args) {
|
|
|
3065
3039
|
for (const [key, value] of Object.entries(args)) {
|
|
3066
3040
|
scanValue(key, value);
|
|
3067
3041
|
}
|
|
3042
|
+
const expanded = [];
|
|
3043
|
+
for (const cmd of commands) {
|
|
3044
|
+
for (const inner of extractInnerCommands(cmd)) {
|
|
3045
|
+
if (!seen.has(inner)) {
|
|
3046
|
+
seen.add(inner);
|
|
3047
|
+
expanded.push(inner);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
commands.push(...expanded);
|
|
3068
3052
|
return commands;
|
|
3069
3053
|
}
|
|
3070
3054
|
function matchCommandPattern(command, pattern) {
|
|
@@ -3109,6 +3093,37 @@ function isCommandAllowed(command, constraints) {
|
|
|
3109
3093
|
}
|
|
3110
3094
|
return true;
|
|
3111
3095
|
}
|
|
3096
|
+
var SENSITIVE_FILENAMES = [
|
|
3097
|
+
".env",
|
|
3098
|
+
".env.local",
|
|
3099
|
+
".env.production",
|
|
3100
|
+
".env.development",
|
|
3101
|
+
".env.staging",
|
|
3102
|
+
".env.test",
|
|
3103
|
+
".env.example",
|
|
3104
|
+
"credentials.json",
|
|
3105
|
+
"secrets.json",
|
|
3106
|
+
"secrets.yaml",
|
|
3107
|
+
"secrets.yml",
|
|
3108
|
+
".npmrc",
|
|
3109
|
+
".pypirc",
|
|
3110
|
+
".netrc",
|
|
3111
|
+
".docker/config.json",
|
|
3112
|
+
"id_rsa",
|
|
3113
|
+
"id_dsa",
|
|
3114
|
+
"id_ecdsa",
|
|
3115
|
+
"id_ed25519",
|
|
3116
|
+
"authorized_keys",
|
|
3117
|
+
"known_hosts",
|
|
3118
|
+
"policy.json",
|
|
3119
|
+
".mcp.json",
|
|
3120
|
+
"guard.mjs",
|
|
3121
|
+
"audit.mjs",
|
|
3122
|
+
"settings.json"
|
|
3123
|
+
];
|
|
3124
|
+
function stripShellSyntax(value) {
|
|
3125
|
+
return value.replace(/\$\(/g, " ").replace(/\)/g, " ").replace(/`/g, " ").replace(/\$\{[^}]*\}/g, " ").replace(/\$\w+/g, " ").replace(/['"]/g, " ").replace(/>{1,2}/g, " ").replace(/<{1,3}/g, " ").replace(/[|;&]/g, " ").replace(/\+=/g, " ").replace(/(\w)=/g, "$1 ").replace(/[{}]/g, " ").replace(/[()]/g, " ").replace(/\\/g, " ").replace(/\s+/g, " ").trim();
|
|
3126
|
+
}
|
|
3112
3127
|
function extractFilenames(args) {
|
|
3113
3128
|
const filenames = [];
|
|
3114
3129
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3123,30 +3138,78 @@ function extractFilenames(args) {
|
|
|
3123
3138
|
if (typeof value === "string") {
|
|
3124
3139
|
const trimmed = value.trim();
|
|
3125
3140
|
if (!trimmed) return;
|
|
3126
|
-
|
|
3127
|
-
if (
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
const
|
|
3131
|
-
if (
|
|
3132
|
-
addFilename(
|
|
3141
|
+
const isUrl = /^https?:\/\//i.test(trimmed);
|
|
3142
|
+
if (isUrl) return;
|
|
3143
|
+
const lower = trimmed.toLowerCase();
|
|
3144
|
+
for (const sensitive of SENSITIVE_FILENAMES) {
|
|
3145
|
+
const sl = sensitive.toLowerCase();
|
|
3146
|
+
if (lower.includes(sl)) {
|
|
3147
|
+
addFilename(sensitive);
|
|
3133
3148
|
}
|
|
3134
|
-
return;
|
|
3135
3149
|
}
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3150
|
+
const stripped = stripShellSyntax(trimmed);
|
|
3151
|
+
const words = stripped.split(/\s+/).filter(Boolean);
|
|
3152
|
+
for (const word of words) {
|
|
3153
|
+
if (looksLikeFilename(word)) {
|
|
3154
|
+
addFilename(word);
|
|
3155
|
+
}
|
|
3156
|
+
if (word.includes("/")) {
|
|
3157
|
+
const parts = word.split("/");
|
|
3158
|
+
const basename = parts[parts.length - 1];
|
|
3159
|
+
if (basename && looksLikeFilename(basename)) {
|
|
3160
|
+
addFilename(basename);
|
|
3144
3161
|
}
|
|
3145
3162
|
}
|
|
3146
|
-
|
|
3163
|
+
if (word.includes("*") || word.includes("?")) {
|
|
3164
|
+
for (const expanded of expandSensitiveGlob(word)) {
|
|
3165
|
+
addFilename(expanded);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
const quotedParts = [];
|
|
3170
|
+
const quotedMatches = trimmed.matchAll(/['"]([^'"]*)['"]/g);
|
|
3171
|
+
for (const m of quotedMatches) {
|
|
3172
|
+
if (m[1]) quotedParts.push(m[1]);
|
|
3147
3173
|
}
|
|
3148
|
-
|
|
3149
|
-
|
|
3174
|
+
const assignMatches = trimmed.matchAll(/\b\w+=([^\s&;|'"]+)/g);
|
|
3175
|
+
for (const m of assignMatches) {
|
|
3176
|
+
if (m[1]) quotedParts.push(m[1]);
|
|
3177
|
+
}
|
|
3178
|
+
const cappedParts = quotedParts.length > 8 ? quotedParts.slice(0, 8) : quotedParts;
|
|
3179
|
+
if (cappedParts.length >= 2) {
|
|
3180
|
+
for (let i = 0; i < cappedParts.length; i++) {
|
|
3181
|
+
for (let j = i + 1; j < cappedParts.length; j++) {
|
|
3182
|
+
const concat = cappedParts[i] + cappedParts[j];
|
|
3183
|
+
if (looksLikeFilename(concat)) addFilename(concat);
|
|
3184
|
+
const concatLower = concat.toLowerCase();
|
|
3185
|
+
for (const sensitive of SENSITIVE_FILENAMES) {
|
|
3186
|
+
if (concatLower === sensitive.toLowerCase()) {
|
|
3187
|
+
addFilename(sensitive);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
for (let k = j + 1; k < cappedParts.length; k++) {
|
|
3191
|
+
const triple = concat + cappedParts[k];
|
|
3192
|
+
if (looksLikeFilename(triple)) addFilename(triple);
|
|
3193
|
+
const tripleLower = triple.toLowerCase();
|
|
3194
|
+
for (const sensitive of SENSITIVE_FILENAMES) {
|
|
3195
|
+
if (tripleLower === sensitive.toLowerCase()) {
|
|
3196
|
+
addFilename(sensitive);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
for (const word of words) {
|
|
3204
|
+
const wordLower = word.toLowerCase().replace(/[*?]/g, "");
|
|
3205
|
+
if (wordLower.length >= 3) {
|
|
3206
|
+
for (const sensitive of SENSITIVE_FILENAMES) {
|
|
3207
|
+
const sl = sensitive.toLowerCase();
|
|
3208
|
+
if (sl.startsWith(wordLower) && wordLower !== sl) {
|
|
3209
|
+
addFilename(sensitive);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3150
3213
|
}
|
|
3151
3214
|
return;
|
|
3152
3215
|
}
|
|
@@ -3165,24 +3228,49 @@ function extractFilenames(args) {
|
|
|
3165
3228
|
}
|
|
3166
3229
|
return filenames;
|
|
3167
3230
|
}
|
|
3231
|
+
function expandSensitiveGlob(pattern) {
|
|
3232
|
+
const p = pattern.toLowerCase();
|
|
3233
|
+
const matches = [];
|
|
3234
|
+
if (p === "*") return matches;
|
|
3235
|
+
for (const filename of SENSITIVE_FILENAMES) {
|
|
3236
|
+
const f = filename.toLowerCase();
|
|
3237
|
+
const startsWithStar = p.startsWith("*");
|
|
3238
|
+
const endsWithStar = p.endsWith("*");
|
|
3239
|
+
if (startsWithStar && endsWithStar) {
|
|
3240
|
+
const infix = p.slice(1, -1);
|
|
3241
|
+
if (infix && f.includes(infix)) matches.push(filename);
|
|
3242
|
+
} else if (endsWithStar) {
|
|
3243
|
+
const prefix = p.slice(0, -1);
|
|
3244
|
+
if (f.startsWith(prefix)) matches.push(filename);
|
|
3245
|
+
} else if (startsWithStar) {
|
|
3246
|
+
const suffix = p.slice(1);
|
|
3247
|
+
if (f.endsWith(suffix)) matches.push(filename);
|
|
3248
|
+
} else if (p.includes("?")) {
|
|
3249
|
+
const regex = new RegExp("^" + p.replace(/\?/g, ".").replace(/\*/g, ".*") + "$", "i");
|
|
3250
|
+
if (regex.test(f)) matches.push(filename);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
return matches;
|
|
3254
|
+
}
|
|
3255
|
+
var KNOWN_EXTENSIONLESS_FILES = /* @__PURE__ */ new Set([
|
|
3256
|
+
"id_rsa",
|
|
3257
|
+
"id_dsa",
|
|
3258
|
+
"id_ecdsa",
|
|
3259
|
+
"id_ed25519",
|
|
3260
|
+
"authorized_keys",
|
|
3261
|
+
"known_hosts",
|
|
3262
|
+
"makefile",
|
|
3263
|
+
"dockerfile",
|
|
3264
|
+
"vagrantfile",
|
|
3265
|
+
"gemfile",
|
|
3266
|
+
"rakefile",
|
|
3267
|
+
"procfile",
|
|
3268
|
+
"environ"
|
|
3269
|
+
]);
|
|
3168
3270
|
function looksLikeFilename(s) {
|
|
3169
3271
|
if (s.startsWith(".")) return true;
|
|
3170
3272
|
if (/\.\w+$/.test(s)) return true;
|
|
3171
|
-
|
|
3172
|
-
"id_rsa",
|
|
3173
|
-
"id_dsa",
|
|
3174
|
-
"id_ecdsa",
|
|
3175
|
-
"id_ed25519",
|
|
3176
|
-
"authorized_keys",
|
|
3177
|
-
"known_hosts",
|
|
3178
|
-
"makefile",
|
|
3179
|
-
"dockerfile",
|
|
3180
|
-
"vagrantfile",
|
|
3181
|
-
"gemfile",
|
|
3182
|
-
"rakefile",
|
|
3183
|
-
"procfile"
|
|
3184
|
-
]);
|
|
3185
|
-
if (knownFiles.has(s.toLowerCase())) return true;
|
|
3273
|
+
if (KNOWN_EXTENSIONLESS_FILES.has(s.toLowerCase())) return true;
|
|
3186
3274
|
return false;
|
|
3187
3275
|
}
|
|
3188
3276
|
function matchFilenamePattern(filename, pattern) {
|
|
@@ -3473,9 +3561,7 @@ function urlConstraintsMatch(constraints, args) {
|
|
|
3473
3561
|
}
|
|
3474
3562
|
function evaluatePolicy(policySet, request) {
|
|
3475
3563
|
const startTime = performance.now();
|
|
3476
|
-
const sortedRules =
|
|
3477
|
-
(a, b) => a.priority - b.priority
|
|
3478
|
-
);
|
|
3564
|
+
const sortedRules = policySet.rules;
|
|
3479
3565
|
for (const rule of sortedRules) {
|
|
3480
3566
|
if (ruleMatchesRequest(rule, request)) {
|
|
3481
3567
|
const endTime2 = performance.now();
|
|
@@ -3497,7 +3583,6 @@ function evaluatePolicy(policySet, request) {
|
|
|
3497
3583
|
evaluationTimeMs: endTime - startTime,
|
|
3498
3584
|
metadata: {
|
|
3499
3585
|
evaluatedRules: sortedRules.length,
|
|
3500
|
-
ruleIds: sortedRules.map((r) => r.id),
|
|
3501
3586
|
requestContext: {
|
|
3502
3587
|
tool: request.toolName,
|
|
3503
3588
|
arguments: Object.keys(request.arguments ?? {})
|
|
@@ -3505,31 +3590,6 @@ function evaluatePolicy(policySet, request) {
|
|
|
3505
3590
|
}
|
|
3506
3591
|
};
|
|
3507
3592
|
}
|
|
3508
|
-
function validatePolicyRule(input) {
|
|
3509
|
-
const errors = [];
|
|
3510
|
-
const warnings = [];
|
|
3511
|
-
const result = PolicyRuleSchema.safeParse(input);
|
|
3512
|
-
if (!result.success) {
|
|
3513
|
-
return {
|
|
3514
|
-
valid: false,
|
|
3515
|
-
errors: result.error.errors.map(
|
|
3516
|
-
(e) => `${e.path.join(".")}: ${e.message}`
|
|
3517
|
-
),
|
|
3518
|
-
warnings: []
|
|
3519
|
-
};
|
|
3520
|
-
}
|
|
3521
|
-
const rule = result.data;
|
|
3522
|
-
if (rule.toolPattern === "*" && rule.effect === "ALLOW") {
|
|
3523
|
-
warnings.push(UNSAFE_CONFIGURATION_WARNINGS.WILDCARD_ALLOW);
|
|
3524
|
-
}
|
|
3525
|
-
if (rule.minimumTrustLevel === "TRUSTED") {
|
|
3526
|
-
warnings.push(UNSAFE_CONFIGURATION_WARNINGS.TRUSTED_LEVEL_EXTERNAL);
|
|
3527
|
-
}
|
|
3528
|
-
if (!rule.permission || rule.permission === "EXECUTE") {
|
|
3529
|
-
warnings.push(UNSAFE_CONFIGURATION_WARNINGS.EXECUTE_WITHOUT_REVIEW);
|
|
3530
|
-
}
|
|
3531
|
-
return { valid: true, errors, warnings };
|
|
3532
|
-
}
|
|
3533
3593
|
function validatePolicySet(input) {
|
|
3534
3594
|
const errors = [];
|
|
3535
3595
|
const warnings = [];
|
|
@@ -3557,8 +3617,15 @@ function validatePolicySet(input) {
|
|
|
3557
3617
|
ruleIds.add(rule.id);
|
|
3558
3618
|
}
|
|
3559
3619
|
for (const rule of policySet.rules) {
|
|
3560
|
-
|
|
3561
|
-
|
|
3620
|
+
if (rule.toolPattern === "*" && rule.effect === "ALLOW") {
|
|
3621
|
+
warnings.push(UNSAFE_CONFIGURATION_WARNINGS.WILDCARD_ALLOW);
|
|
3622
|
+
}
|
|
3623
|
+
if (rule.minimumTrustLevel === "TRUSTED") {
|
|
3624
|
+
warnings.push(UNSAFE_CONFIGURATION_WARNINGS.TRUSTED_LEVEL_EXTERNAL);
|
|
3625
|
+
}
|
|
3626
|
+
if (!rule.permission || rule.permission === "EXECUTE") {
|
|
3627
|
+
warnings.push(UNSAFE_CONFIGURATION_WARNINGS.EXECUTE_WITHOUT_REVIEW);
|
|
3628
|
+
}
|
|
3562
3629
|
}
|
|
3563
3630
|
const hasDenyRule = policySet.rules.some((r) => r.effect === "DENY");
|
|
3564
3631
|
if (!hasDenyRule && policySet.rules.length > 0) {
|
|
@@ -3664,10 +3731,12 @@ function createDefaultDenyPolicySet() {
|
|
|
3664
3731
|
}
|
|
3665
3732
|
var PolicyEngine = class {
|
|
3666
3733
|
policySet;
|
|
3734
|
+
sortedRules = [];
|
|
3667
3735
|
timeoutMs;
|
|
3668
3736
|
store;
|
|
3669
3737
|
constructor(options) {
|
|
3670
3738
|
this.policySet = options?.policySet ?? createDefaultDenyPolicySet();
|
|
3739
|
+
this.sortedRules = [...this.policySet.rules].sort((a, b) => a.priority - b.priority);
|
|
3671
3740
|
this.timeoutMs = options?.timeoutMs ?? POLICY_EVALUATION_TIMEOUT_MS;
|
|
3672
3741
|
this.store = options?.store ?? null;
|
|
3673
3742
|
}
|
|
@@ -3677,7 +3746,8 @@ var PolicyEngine = class {
|
|
|
3677
3746
|
*/
|
|
3678
3747
|
evaluate(request) {
|
|
3679
3748
|
const startTime = performance.now();
|
|
3680
|
-
const
|
|
3749
|
+
const preSorted = { ...this.policySet, rules: this.sortedRules };
|
|
3750
|
+
const decision = evaluatePolicy(preSorted, request);
|
|
3681
3751
|
const elapsed = performance.now() - startTime;
|
|
3682
3752
|
if (elapsed > this.timeoutMs) {
|
|
3683
3753
|
console.warn(
|
|
@@ -3696,6 +3766,7 @@ var PolicyEngine = class {
|
|
|
3696
3766
|
return validation;
|
|
3697
3767
|
}
|
|
3698
3768
|
this.policySet = policySet;
|
|
3769
|
+
this.sortedRules = [...policySet.rules].sort((a, b) => a.priority - b.priority);
|
|
3699
3770
|
if (this.store) {
|
|
3700
3771
|
this.store.saveVersion(
|
|
3701
3772
|
policySet,
|
|
@@ -3715,6 +3786,7 @@ var PolicyEngine = class {
|
|
|
3715
3786
|
}
|
|
3716
3787
|
const policyVersion = this.store.rollback(this.policySet.id, version);
|
|
3717
3788
|
this.policySet = policyVersion.policySet;
|
|
3789
|
+
this.sortedRules = [...this.policySet.rules].sort((a, b) => a.priority - b.priority);
|
|
3718
3790
|
return policyVersion;
|
|
3719
3791
|
}
|
|
3720
3792
|
getPolicySet() {
|
|
@@ -3728,8 +3800,15 @@ var PolicyEngine = class {
|
|
|
3728
3800
|
}
|
|
3729
3801
|
reset() {
|
|
3730
3802
|
this.policySet = createDefaultDenyPolicySet();
|
|
3803
|
+
this.sortedRules = [...this.policySet.rules].sort((a, b) => a.priority - b.priority);
|
|
3731
3804
|
}
|
|
3732
3805
|
};
|
|
3806
|
+
function stableStringify(val) {
|
|
3807
|
+
return JSON.stringify(
|
|
3808
|
+
val,
|
|
3809
|
+
(_key, v) => v !== null && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b))) : v
|
|
3810
|
+
);
|
|
3811
|
+
}
|
|
3733
3812
|
var PolicyStore = class {
|
|
3734
3813
|
versions = /* @__PURE__ */ new Map();
|
|
3735
3814
|
/**
|
|
@@ -3748,8 +3827,8 @@ var PolicyStore = class {
|
|
|
3748
3827
|
createdBy,
|
|
3749
3828
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3750
3829
|
};
|
|
3751
|
-
|
|
3752
|
-
this.versions.set(id,
|
|
3830
|
+
history.push(version);
|
|
3831
|
+
this.versions.set(id, history);
|
|
3753
3832
|
return version;
|
|
3754
3833
|
}
|
|
3755
3834
|
/**
|
|
@@ -3802,7 +3881,7 @@ var PolicyStore = class {
|
|
|
3802
3881
|
const oldRule = oldRulesMap.get(id);
|
|
3803
3882
|
if (!oldRule) {
|
|
3804
3883
|
added.push(newRule);
|
|
3805
|
-
} else if (
|
|
3884
|
+
} else if (stableStringify(oldRule) !== stableStringify(newRule)) {
|
|
3806
3885
|
modified.push({ old: oldRule, new: newRule });
|
|
3807
3886
|
}
|
|
3808
3887
|
}
|
|
@@ -3817,7 +3896,10 @@ var PolicyStore = class {
|
|
|
3817
3896
|
* Computes SHA256 hash of a policy set for integrity verification.
|
|
3818
3897
|
*/
|
|
3819
3898
|
computeHash(policySet) {
|
|
3820
|
-
const serialized = JSON.stringify(
|
|
3899
|
+
const serialized = JSON.stringify(
|
|
3900
|
+
policySet,
|
|
3901
|
+
(_key, val) => val !== null && typeof val === "object" && !Array.isArray(val) ? Object.fromEntries(Object.entries(val).sort(([a], [b]) => a.localeCompare(b))) : val
|
|
3902
|
+
);
|
|
3821
3903
|
return createHash("sha256").update(serialized).digest("hex");
|
|
3822
3904
|
}
|
|
3823
3905
|
};
|
|
@@ -3882,12 +3964,13 @@ var DATA_SINK_TOOLS = /* @__PURE__ */ new Set([
|
|
|
3882
3964
|
var CHAIN_WINDOW_SIZE = 10;
|
|
3883
3965
|
var CHAIN_TIME_WINDOW_MS = 6e4;
|
|
3884
3966
|
var ExfiltrationChainTracker = class {
|
|
3885
|
-
recentCalls =
|
|
3967
|
+
recentCalls = new Array(CHAIN_WINDOW_SIZE);
|
|
3968
|
+
writeIndex = 0;
|
|
3969
|
+
count = 0;
|
|
3886
3970
|
record(toolName) {
|
|
3887
|
-
this.recentCalls.
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
}
|
|
3971
|
+
this.recentCalls[this.writeIndex] = { name: toolName, timestamp: Date.now() };
|
|
3972
|
+
this.writeIndex = (this.writeIndex + 1) % CHAIN_WINDOW_SIZE;
|
|
3973
|
+
if (this.count < CHAIN_WINDOW_SIZE) this.count++;
|
|
3891
3974
|
}
|
|
3892
3975
|
/**
|
|
3893
3976
|
* Check if a data sink tool call follows a recent data source tool call,
|
|
@@ -3897,9 +3980,13 @@ var ExfiltrationChainTracker = class {
|
|
|
3897
3980
|
if (!DATA_SINK_TOOLS.has(currentTool)) return false;
|
|
3898
3981
|
const now = Date.now();
|
|
3899
3982
|
const cutoff = now - CHAIN_TIME_WINDOW_MS;
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3983
|
+
for (let i = 0; i < this.count; i++) {
|
|
3984
|
+
const call = this.recentCalls[i];
|
|
3985
|
+
if (call && DATA_SOURCE_TOOLS.has(call.name) && call.timestamp >= cutoff) {
|
|
3986
|
+
return true;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
return false;
|
|
3903
3990
|
}
|
|
3904
3991
|
};
|
|
3905
3992
|
async function interceptToolCall(params, upstreamCall, options) {
|
|
@@ -3925,7 +4012,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
3925
4012
|
status: "ERROR",
|
|
3926
4013
|
request,
|
|
3927
4014
|
error: new RateLimitError(params.name, options.rateLimitPerTool),
|
|
3928
|
-
timestamp
|
|
4015
|
+
timestamp
|
|
3929
4016
|
};
|
|
3930
4017
|
options.onDecision?.(result);
|
|
3931
4018
|
return createDeniedToolResult(
|
|
@@ -3942,7 +4029,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
3942
4029
|
status: "ERROR",
|
|
3943
4030
|
request,
|
|
3944
4031
|
error: new RateLimitError("*", options.globalRateLimitPerMinute),
|
|
3945
|
-
timestamp
|
|
4032
|
+
timestamp
|
|
3946
4033
|
};
|
|
3947
4034
|
options.onDecision?.(result);
|
|
3948
4035
|
return createDeniedToolResult("Global rate limit exceeded");
|
|
@@ -3958,10 +4045,10 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
3958
4045
|
effect: "DENY",
|
|
3959
4046
|
matchedRule: null,
|
|
3960
4047
|
reason: `Exfiltration chain detected: data-sink tool "${params.name}" called after recent data-source tool`,
|
|
3961
|
-
timestamp
|
|
4048
|
+
timestamp,
|
|
3962
4049
|
evaluationTimeMs: 0
|
|
3963
4050
|
},
|
|
3964
|
-
timestamp
|
|
4051
|
+
timestamp
|
|
3965
4052
|
};
|
|
3966
4053
|
options.onDecision?.(result);
|
|
3967
4054
|
return createDeniedToolResult(
|
|
@@ -3976,7 +4063,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
3976
4063
|
status: "DENIED",
|
|
3977
4064
|
request,
|
|
3978
4065
|
decision,
|
|
3979
|
-
timestamp
|
|
4066
|
+
timestamp
|
|
3980
4067
|
};
|
|
3981
4068
|
options.onDecision?.(result);
|
|
3982
4069
|
const reason = options.verboseErrors ? decision.reason : "Tool execution denied by security policy.";
|
|
@@ -3990,12 +4077,14 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
3990
4077
|
[params.name]
|
|
3991
4078
|
);
|
|
3992
4079
|
}
|
|
4080
|
+
let callParams = params;
|
|
3993
4081
|
if (options.serverVerifier && capabilityToken) {
|
|
3994
|
-
options.serverVerifier.createSignedRequest(params, capabilityToken);
|
|
4082
|
+
const signed = options.serverVerifier.createSignedRequest(params, capabilityToken);
|
|
4083
|
+
callParams = signed;
|
|
3995
4084
|
}
|
|
3996
4085
|
try {
|
|
3997
4086
|
const startTime = performance.now();
|
|
3998
|
-
const toolResult = await upstreamCall(
|
|
4087
|
+
const toolResult = await upstreamCall(callParams);
|
|
3999
4088
|
const durationMs = performance.now() - startTime;
|
|
4000
4089
|
const scanConfig = options.responseScanConfig ?? DEFAULT_RESPONSE_SCAN_CONFIG;
|
|
4001
4090
|
let finalResult = toolResult;
|
|
@@ -4026,7 +4115,7 @@ ${item.text}`;
|
|
|
4026
4115
|
decision,
|
|
4027
4116
|
toolResult: finalResult,
|
|
4028
4117
|
durationMs,
|
|
4029
|
-
timestamp
|
|
4118
|
+
timestamp
|
|
4030
4119
|
};
|
|
4031
4120
|
options.onDecision?.(result);
|
|
4032
4121
|
return finalResult;
|
|
@@ -4035,7 +4124,7 @@ ${item.text}`;
|
|
|
4035
4124
|
status: "ERROR",
|
|
4036
4125
|
request,
|
|
4037
4126
|
error: error instanceof Error ? new PolicyDeniedError(params.name, error.message) : new PolicyDeniedError(params.name, "Unknown upstream error"),
|
|
4038
|
-
timestamp
|
|
4127
|
+
timestamp
|
|
4039
4128
|
};
|
|
4040
4129
|
options.onDecision?.(result);
|
|
4041
4130
|
throw error;
|
|
@@ -4115,6 +4204,7 @@ var ExpiringSet = class {
|
|
|
4115
4204
|
return true;
|
|
4116
4205
|
}
|
|
4117
4206
|
get size() {
|
|
4207
|
+
this.maybeSweep();
|
|
4118
4208
|
return this.entries.size;
|
|
4119
4209
|
}
|
|
4120
4210
|
maybeSweep() {
|
|
@@ -4552,7 +4642,7 @@ var SolonGate = class {
|
|
|
4552
4642
|
"X-API-Key": this.apiKey,
|
|
4553
4643
|
"Authorization": `Bearer ${this.apiKey}`
|
|
4554
4644
|
},
|
|
4555
|
-
signal: AbortSignal.timeout(
|
|
4645
|
+
signal: AbortSignal.timeout(5e3)
|
|
4556
4646
|
});
|
|
4557
4647
|
if (res.status === 401) {
|
|
4558
4648
|
throw new LicenseError("Invalid or expired API key.");
|
|
@@ -4563,13 +4653,13 @@ var SolonGate = class {
|
|
|
4563
4653
|
this.licenseValidated = true;
|
|
4564
4654
|
} catch (err) {
|
|
4565
4655
|
if (err instanceof LicenseError) throw err;
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
);
|
|
4656
|
+
console.warn("[SolonGate] License validation failed (network error), allowing through:", err instanceof Error ? err.message : String(err));
|
|
4657
|
+
this.licenseValidated = true;
|
|
4569
4658
|
}
|
|
4570
4659
|
}
|
|
4571
4660
|
/**
|
|
4572
4661
|
* Fetch policy from SolonGate Cloud API (fire once, non-blocking).
|
|
4662
|
+
* TODO: extract cloud policy parsing to shared module with packages/proxy/src/config.ts
|
|
4573
4663
|
*/
|
|
4574
4664
|
fetchCloudPolicyOnce() {
|
|
4575
4665
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
@@ -4589,7 +4679,6 @@ var SolonGate = class {
|
|
|
4589
4679
|
updatedAt: ""
|
|
4590
4680
|
};
|
|
4591
4681
|
this.policyEngine.loadPolicySet(policySet);
|
|
4592
|
-
console.warn(`[SolonGate] Loaded cloud policy: ${policySet.name} (${policySet.rules.length} rules)`);
|
|
4593
4682
|
}).catch(() => {
|
|
4594
4683
|
});
|
|
4595
4684
|
}
|
|
@@ -4599,7 +4688,7 @@ var SolonGate = class {
|
|
|
4599
4688
|
startPolicyPolling() {
|
|
4600
4689
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
4601
4690
|
let currentVersion = 0;
|
|
4602
|
-
|
|
4691
|
+
const timer = setInterval(async () => {
|
|
4603
4692
|
try {
|
|
4604
4693
|
const res = await fetch(`${apiUrl}/api/v1/policies/default`, {
|
|
4605
4694
|
headers: { "Authorization": `Bearer ${this.apiKey}` },
|
|
@@ -4608,7 +4697,6 @@ var SolonGate = class {
|
|
|
4608
4697
|
if (!res.ok) return;
|
|
4609
4698
|
const data = await res.json();
|
|
4610
4699
|
const version = Number(data._version ?? 0);
|
|
4611
|
-
const rulesCount = Array.isArray(data.rules) ? data.rules.length : 0;
|
|
4612
4700
|
if (version !== currentVersion && version > 0) {
|
|
4613
4701
|
const policySet = {
|
|
4614
4702
|
id: String(data.id ?? "cloud"),
|
|
@@ -4621,11 +4709,12 @@ var SolonGate = class {
|
|
|
4621
4709
|
};
|
|
4622
4710
|
this.policyEngine.loadPolicySet(policySet);
|
|
4623
4711
|
currentVersion = version;
|
|
4624
|
-
console.warn(`[SolonGate] Policy updated from dashboard: ${policySet.name} v${version} (${rulesCount} rules)`);
|
|
4625
4712
|
}
|
|
4626
4713
|
} catch {
|
|
4627
4714
|
}
|
|
4628
4715
|
}, 6e4);
|
|
4716
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
4717
|
+
this.pollingTimer = timer;
|
|
4629
4718
|
}
|
|
4630
4719
|
/**
|
|
4631
4720
|
* Send audit log to SolonGate Cloud API (fire-and-forget).
|
|
@@ -4639,7 +4728,8 @@ var SolonGate = class {
|
|
|
4639
4728
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
4640
4729
|
"Content-Type": "application/json"
|
|
4641
4730
|
},
|
|
4642
|
-
body: JSON.stringify(entry)
|
|
4731
|
+
body: JSON.stringify(entry),
|
|
4732
|
+
signal: AbortSignal.timeout(5e3)
|
|
4643
4733
|
}).catch(() => {
|
|
4644
4734
|
});
|
|
4645
4735
|
}
|
|
@@ -4727,21 +4817,6 @@ var __export2 = (target, all) => {
|
|
|
4727
4817
|
for (var name in all)
|
|
4728
4818
|
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
4729
4819
|
};
|
|
4730
|
-
var DEFAULT_ADVANCED_DETECTION_CONFIG2;
|
|
4731
|
-
var init_types2 = __esm3({
|
|
4732
|
-
"src/prompt-injection/types.ts"() {
|
|
4733
|
-
DEFAULT_ADVANCED_DETECTION_CONFIG2 = {
|
|
4734
|
-
enabled: true,
|
|
4735
|
-
threshold: 0.5,
|
|
4736
|
-
weights: {
|
|
4737
|
-
rules: 0.3,
|
|
4738
|
-
embedding: 0.3,
|
|
4739
|
-
classifier: 0.4
|
|
4740
|
-
},
|
|
4741
|
-
onModelDownloadStart: void 0
|
|
4742
|
-
};
|
|
4743
|
-
}
|
|
4744
|
-
});
|
|
4745
4820
|
function runStage1Rules2(input) {
|
|
4746
4821
|
const matchedCategories = [];
|
|
4747
4822
|
let maxWeight = 0;
|
|
@@ -4869,6 +4944,21 @@ var init_stage1_rules2 = __esm3({
|
|
|
4869
4944
|
ADDITIONAL_MATCH_BONUS2 = 0.05;
|
|
4870
4945
|
}
|
|
4871
4946
|
});
|
|
4947
|
+
var DEFAULT_ADVANCED_DETECTION_CONFIG2;
|
|
4948
|
+
var init_types2 = __esm3({
|
|
4949
|
+
"src/prompt-injection/types.ts"() {
|
|
4950
|
+
DEFAULT_ADVANCED_DETECTION_CONFIG2 = {
|
|
4951
|
+
enabled: true,
|
|
4952
|
+
threshold: 0.5,
|
|
4953
|
+
weights: {
|
|
4954
|
+
rules: 0.3,
|
|
4955
|
+
embedding: 0.3,
|
|
4956
|
+
classifier: 0.4
|
|
4957
|
+
},
|
|
4958
|
+
onModelDownloadStart: void 0
|
|
4959
|
+
};
|
|
4960
|
+
}
|
|
4961
|
+
});
|
|
4872
4962
|
var ATTACK_VECTORS2;
|
|
4873
4963
|
var init_attack_vectors2 = __esm3({
|
|
4874
4964
|
"src/prompt-injection/attack-vectors.ts"() {
|
|
@@ -4990,37 +5080,49 @@ async function getOrCreatePipeline2(task, model, onDownloadStart) {
|
|
|
4990
5080
|
if (pipelineCache2.has(cacheKey)) {
|
|
4991
5081
|
return pipelineCache2.get(cacheKey);
|
|
4992
5082
|
}
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
const modelSizes = {
|
|
4996
|
-
"Xenova/all-MiniLM-L6-v2": 22,
|
|
4997
|
-
"Xenova/deberta-v3-base-prompt-injection-v2": 184
|
|
4998
|
-
};
|
|
4999
|
-
console.warn(
|
|
5000
|
-
`[SolonGate] Downloading model "${model}" (~${modelSizes[model] ?? "?"}MB) for prompt injection detection. This is a one-time download cached at ~/.cache/huggingface/hub/`
|
|
5001
|
-
);
|
|
5002
|
-
if (onDownloadStart) {
|
|
5003
|
-
onDownloadStart(model, modelSizes[model] ?? 0);
|
|
5004
|
-
}
|
|
5005
|
-
try {
|
|
5006
|
-
const pipe = await transformers.pipeline(task, model);
|
|
5007
|
-
pipelineCache2.set(cacheKey, pipe);
|
|
5008
|
-
return pipe;
|
|
5009
|
-
} catch (err) {
|
|
5010
|
-
console.warn(`[SolonGate] Failed to load model "${model}":`, err);
|
|
5011
|
-
return null;
|
|
5083
|
+
if (pipelineInflight2.has(cacheKey)) {
|
|
5084
|
+
return pipelineInflight2.get(cacheKey);
|
|
5012
5085
|
}
|
|
5086
|
+
const promise = (async () => {
|
|
5087
|
+
const transformers = await getTransformers2();
|
|
5088
|
+
if (!transformers) return null;
|
|
5089
|
+
const modelSizes = {
|
|
5090
|
+
"Xenova/all-MiniLM-L6-v2": 22,
|
|
5091
|
+
"Xenova/deberta-v3-base-prompt-injection-v2": 184
|
|
5092
|
+
};
|
|
5093
|
+
if (onDownloadStart) {
|
|
5094
|
+
onDownloadStart(model, modelSizes[model] ?? 0);
|
|
5095
|
+
} else {
|
|
5096
|
+
console.warn(
|
|
5097
|
+
`[SolonGate] Downloading model "${model}" (~${modelSizes[model] ?? "?"}MB) for prompt injection detection. This is a one-time download cached at ~/.cache/huggingface/hub/`
|
|
5098
|
+
);
|
|
5099
|
+
}
|
|
5100
|
+
try {
|
|
5101
|
+
const pipe = await transformers.pipeline(task, model);
|
|
5102
|
+
pipelineCache2.set(cacheKey, pipe);
|
|
5103
|
+
return pipe;
|
|
5104
|
+
} catch (err) {
|
|
5105
|
+
console.warn(`[SolonGate] Failed to load model "${model}":`, err);
|
|
5106
|
+
return null;
|
|
5107
|
+
} finally {
|
|
5108
|
+
pipelineInflight2.delete(cacheKey);
|
|
5109
|
+
}
|
|
5110
|
+
})();
|
|
5111
|
+
pipelineInflight2.set(cacheKey, promise);
|
|
5112
|
+
return promise;
|
|
5013
5113
|
}
|
|
5014
5114
|
var transformersModule2;
|
|
5015
5115
|
var transformersChecked2;
|
|
5016
5116
|
var loadingPromise2;
|
|
5017
5117
|
var pipelineCache2;
|
|
5118
|
+
var pipelineInflight2;
|
|
5018
5119
|
var init_model_manager2 = __esm3({
|
|
5019
5120
|
"src/prompt-injection/model-manager.ts"() {
|
|
5020
5121
|
transformersModule2 = null;
|
|
5021
5122
|
transformersChecked2 = false;
|
|
5022
5123
|
loadingPromise2 = null;
|
|
5023
5124
|
pipelineCache2 = /* @__PURE__ */ new Map();
|
|
5125
|
+
pipelineInflight2 = /* @__PURE__ */ new Map();
|
|
5024
5126
|
}
|
|
5025
5127
|
});
|
|
5026
5128
|
function cosineSimilarity2(a, b) {
|
|
@@ -5036,10 +5138,11 @@ function cosineSimilarity2(a, b) {
|
|
|
5036
5138
|
return denom === 0 ? 0 : dotProduct / denom;
|
|
5037
5139
|
}
|
|
5038
5140
|
async function embed2(pipe, texts) {
|
|
5141
|
+
const output = await pipe(texts, { pooling: "mean", normalize: true });
|
|
5142
|
+
const dim2 = output.dims?.[1] ?? output.data.length / texts.length;
|
|
5039
5143
|
const results = [];
|
|
5040
|
-
for (
|
|
5041
|
-
|
|
5042
|
-
results.push(new Float32Array(output.data));
|
|
5144
|
+
for (let i = 0; i < texts.length; i++) {
|
|
5145
|
+
results.push(new Float32Array(output.data.slice(i * dim2, (i + 1) * dim2)));
|
|
5043
5146
|
}
|
|
5044
5147
|
return results;
|
|
5045
5148
|
}
|
|
@@ -5308,6 +5411,10 @@ var PolicySetSchema2 = z2.object({
|
|
|
5308
5411
|
updatedAt: z2.string().datetime()
|
|
5309
5412
|
});
|
|
5310
5413
|
var SECURITY_CONTEXT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5414
|
+
var INPUT_GUARD_ENTROPY_THRESHOLD = 4.5;
|
|
5415
|
+
var INPUT_GUARD_MIN_ENTROPY_LENGTH = 32;
|
|
5416
|
+
var INPUT_GUARD_MAX_WILDCARDS = 3;
|
|
5417
|
+
init_stage1_rules2();
|
|
5311
5418
|
var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
|
|
5312
5419
|
pathTraversal: true,
|
|
5313
5420
|
shellInjection: true,
|
|
@@ -5431,11 +5538,12 @@ function detectShellInjection(value) {
|
|
|
5431
5538
|
}
|
|
5432
5539
|
return false;
|
|
5433
5540
|
}
|
|
5434
|
-
var MAX_WILDCARDS_PER_VALUE = 3;
|
|
5435
5541
|
function detectWildcardAbuse(value) {
|
|
5436
5542
|
if (value.includes("**")) return true;
|
|
5437
|
-
|
|
5438
|
-
|
|
5543
|
+
let count = 0;
|
|
5544
|
+
for (let i = 0; i < value.length; i++) {
|
|
5545
|
+
if (value.charCodeAt(i) === 42 && ++count > INPUT_GUARD_MAX_WILDCARDS) return true;
|
|
5546
|
+
}
|
|
5439
5547
|
return false;
|
|
5440
5548
|
}
|
|
5441
5549
|
var SSRF_PATTERNS = [
|
|
@@ -5523,43 +5631,9 @@ function detectSQLInjection(value) {
|
|
|
5523
5631
|
}
|
|
5524
5632
|
return false;
|
|
5525
5633
|
}
|
|
5526
|
-
var PROMPT_INJECTION_PATTERNS = [
|
|
5527
|
-
// Instruction override attempts
|
|
5528
|
-
/\bignore\s+(all\s+)?(previous|prior|above|earlier)\s+(instructions?|prompts?|rules?|directives?)\b/i,
|
|
5529
|
-
/\bdisregard\s+(all\s+)?(previous|prior|above|earlier|your)\s+(instructions?|prompts?|rules?|guidelines?)\b/i,
|
|
5530
|
-
/\bforget\s+(all\s+)?(your|the|previous|prior)\s+(instructions?|rules?|constraints?|guidelines?)\b/i,
|
|
5531
|
-
/\boverride\s+(the\s+)?(system|previous|current)\s+(prompt|instructions?|rules?|settings?)\b/i,
|
|
5532
|
-
/\bdo\s+not\s+follow\s+(your|the|any)\s+(instructions?|rules?|guidelines?)\b/i,
|
|
5533
|
-
// Role hijacking
|
|
5534
|
-
/\b(pretend|act|behave)\s+(you\s+are|as\s+if\s+you|like\s+you|to\s+be)\b/i,
|
|
5535
|
-
/\byou\s+are\s+now\s+(a|an|the|my)\b/i,
|
|
5536
|
-
/\bsimulate\s+being\b/i,
|
|
5537
|
-
/\bassume\s+the\s+role\s+of\b/i,
|
|
5538
|
-
/\benter\s+(developer|admin|debug|god|sudo)\s+mode\b/i,
|
|
5539
|
-
// Delimiter injection (LLM token boundaries)
|
|
5540
|
-
/<\/system>/i,
|
|
5541
|
-
/<\|im_end\|>/i,
|
|
5542
|
-
/<\|im_start\|>/i,
|
|
5543
|
-
/<\|endoftext\|>/i,
|
|
5544
|
-
/\[INST\]/i,
|
|
5545
|
-
/\[\/INST\]/i,
|
|
5546
|
-
/<<SYS>>/i,
|
|
5547
|
-
/<<\/SYS>>/i,
|
|
5548
|
-
/###\s*(Human|Assistant|System)\s*:/i,
|
|
5549
|
-
/<\|user\|>/i,
|
|
5550
|
-
/<\|assistant\|>/i,
|
|
5551
|
-
// Meta-prompting / jailbreak keywords
|
|
5552
|
-
/\b(system\s+override|admin\s+mode|debug\s+mode|developer\s+mode|maintenance\s+mode)\b/i,
|
|
5553
|
-
/\bjailbreak\b/i,
|
|
5554
|
-
/\bDAN\s+mode\b/i,
|
|
5555
|
-
// Instruction injection via separators
|
|
5556
|
-
/[-=]{3,}\s*\n\s*(new\s+instructions?|system|instructions?)\s*:/i
|
|
5557
|
-
];
|
|
5558
5634
|
function detectPromptInjection(value) {
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
}
|
|
5562
|
-
return false;
|
|
5635
|
+
const result = runStage1Rules2(value);
|
|
5636
|
+
return result.score > 0;
|
|
5563
5637
|
}
|
|
5564
5638
|
var EXFILTRATION_PATTERNS = [
|
|
5565
5639
|
// Base64 data in URL query parameters (min 20 chars of base64)
|
|
@@ -5590,26 +5664,34 @@ function detectBoundaryEscape(value) {
|
|
|
5590
5664
|
function checkLengthLimits(value, maxLength = 4096) {
|
|
5591
5665
|
return value.length <= maxLength;
|
|
5592
5666
|
}
|
|
5593
|
-
var ENTROPY_THRESHOLD = 4.5;
|
|
5594
|
-
var MIN_LENGTH_FOR_ENTROPY_CHECK = 32;
|
|
5595
5667
|
function checkEntropyLimits(value) {
|
|
5596
|
-
if (value.length <
|
|
5668
|
+
if (value.length < INPUT_GUARD_MIN_ENTROPY_LENGTH) return true;
|
|
5597
5669
|
const entropy = calculateShannonEntropy(value);
|
|
5598
|
-
return entropy <=
|
|
5670
|
+
return entropy <= INPUT_GUARD_ENTROPY_THRESHOLD;
|
|
5599
5671
|
}
|
|
5600
5672
|
function calculateShannonEntropy(str) {
|
|
5601
|
-
const freq =
|
|
5602
|
-
|
|
5603
|
-
|
|
5673
|
+
const freq = new Uint32Array(128);
|
|
5674
|
+
let nonAsciiCount = 0;
|
|
5675
|
+
for (let i = 0; i < str.length; i++) {
|
|
5676
|
+
const code = str.charCodeAt(i);
|
|
5677
|
+
if (code < 128) {
|
|
5678
|
+
freq[code] = (freq[code] ?? 0) + 1;
|
|
5679
|
+
} else {
|
|
5680
|
+
nonAsciiCount++;
|
|
5681
|
+
}
|
|
5604
5682
|
}
|
|
5605
5683
|
let entropy = 0;
|
|
5606
5684
|
const len = str.length;
|
|
5607
|
-
for (
|
|
5608
|
-
|
|
5609
|
-
|
|
5685
|
+
for (let i = 0; i < 128; i++) {
|
|
5686
|
+
if ((freq[i] ?? 0) > 0) {
|
|
5687
|
+
const p = (freq[i] ?? 0) / len;
|
|
5610
5688
|
entropy -= p * Math.log2(p);
|
|
5611
5689
|
}
|
|
5612
5690
|
}
|
|
5691
|
+
if (nonAsciiCount > 0) {
|
|
5692
|
+
const p = nonAsciiCount / len;
|
|
5693
|
+
entropy -= p * Math.log2(p);
|
|
5694
|
+
}
|
|
5613
5695
|
return entropy;
|
|
5614
5696
|
}
|
|
5615
5697
|
function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG2) {
|
|
@@ -5749,18 +5831,11 @@ async function sanitizeInputAsync(field, value, config = DEFAULT_INPUT_GUARD_CON
|
|
|
5749
5831
|
return { ...syncResult, trustScore: void 0 };
|
|
5750
5832
|
}
|
|
5751
5833
|
async function sanitizeObjectAsync(basePath, obj, config) {
|
|
5752
|
-
const
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
}
|
|
5758
|
-
} else {
|
|
5759
|
-
for (const [key, val] of Object.entries(obj)) {
|
|
5760
|
-
const result = await sanitizeInputAsync(`${basePath}.${key}`, val, config);
|
|
5761
|
-
threats.push(...result.threats);
|
|
5762
|
-
}
|
|
5763
|
-
}
|
|
5834
|
+
const entries = Array.isArray(obj) ? obj.map((item, i) => [`[${i}]`, item]) : Object.entries(obj);
|
|
5835
|
+
const results = await Promise.all(
|
|
5836
|
+
entries.map(([key, val]) => sanitizeInputAsync(`${basePath}.${key}`, val, config))
|
|
5837
|
+
);
|
|
5838
|
+
const threats = results.flatMap((r) => r.threats);
|
|
5764
5839
|
return { safe: threats.length === 0, threats, trustScore: void 0 };
|
|
5765
5840
|
}
|
|
5766
5841
|
init_detector2();
|
|
@@ -5815,61 +5890,14 @@ function detectHiddenDirective2(value) {
|
|
|
5815
5890
|
}
|
|
5816
5891
|
return false;
|
|
5817
5892
|
}
|
|
5818
|
-
var
|
|
5819
|
-
/\u200B/,
|
|
5820
|
-
// Zero-width space
|
|
5821
|
-
/\u200C/,
|
|
5822
|
-
// Zero-width non-joiner
|
|
5823
|
-
/\u200D/,
|
|
5824
|
-
// Zero-width joiner
|
|
5825
|
-
/\u200E/,
|
|
5826
|
-
// Left-to-right mark
|
|
5827
|
-
/\u200F/,
|
|
5828
|
-
// Right-to-left mark
|
|
5829
|
-
/\u2060/,
|
|
5830
|
-
// Word joiner
|
|
5831
|
-
/\u2061/,
|
|
5832
|
-
// Function application
|
|
5833
|
-
/\u2062/,
|
|
5834
|
-
// Invisible times
|
|
5835
|
-
/\u2063/,
|
|
5836
|
-
// Invisible separator
|
|
5837
|
-
/\u2064/,
|
|
5838
|
-
// Invisible plus
|
|
5839
|
-
/\uFEFF/,
|
|
5840
|
-
// Zero-width no-break space (BOM)
|
|
5841
|
-
/\u202A/,
|
|
5842
|
-
// Left-to-right embedding
|
|
5843
|
-
/\u202B/,
|
|
5844
|
-
// Right-to-left embedding
|
|
5845
|
-
/\u202C/,
|
|
5846
|
-
// Pop directional formatting
|
|
5847
|
-
/\u202D/,
|
|
5848
|
-
// Left-to-right override
|
|
5849
|
-
/\u202E/,
|
|
5850
|
-
// Right-to-left override (text reversal attack)
|
|
5851
|
-
/\u2066/,
|
|
5852
|
-
// Left-to-right isolate
|
|
5853
|
-
/\u2067/,
|
|
5854
|
-
// Right-to-left isolate
|
|
5855
|
-
/\u2068/,
|
|
5856
|
-
// First strong isolate
|
|
5857
|
-
/\u2069/,
|
|
5858
|
-
// Pop directional isolate
|
|
5859
|
-
/[\uE000-\uF8FF]/,
|
|
5860
|
-
// Private Use Area
|
|
5861
|
-
/[\uDB80-\uDBFF][\uDC00-\uDFFF]/
|
|
5862
|
-
// Supplementary Private Use Area
|
|
5863
|
-
];
|
|
5893
|
+
var INVISIBLE_UNICODE_RE2 = /[\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF\uE000-\uF8FF]|[\uDB80-\uDBFF][\uDC00-\uDFFF]/g;
|
|
5864
5894
|
var INVISIBLE_CHAR_THRESHOLD2 = 3;
|
|
5865
5895
|
function detectInvisibleUnicode2(value) {
|
|
5896
|
+
INVISIBLE_UNICODE_RE2.lastIndex = 0;
|
|
5866
5897
|
let count = 0;
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
if (
|
|
5870
|
-
count += matches.length;
|
|
5871
|
-
if (count >= INVISIBLE_CHAR_THRESHOLD2) return true;
|
|
5872
|
-
}
|
|
5898
|
+
while (INVISIBLE_UNICODE_RE2.exec(value)) {
|
|
5899
|
+
count++;
|
|
5900
|
+
if (count >= INVISIBLE_CHAR_THRESHOLD2) return true;
|
|
5873
5901
|
}
|
|
5874
5902
|
return false;
|
|
5875
5903
|
}
|
|
@@ -5944,7 +5972,7 @@ var PolicySyncManager = class {
|
|
|
5944
5972
|
currentPolicy;
|
|
5945
5973
|
localVersion;
|
|
5946
5974
|
cloudVersion;
|
|
5947
|
-
|
|
5975
|
+
lastWriteTime = 0;
|
|
5948
5976
|
debounceTimer = null;
|
|
5949
5977
|
pollTimer = null;
|
|
5950
5978
|
watcher = null;
|
|
@@ -6013,8 +6041,7 @@ var PolicySyncManager = class {
|
|
|
6013
6041
|
* Handle local file change event.
|
|
6014
6042
|
*/
|
|
6015
6043
|
async onFileChange(filePath) {
|
|
6016
|
-
if (this.
|
|
6017
|
-
this.skipNextWatch = false;
|
|
6044
|
+
if (Date.now() - this.lastWriteTime < 1e3) {
|
|
6018
6045
|
return;
|
|
6019
6046
|
}
|
|
6020
6047
|
try {
|
|
@@ -6083,31 +6110,43 @@ var PolicySyncManager = class {
|
|
|
6083
6110
|
*/
|
|
6084
6111
|
async pushToCloud(policy) {
|
|
6085
6112
|
const cloudId = this.policyId || policy.id || "default";
|
|
6086
|
-
const
|
|
6087
|
-
|
|
6113
|
+
const payload = JSON.stringify({
|
|
6114
|
+
id: cloudId,
|
|
6115
|
+
name: policy.name || "Default Policy",
|
|
6116
|
+
description: policy.description || "Synced from proxy",
|
|
6117
|
+
version: policy.version || 1,
|
|
6118
|
+
rules: policy.rules
|
|
6088
6119
|
});
|
|
6089
|
-
const
|
|
6090
|
-
|
|
6091
|
-
const res = await fetch(url, {
|
|
6092
|
-
method,
|
|
6120
|
+
const putRes = await fetch(`${this.apiUrl}/api/v1/policies/${cloudId}`, {
|
|
6121
|
+
method: "PUT",
|
|
6093
6122
|
headers: {
|
|
6094
6123
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
6095
6124
|
"Content-Type": "application/json"
|
|
6096
6125
|
},
|
|
6097
|
-
body:
|
|
6098
|
-
id: cloudId,
|
|
6099
|
-
name: policy.name || "Default Policy",
|
|
6100
|
-
description: policy.description || "Synced from proxy",
|
|
6101
|
-
version: policy.version || 1,
|
|
6102
|
-
rules: policy.rules
|
|
6103
|
-
})
|
|
6126
|
+
body: payload
|
|
6104
6127
|
});
|
|
6105
|
-
if (
|
|
6106
|
-
const
|
|
6107
|
-
|
|
6128
|
+
if (putRes.ok) {
|
|
6129
|
+
const data = await putRes.json();
|
|
6130
|
+
return { version: Number(data._version ?? policy.version) };
|
|
6108
6131
|
}
|
|
6109
|
-
|
|
6110
|
-
|
|
6132
|
+
if (putRes.status === 404) {
|
|
6133
|
+
const postRes = await fetch(`${this.apiUrl}/api/v1/policies`, {
|
|
6134
|
+
method: "POST",
|
|
6135
|
+
headers: {
|
|
6136
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
6137
|
+
"Content-Type": "application/json"
|
|
6138
|
+
},
|
|
6139
|
+
body: payload
|
|
6140
|
+
});
|
|
6141
|
+
if (!postRes.ok) {
|
|
6142
|
+
const body2 = await postRes.text().catch(() => "");
|
|
6143
|
+
throw new Error(`Push failed (${postRes.status}): ${body2}`);
|
|
6144
|
+
}
|
|
6145
|
+
const data = await postRes.json();
|
|
6146
|
+
return { version: Number(data._version ?? policy.version) };
|
|
6147
|
+
}
|
|
6148
|
+
const body = await putRes.text().catch(() => "");
|
|
6149
|
+
throw new Error(`Push failed (${putRes.status}): ${body}`);
|
|
6111
6150
|
}
|
|
6112
6151
|
/**
|
|
6113
6152
|
* Write policy to local file (with loop prevention).
|
|
@@ -6115,14 +6154,13 @@ var PolicySyncManager = class {
|
|
|
6115
6154
|
*/
|
|
6116
6155
|
writeToFile(policy) {
|
|
6117
6156
|
if (!this.localPath) return;
|
|
6118
|
-
this.
|
|
6157
|
+
this.lastWriteTime = Date.now();
|
|
6119
6158
|
try {
|
|
6120
6159
|
const { id: _id, ...rest } = policy;
|
|
6121
6160
|
const json = JSON.stringify(rest, null, 2) + "\n";
|
|
6122
6161
|
writeFileSync(this.localPath, json, "utf-8");
|
|
6123
6162
|
} catch (err) {
|
|
6124
6163
|
log(`File write error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6125
|
-
this.skipNextWatch = false;
|
|
6126
6164
|
}
|
|
6127
6165
|
}
|
|
6128
6166
|
/**
|
|
@@ -6130,6 +6168,9 @@ var PolicySyncManager = class {
|
|
|
6130
6168
|
*/
|
|
6131
6169
|
policiesEqual(a, b) {
|
|
6132
6170
|
if (a.name !== b.name || a.rules.length !== b.rules.length) return false;
|
|
6171
|
+
if (a.version !== void 0 && b.version !== void 0 && a.version === b.version && a.id === b.id) {
|
|
6172
|
+
return true;
|
|
6173
|
+
}
|
|
6133
6174
|
return JSON.stringify(a.rules) === JSON.stringify(b.rules);
|
|
6134
6175
|
}
|
|
6135
6176
|
};
|
|
@@ -6193,9 +6234,10 @@ var AiJudge = class {
|
|
|
6193
6234
|
* Fail-closed: any error (timeout, parse failure, connection refused) → DENY.
|
|
6194
6235
|
*/
|
|
6195
6236
|
async evaluate(toolName, args) {
|
|
6237
|
+
const sanitizedArgs = this.sanitizeArgs(args);
|
|
6196
6238
|
const userMessage = JSON.stringify({
|
|
6197
6239
|
tool: toolName,
|
|
6198
|
-
arguments:
|
|
6240
|
+
arguments: sanitizedArgs,
|
|
6199
6241
|
protected_files: this.protectedFiles,
|
|
6200
6242
|
protected_paths: this.protectedPaths,
|
|
6201
6243
|
denied_actions: this.deniedActions
|
|
@@ -6212,13 +6254,35 @@ var AiJudge = class {
|
|
|
6212
6254
|
};
|
|
6213
6255
|
}
|
|
6214
6256
|
}
|
|
6257
|
+
/**
|
|
6258
|
+
* Sanitize tool arguments before sending to the judge LLM.
|
|
6259
|
+
* Truncates long strings and strips control characters to reduce injection surface.
|
|
6260
|
+
*/
|
|
6261
|
+
sanitizeArgs(args, maxStringLen = 2e3) {
|
|
6262
|
+
const sanitize = (val, depth = 0) => {
|
|
6263
|
+
if (depth > 10) return "[nested]";
|
|
6264
|
+
if (typeof val === "string") {
|
|
6265
|
+
const truncated = val.length > maxStringLen ? val.slice(0, maxStringLen) + "...[truncated]" : val;
|
|
6266
|
+
return truncated;
|
|
6267
|
+
}
|
|
6268
|
+
if (Array.isArray(val)) return val.slice(0, 50).map((v) => sanitize(v, depth + 1));
|
|
6269
|
+
if (val && typeof val === "object") {
|
|
6270
|
+
const out = {};
|
|
6271
|
+
for (const [k, v] of Object.entries(val)) {
|
|
6272
|
+
out[k] = sanitize(v, depth + 1);
|
|
6273
|
+
}
|
|
6274
|
+
return out;
|
|
6275
|
+
}
|
|
6276
|
+
return val;
|
|
6277
|
+
};
|
|
6278
|
+
return sanitize(args);
|
|
6279
|
+
}
|
|
6215
6280
|
/**
|
|
6216
6281
|
* Call the LLM endpoint. Supports Groq, OpenAI, and Ollama.
|
|
6217
6282
|
*/
|
|
6218
6283
|
async callLLM(userMessage) {
|
|
6219
|
-
const
|
|
6220
|
-
|
|
6221
|
-
try {
|
|
6284
|
+
const signal = AbortSignal.timeout(this.config.timeoutMs);
|
|
6285
|
+
{
|
|
6222
6286
|
let url;
|
|
6223
6287
|
let body;
|
|
6224
6288
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -6252,7 +6316,7 @@ var AiJudge = class {
|
|
|
6252
6316
|
method: "POST",
|
|
6253
6317
|
headers,
|
|
6254
6318
|
body,
|
|
6255
|
-
signal
|
|
6319
|
+
signal
|
|
6256
6320
|
});
|
|
6257
6321
|
if (!res.ok) {
|
|
6258
6322
|
const errBody = await res.text().catch(() => "");
|
|
@@ -6268,8 +6332,6 @@ var AiJudge = class {
|
|
|
6268
6332
|
const message = first?.message;
|
|
6269
6333
|
return message?.content ?? "";
|
|
6270
6334
|
}
|
|
6271
|
-
} finally {
|
|
6272
|
-
clearTimeout(timeout);
|
|
6273
6335
|
}
|
|
6274
6336
|
}
|
|
6275
6337
|
/**
|
|
@@ -6297,6 +6359,13 @@ var AiJudge = class {
|
|
|
6297
6359
|
confidence: 1
|
|
6298
6360
|
};
|
|
6299
6361
|
}
|
|
6362
|
+
if (decision === "ALLOW" && confidence < 0.7) {
|
|
6363
|
+
return {
|
|
6364
|
+
decision: "DENY",
|
|
6365
|
+
reason: `Low-confidence ALLOW (${confidence.toFixed(2)}) treated as DENY \u2014 ${reason}`,
|
|
6366
|
+
confidence
|
|
6367
|
+
};
|
|
6368
|
+
}
|
|
6300
6369
|
return { decision, reason, confidence };
|
|
6301
6370
|
} catch {
|
|
6302
6371
|
return {
|
|
@@ -6311,6 +6380,7 @@ var AiJudge = class {
|
|
|
6311
6380
|
// src/proxy.ts
|
|
6312
6381
|
var log2 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
|
|
6313
6382
|
`);
|
|
6383
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
6314
6384
|
var Mutex = class {
|
|
6315
6385
|
queue = [];
|
|
6316
6386
|
locked = false;
|
|
@@ -6319,7 +6389,7 @@ var Mutex = class {
|
|
|
6319
6389
|
this.locked = true;
|
|
6320
6390
|
return;
|
|
6321
6391
|
}
|
|
6322
|
-
return new Promise((
|
|
6392
|
+
return new Promise((resolve7, reject) => {
|
|
6323
6393
|
const timer = setTimeout(() => {
|
|
6324
6394
|
const idx = this.queue.indexOf(onReady);
|
|
6325
6395
|
if (idx !== -1) this.queue.splice(idx, 1);
|
|
@@ -6327,7 +6397,7 @@ var Mutex = class {
|
|
|
6327
6397
|
}, timeoutMs);
|
|
6328
6398
|
const onReady = () => {
|
|
6329
6399
|
clearTimeout(timer);
|
|
6330
|
-
|
|
6400
|
+
resolve7();
|
|
6331
6401
|
};
|
|
6332
6402
|
this.queue.push(onReady);
|
|
6333
6403
|
});
|
|
@@ -6361,8 +6431,10 @@ var SolonGateProxy = class {
|
|
|
6361
6431
|
syncManager = null;
|
|
6362
6432
|
aiJudge = null;
|
|
6363
6433
|
upstreamTools = [];
|
|
6434
|
+
guardConfig;
|
|
6364
6435
|
constructor(config) {
|
|
6365
6436
|
this.config = config;
|
|
6437
|
+
this.guardConfig = config.advancedDetection ? { ...DEFAULT_INPUT_GUARD_CONFIG2, advancedDetection: config.advancedDetection } : DEFAULT_INPUT_GUARD_CONFIG2;
|
|
6366
6438
|
this.gate = new SolonGate({
|
|
6367
6439
|
name: config.name ?? "solongate-proxy",
|
|
6368
6440
|
apiKey: "sg_test_proxy_internal_00000000",
|
|
@@ -6384,7 +6456,7 @@ var SolonGateProxy = class {
|
|
|
6384
6456
|
*/
|
|
6385
6457
|
async start() {
|
|
6386
6458
|
log2("Starting SolonGate Proxy...");
|
|
6387
|
-
const apiUrl = this.config.apiUrl ??
|
|
6459
|
+
const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
|
|
6388
6460
|
if (this.config.apiKey) {
|
|
6389
6461
|
if (this.config.apiKey.startsWith("sg_test_")) {
|
|
6390
6462
|
const nodeEnv = process.env.NODE_ENV ?? "";
|
|
@@ -6449,10 +6521,9 @@ var SolonGateProxy = class {
|
|
|
6449
6521
|
log2("AI Judge: CLI flags override cloud config.");
|
|
6450
6522
|
} else if (cloudJudge.enabled) {
|
|
6451
6523
|
let groqKey;
|
|
6452
|
-
const dotenvPath = (
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
const content = readFileSync6(dotenvPath, "utf-8");
|
|
6524
|
+
const dotenvPath = resolve2(".env");
|
|
6525
|
+
if (existsSync3(dotenvPath)) {
|
|
6526
|
+
const content = readFileSync3(dotenvPath, "utf-8");
|
|
6456
6527
|
const match = content.match(/^GROQ_API_KEY=(.+)/m);
|
|
6457
6528
|
if (match) groqKey = match[1].trim();
|
|
6458
6529
|
}
|
|
@@ -6567,7 +6638,7 @@ var SolonGateProxy = class {
|
|
|
6567
6638
|
const MUTEX_TIMEOUT_MS = 3e4;
|
|
6568
6639
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
6569
6640
|
const { name, arguments: args } = request.params;
|
|
6570
|
-
const argsSize =
|
|
6641
|
+
const argsSize = TEXT_ENCODER.encode(JSON.stringify(args ?? {})).length;
|
|
6571
6642
|
if (argsSize > MAX_ARGUMENT_SIZE) {
|
|
6572
6643
|
log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
|
|
6573
6644
|
return {
|
|
@@ -6578,8 +6649,7 @@ var SolonGateProxy = class {
|
|
|
6578
6649
|
log2(`Tool call: ${name}`);
|
|
6579
6650
|
let piResult;
|
|
6580
6651
|
if (args && typeof args === "object") {
|
|
6581
|
-
const
|
|
6582
|
-
const argsCheck = this.config.advancedDetection ? await sanitizeInputAsync("tool.arguments", args, guardConfig) : sanitizeInput("tool.arguments", args);
|
|
6652
|
+
const argsCheck = this.config.advancedDetection ? await sanitizeInputAsync("tool.arguments", args, this.guardConfig) : sanitizeInput("tool.arguments", args);
|
|
6583
6653
|
const hasPromptInjection = argsCheck.threats.some((t) => t.type === "PROMPT_INJECTION");
|
|
6584
6654
|
if (hasPromptInjection) {
|
|
6585
6655
|
const trustResult = "trustScore" in argsCheck ? argsCheck.trustScore : void 0;
|
|
@@ -6598,7 +6668,7 @@ var SolonGateProxy = class {
|
|
|
6598
6668
|
const threats = argsCheck.threats.map((t) => `${t.type}: ${t.description}`).join("; ");
|
|
6599
6669
|
log2(`DENY tool call: ${name} \u2014 ${threats}`);
|
|
6600
6670
|
if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
|
|
6601
|
-
const apiUrl = this.config.apiUrl ??
|
|
6671
|
+
const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
|
|
6602
6672
|
sendAuditLog(this.config.apiKey, apiUrl, {
|
|
6603
6673
|
tool: name,
|
|
6604
6674
|
arguments: args ?? {},
|
|
@@ -6675,7 +6745,7 @@ var SolonGateProxy = class {
|
|
|
6675
6745
|
const evaluationTimeMs = Date.now() - startTime;
|
|
6676
6746
|
log2(`Result: ${decision} (${evaluationTimeMs}ms)`);
|
|
6677
6747
|
if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
|
|
6678
|
-
const apiUrl = this.config.apiUrl ??
|
|
6748
|
+
const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
|
|
6679
6749
|
log2(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
|
|
6680
6750
|
let reason = "allowed";
|
|
6681
6751
|
let matchedRule;
|
|
@@ -6721,8 +6791,7 @@ var SolonGateProxy = class {
|
|
|
6721
6791
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
6722
6792
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
6723
6793
|
const uri = request.params.uri;
|
|
6724
|
-
const
|
|
6725
|
-
const uriCheck = guardConfig ? await sanitizeInputAsync("resource.uri", uri, guardConfig) : sanitizeInput("resource.uri", uri);
|
|
6794
|
+
const uriCheck = this.config.advancedDetection ? await sanitizeInputAsync("resource.uri", uri, this.guardConfig) : sanitizeInput("resource.uri", uri);
|
|
6726
6795
|
if (!uriCheck.safe) {
|
|
6727
6796
|
const threats = uriCheck.threats.map((t) => `${t.type}: ${t.description}`).join("; ");
|
|
6728
6797
|
log2(`DENY resource read: ${uri} \u2014 ${threats}`);
|
|
@@ -6777,8 +6846,7 @@ ${content.text}`;
|
|
|
6777
6846
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
6778
6847
|
const args = request.params.arguments;
|
|
6779
6848
|
if (args && typeof args === "object") {
|
|
6780
|
-
const
|
|
6781
|
-
const argsCheck = promptGuardConfig ? await sanitizeInputAsync("prompt.arguments", args, promptGuardConfig) : sanitizeInput("prompt.arguments", args);
|
|
6849
|
+
const argsCheck = this.config.advancedDetection ? await sanitizeInputAsync("prompt.arguments", args, this.guardConfig) : sanitizeInput("prompt.arguments", args);
|
|
6782
6850
|
if (!argsCheck.safe) {
|
|
6783
6851
|
const threats = argsCheck.threats.map((t) => `${t.type}: ${t.description}`).join("; ");
|
|
6784
6852
|
log2(`DENY prompt get: ${request.params.name} \u2014 ${threats}`);
|
|
@@ -6813,11 +6881,11 @@ ${msg.content.text}`;
|
|
|
6813
6881
|
*/
|
|
6814
6882
|
registerToolsToCloud() {
|
|
6815
6883
|
if (!this.config.apiKey || this.config.apiKey.startsWith("sg_test_")) return;
|
|
6816
|
-
const apiUrl = this.config.apiUrl ??
|
|
6817
|
-
let registered = 0;
|
|
6884
|
+
const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
|
|
6818
6885
|
const total = this.upstreamTools.length;
|
|
6819
|
-
|
|
6820
|
-
|
|
6886
|
+
log2(`Registering ${total} tools to dashboard...`);
|
|
6887
|
+
const promises = this.upstreamTools.map(
|
|
6888
|
+
(tool) => fetch(`${apiUrl}/api/v1/tools`, {
|
|
6821
6889
|
method: "POST",
|
|
6822
6890
|
headers: {
|
|
6823
6891
|
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
@@ -6831,17 +6899,24 @@ ${msg.content.text}`;
|
|
|
6831
6899
|
enabled: true
|
|
6832
6900
|
})
|
|
6833
6901
|
}).then(async (res) => {
|
|
6834
|
-
if (res.ok
|
|
6835
|
-
registered++;
|
|
6836
|
-
} else {
|
|
6902
|
+
if (!res.ok && res.status !== 409) {
|
|
6837
6903
|
const body = await res.text().catch(() => "");
|
|
6838
|
-
|
|
6904
|
+
throw new Error(`${tool.name} (${res.status}): ${body}`);
|
|
6839
6905
|
}
|
|
6840
|
-
})
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6906
|
+
})
|
|
6907
|
+
);
|
|
6908
|
+
Promise.allSettled(promises).then((results) => {
|
|
6909
|
+
const fulfilled = results.filter((r) => r.status === "fulfilled").length;
|
|
6910
|
+
const rejected = results.filter((r) => r.status === "rejected");
|
|
6911
|
+
if (rejected.length > 0) {
|
|
6912
|
+
for (const r of rejected) {
|
|
6913
|
+
log2(`Tool registration failed: ${r.reason}`);
|
|
6914
|
+
}
|
|
6915
|
+
log2(`Tool registration: ${fulfilled}/${total} succeeded, ${rejected.length} failed.`);
|
|
6916
|
+
} else {
|
|
6917
|
+
log2(`Tool registration: ${fulfilled}/${total} succeeded.`);
|
|
6918
|
+
}
|
|
6919
|
+
});
|
|
6845
6920
|
}
|
|
6846
6921
|
/**
|
|
6847
6922
|
* Guess tool permissions from tool name.
|
|
@@ -6862,7 +6937,7 @@ ${msg.content.text}`;
|
|
|
6862
6937
|
*/
|
|
6863
6938
|
registerServerToCloud() {
|
|
6864
6939
|
if (!this.config.apiKey || this.config.apiKey.startsWith("sg_test_")) return;
|
|
6865
|
-
const apiUrl = this.config.apiUrl ??
|
|
6940
|
+
const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
|
|
6866
6941
|
const transport = this.config.upstream.transport ?? "stdio";
|
|
6867
6942
|
let serverName = this.config.name ?? "solongate-proxy";
|
|
6868
6943
|
let serverUrl;
|
|
@@ -6946,7 +7021,7 @@ ${msg.content.text}`;
|
|
|
6946
7021
|
startPolicySync() {
|
|
6947
7022
|
const apiKey = this.config.apiKey;
|
|
6948
7023
|
if (!apiKey) return;
|
|
6949
|
-
const apiUrl = this.config.apiUrl ??
|
|
7024
|
+
const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
|
|
6950
7025
|
this.syncManager = new PolicySyncManager({
|
|
6951
7026
|
localPath: this.config.policyPath ?? null,
|
|
6952
7027
|
apiKey,
|