@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/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, appendFileSync } from "fs";
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
- appendFileSync(AUDIT_LOG_BACKUP_PATH, line, "utf-8");
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: { ...process.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 readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
372
- import { resolve as resolve2, join, dirname as dirname2 } from "path";
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 { execSync } from "child_process";
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 (existsSync3(explicitPath)) {
379
- return { path: resolve2(explicitPath), type: "mcp" };
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 = resolve2(searchPath);
385
- if (existsSync3(full)) return { path: full, type: "mcp" };
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 = resolve2(".mcp.json");
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 (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
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 = readFileSync3(filePath, "utf-8");
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 readFileSync3(join(HOOKS_DIR, filename), "utf-8");
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 = resolve2(dir);
534
- if (!existsSync3(fullDir)) continue;
588
+ const fullDir = resolve3(dir);
589
+ if (!existsSync4(fullDir)) continue;
535
590
  try {
536
591
  if (process.platform === "win32") {
537
592
  try {
538
- execSync(`powershell.exe -Command "icacls '${fullDir}' /remove:d '*S-1-1-0' /T /Q"`, { stdio: "ignore" });
593
+ execFileSync("icacls", [fullDir, "/remove:d", "*S-1-1-0", "/T", "/Q"], { stdio: "ignore" });
539
594
  } catch {
540
595
  }
541
596
  try {
542
- execSync(`attrib -R /S /D "${fullDir}"`, { stdio: "ignore" });
597
+ execFileSync("attrib", ["-R", "/S", "/D", fullDir], { stdio: "ignore" });
543
598
  } catch {
544
599
  }
545
600
  } else {
546
601
  try {
547
- execSync(`chattr -i -R "${fullDir}"`, { stdio: "ignore" });
602
+ execFileSync("chattr", ["-i", "-R", fullDir], { stdio: "ignore" });
548
603
  } catch {
549
604
  }
550
- execSync(`chmod -R u+w "${fullDir}"`, { stdio: "ignore" });
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 = resolve2(file);
558
- if (!existsSync3(fullPath)) continue;
612
+ const fullPath = resolve3(file);
613
+ if (!existsSync4(fullPath)) continue;
559
614
  try {
560
615
  if (process.platform === "win32") {
561
616
  try {
562
- execSync(`powershell.exe -Command "icacls '${fullPath}' /remove:d '*S-1-1-0' /Q"`, { stdio: "ignore" });
617
+ execFileSync("icacls", [fullPath, "/remove:d", "*S-1-1-0", "/Q"], { stdio: "ignore" });
563
618
  } catch {
564
619
  }
565
620
  try {
566
- execSync(`attrib -R "${fullPath}"`, { stdio: "ignore" });
621
+ execFileSync("attrib", ["-R", fullPath], { stdio: "ignore" });
567
622
  } catch {
568
623
  }
569
624
  } else {
570
625
  try {
571
- execSync(`chattr -i "${fullPath}"`, { stdio: "ignore" });
626
+ execFileSync("chattr", ["-i", fullPath], { stdio: "ignore" });
572
627
  } catch {
573
628
  }
574
- execSync(`chmod u+w "${fullPath}"`, { stdio: "ignore" });
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 = resolve2(".solongate", "hooks");
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 = resolve2(client.dir);
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(readFileSync3(settingsPath, "utf-8"));
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 = resolve2(".env");
642
- if (!existsSync3(envPath)) {
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 = resolve2(".gitignore");
659
- if (existsSync3(gitignorePath)) {
660
- let gitignore = readFileSync3(gitignorePath, "utf-8");
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 _c = {
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 = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
763
+ const bannerColors = BANNER_COLORS;
728
764
  console.log("");
729
765
  for (let i = 0; i < bannerLines.length; i++) {
730
- console.log(`${_c.bold}${bannerColors[i % bannerColors.length]}${bannerLines[i]}${_c.reset}`);
766
+ console.log(`${c.bold}${bannerColors[i % bannerColors.length]}${bannerLines[i]}${c.reset}`);
731
767
  }
732
768
  console.log("");
733
- console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
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 = resolve2(".env");
800
- if (existsSync3(envPath)) {
801
- const envContent = readFileSync3(envPath, "utf-8");
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 = resolve2(options.policy);
827
- if (existsSync3(policyPath)) {
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 = resolve2("policy.json");
836
- if (existsSync3(defaultPolicy)) {
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(readFileSync3(configInfo.path, "utf-8"));
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 = resolve2(__dirname, "..", "hooks");
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 readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, copyFileSync } from "fs";
926
- import { resolve as resolve3 } from "path";
927
- import { execSync as execSync2 } from "child_process";
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 (!existsSync4(resolve3("package.json"))) return false;
1020
+ if (!existsSync5(resolve4("package.json"))) return false;
1005
1021
  try {
1006
- const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
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(readFileSync4(resolve3("package.json"), "utf-8"));
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 (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
1021
- if (existsSync4(resolve3(binPath))) return resolve3(binPath);
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 (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
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 = resolve3(c3);
1040
- if (existsSync4(full)) {
1055
+ const full = resolve4(c3);
1056
+ if (existsSync5(full)) {
1041
1057
  try {
1042
- const content = readFileSync4(full, "utf-8");
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 (existsSync4(resolve3(c3))) return resolve3(c3);
1067
+ if (existsSync5(resolve4(c3))) return resolve4(c3);
1052
1068
  }
1053
1069
  return null;
1054
1070
  }
1055
1071
  function detectPackageManager() {
1056
- if (existsSync4(resolve3("pnpm-lock.yaml"))) return "pnpm";
1057
- if (existsSync4(resolve3("yarn.lock"))) return "yarn";
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(readFileSync4(resolve3("package.json"), "utf-8"));
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
- execSync2(cmd, { stdio: "pipe", cwd: process.cwd() });
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 = readFileSync4(filePath, "utf-8");
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 ? resolve3(opts.file) : findTsEntryFile();
1200
- if (!entryFile || !existsSync4(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 (!existsSync4(backupPath)) {
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 (!existsSync4(backupPath)) {
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
- c = {
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 existsSync5 } from "fs";
1298
- import { resolve as resolve4, join as join2 } from "path";
1299
- import { execSync as execSync3 } from "child_process";
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 ? c2.blue3 : c2.blue5;
1326
- process.stderr.write(`\r ${color}${frames[i++ % frames.length]}${c2.reset} ${c2.dim}${message}${c2.reset}`);
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 ${c2.green}\u2713${c2.reset} ${message}${" ".repeat(20)}
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 ${c2.red}\u2717${c2.reset} ${message} \u2014 failed${" ".repeat(10)}
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
- log4("");
1369
- log4(" Error: Project name required.");
1370
- log4("");
1371
- log4(" Usage: npx @solongate/proxy create <name>");
1372
- log4("");
1373
- log4(" Examples:");
1374
- log4(" npx @solongate/proxy create my-mcp-server");
1375
- log4(" npx @solongate/proxy create weather-api");
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
- log4(`
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 = resolve4(opts.name);
1532
- printBanner2("Create MCP Server");
1533
- if (existsSync5(dir)) {
1534
- log4(` ${c2.red}Error:${c2.reset} Directory "${opts.name}" already exists.`);
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
- execSync3("npm install", { cwd: dir, stdio: "pipe" });
1524
+ execSync2("npm install", { cwd: dir, stdio: "pipe" });
1544
1525
  });
1545
1526
  }
1546
- log4("");
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
- log4(` ${c2.dim}\u2502${c2.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c2.dim}\u2502${c2.reset}`);
1534
+ log3(` ${c.dim}\u2502${c.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c.dim}\u2502${c.reset}`);
1554
1535
  };
1555
- const bEmpty = () => log4(` ${c2.dim}\u2502${c2.reset} ${" ".repeat(W)} ${c2.dim}\u2502${c2.reset}`);
1556
- log4(` ${c2.dim}\u256D${hr}\u256E${c2.reset}`);
1557
- log4(` ${c2.dim}\u2502${c2.reset} ${c2.green}${c2.bold}\u2714 MCP Server created successfully!${c2.reset}${" ".repeat(W - 35)} ${c2.dim}\u2502${c2.reset}`);
1558
- log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
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(`${c2.yellow}Next steps:${c2.reset}`);
1541
+ bLine(`${c.yellow}Next steps:${c.reset}`);
1561
1542
  bEmpty();
1562
- bLine(` ${c2.cyan}$${c2.reset} cd ${c2.bold}${opts.name}${c2.reset}`);
1563
- bLine(` ${c2.cyan}$${c2.reset} npm run build`);
1564
- bLine(` ${c2.cyan}$${c2.reset} npm run dev ${c2.dim}# dev mode${c2.reset}`);
1565
- bLine(` ${c2.cyan}$${c2.reset} npm start ${c2.dim}# production${c2.reset}`);
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
- log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1548
+ log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
1568
1549
  bEmpty();
1569
- bLine(`${c2.yellow}Set your API key:${c2.reset}`);
1550
+ bLine(`${c.yellow}Set your API key:${c.reset}`);
1570
1551
  bEmpty();
1571
- bLine(` ${c2.cyan}$${c2.reset} export SOLONGATE_API_KEY=${c2.blue3}sg_live_xxx${c2.reset}`);
1552
+ bLine(` ${c.cyan}$${c.reset} export SOLONGATE_API_KEY=${c.blue3}sg_live_xxx${c.reset}`);
1572
1553
  bEmpty();
1573
- bLine(`${c2.dim}https://dashboard.solongate.com/api-keys/${c2.reset}`);
1554
+ bLine(`${c.dim}https://dashboard.solongate.com/api-keys/${c.reset}`);
1574
1555
  bEmpty();
1575
- log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1556
+ log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
1576
1557
  bEmpty();
1577
- bLine(`${c2.yellow}Use with any MCP server:${c2.reset}`);
1558
+ bLine(`${c.yellow}Use with any MCP server:${c.reset}`);
1578
1559
  bEmpty();
1579
- bLine(` ${c2.cyan}$${c2.reset} solongate-proxy -- npx @modelcontextprotocol/server-filesystem .`);
1580
- bLine(` ${c2.cyan}$${c2.reset} solongate-proxy -- npx @playwright/mcp@latest`);
1581
- bLine(` ${c2.dim}Wraps any MCP server or AI tool with SolonGate protection${c2.reset}`);
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
- log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1564
+ log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
1584
1565
  bEmpty();
1585
- bLine(`${c2.yellow}Use with Claude Code / Cursor / MCP client:${c2.reset}`);
1566
+ bLine(`${c.yellow}Use with Claude Code / Cursor / MCP client:${c.reset}`);
1586
1567
  bEmpty();
1587
- bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .mcp.json`);
1588
- bLine(` ${c2.dim}2.${c2.reset} Open this folder in your MCP client`);
1589
- bLine(` ${c2.dim}3.${c2.reset} .mcp.json is auto-detected on startup`);
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(`${c2.yellow}Direct test (without MCP client):${c2.reset}`);
1572
+ bLine(`${c.yellow}Direct test (without MCP client):${c.reset}`);
1592
1573
  bEmpty();
1593
- bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
1594
- bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
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
- log4(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
1597
- log4("");
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
- c2 = {
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
- log4(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
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 readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
1632
- import { resolve as resolve5 } from "path";
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 = resolve5(".env");
1636
- if (!existsSync6(envPath)) return;
1597
+ const envPath = resolve6(".env");
1598
+ if (!existsSync7(envPath)) return;
1637
1599
  try {
1638
- const content = readFileSync5(envPath, "utf-8");
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
- log5(red("ERROR: API key not found."));
1681
- log5("");
1682
- log5("Set it in .env file:");
1683
- log5(" SOLONGATE_API_KEY=sg_live_...");
1684
- log5("");
1685
- log5("Or pass via environment:");
1686
- log5(` SOLONGATE_API_KEY=sg_live_... solongate-proxy ${command}`);
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
- log5(red("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
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: resolve5(file), policyId };
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
- log5(yellow("No policies found. Create one in the dashboard first."));
1707
- log5(dim(" https://dashboard.solongate.com/policies"));
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
- log5(red(`Policy not found: ${policyId}`));
1714
- log5("");
1715
- log5("Available policies:");
1675
+ log4(red(`Policy not found: ${policyId}`));
1676
+ log4("");
1677
+ log4("Available policies:");
1716
1678
  for (const p of policies) {
1717
- log5(` ${dim("\u2022")} ${p.id}`);
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
- log5("");
1726
- log5(bold(` Policies (${policies.length})`));
1727
- log5(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"));
1728
- log5("");
1729
- for (const p of policies) {
1730
- try {
1731
- const full = await fetchCloudPolicy(apiKey, API_URL, p.id);
1732
- printPolicySummary(p, full.rules);
1733
- } catch {
1734
- printPolicySummary(p, []);
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
- log5(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"));
1738
- log5("");
1739
- log5(` ${dim("View details:")} solongate-proxy list --policy-id <ID>`);
1740
- log5(` ${dim("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
1741
- log5(` ${dim("Push policy:")} solongate-proxy push --policy-id <ID>`);
1742
- log5("");
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
- log5(` ${cyan(p.id)}`);
1749
- log5(` ${bold(p.name)} ${dim(`v${p.version ?? "?"}`)}`);
1750
- log5(` ${dim("Rules:")} ${ruleCount} ${green(`${allowCount} ALLOW`)} ${red(`${denyCount} DENY`)}`);
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
- log5(` ${dim("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
1714
+ log4(` ${dim("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
1753
1715
  }
1754
- log5("");
1716
+ log4("");
1755
1717
  }
1756
1718
  function printPolicyDetail(policy) {
1757
- log5("");
1758
- log5(bold(` ${policy.name}`));
1759
- log5(` ${dim("ID:")} ${cyan(policy.id)} ${dim("Version:")} ${policy.version} ${dim("Rules:")} ${policy.rules.length}`);
1760
- log5("");
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
- log5(yellow(" No rules defined."));
1763
- log5("");
1724
+ log4(yellow(" No rules defined."));
1725
+ log4("");
1764
1726
  return;
1765
1727
  }
1766
- log5(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"));
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
- log5("");
1770
- log5(` ${effectColor(rule.effect.padEnd(5))} ${bold(rule.toolPattern)} ${dim(`P:${rule.priority}`)}`);
1731
+ log4("");
1732
+ log4(` ${effectColor(rule.effect.padEnd(5))} ${bold(rule.toolPattern)} ${dim(`P:${rule.priority}`)}`);
1771
1733
  if (rule.description) {
1772
- log5(` ${dim(rule.description)}`);
1734
+ log4(` ${dim(rule.description)}`);
1773
1735
  }
1774
- log5(` ${dim(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
1736
+ log4(` ${dim(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
1775
1737
  if (rule.pathConstraints) {
1776
1738
  const pc = rule.pathConstraints;
1777
- if (pc.rootDirectory) log5(` ${magenta("ROOT")} ${pc.rootDirectory}`);
1778
- if (pc.allowed?.length) log5(` ${green("PATHS")} ${pc.allowed.join(", ")}`);
1779
- if (pc.denied?.length) log5(` ${red("DENY")} ${pc.denied.join(", ")}`);
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) log5(` ${green("CMDS")} ${cc.allowed.join(", ")}`);
1784
- if (cc.denied?.length) log5(` ${red("DENY")} ${cc.denied.join(", ")}`);
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) log5(` ${green("FILES")} ${fc.allowed.join(", ")}`);
1789
- if (fc.denied?.length) log5(` ${red("DENY")} ${fc.denied.join(", ")}`);
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) log5(` ${green("URLS")} ${uc.allowed.join(", ")}`);
1794
- if (uc.denied?.length) log5(` ${red("DENY")} ${uc.denied.join(", ")}`);
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
- log5("");
1798
- log5(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"));
1799
- log5("");
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
- log5(red("No policies found. Create one in the dashboard first."));
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
- log5(dim(`Auto-selecting only policy: ${policyId}`));
1772
+ log4(dim(`Auto-selecting only policy: ${policyId}`));
1811
1773
  } else {
1812
- log5(yellow(`Found ${policies.length} policies:`));
1813
- log5("");
1774
+ log4(yellow(`Found ${policies.length} policies:`));
1775
+ log4("");
1814
1776
  for (const p of policies) {
1815
- log5(` ${cyan(p.id)} ${p.name} ${dim(`v${p.version ?? "?"}`)}`);
1777
+ log4(` ${cyan(p.id)} ${p.name} ${dim(`v${p.version ?? "?"}`)}`);
1816
1778
  }
1817
- log5("");
1818
- log5("Use --policy-id <ID> to specify which one to pull.");
1779
+ log4("");
1780
+ log4("Use --policy-id <ID> to specify which one to pull.");
1819
1781
  process.exit(1);
1820
1782
  }
1821
1783
  }
1822
- log5(`Pulling ${cyan(policyId)} from dashboard...`);
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
- log5("");
1828
- log5(green(" Saved to: ") + file);
1829
- log5(` ${dim("Name:")} ${policy.name}`);
1830
- log5(` ${dim("Version:")} ${policy.version}`);
1831
- log5(` ${dim("Rules:")} ${policy.rules.length}`);
1832
- log5("");
1833
- log5(dim("The policy file does not contain an ID."));
1834
- log5(dim("Use --policy-id to specify the target when pushing/pulling."));
1835
- log5("");
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 (!existsSync6(file)) {
1839
- log5(red(`ERROR: File not found: ${file}`));
1800
+ if (!existsSync7(file)) {
1801
+ log4(red(`ERROR: File not found: ${file}`));
1840
1802
  process.exit(1);
1841
1803
  }
1842
1804
  if (!policyId) {
1843
- log5(red("ERROR: --policy-id is required for push."));
1844
- log5("");
1845
- log5("This determines which cloud policy to update.");
1846
- log5("");
1847
- log5("Usage:");
1848
- log5(" solongate-proxy push --policy-id my-policy");
1849
- log5(" solongate-proxy push --policy-id my-policy --file custom.json");
1850
- log5("");
1851
- log5("List your policies:");
1852
- log5(" solongate-proxy list");
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 = readFileSync5(file, "utf-8");
1817
+ const content = readFileSync6(file, "utf-8");
1856
1818
  let policy;
1857
1819
  try {
1858
1820
  policy = JSON.parse(content);
1859
1821
  } catch {
1860
- log5(red(`ERROR: Invalid JSON in ${file}`));
1822
+ log4(red(`ERROR: Invalid JSON in ${file}`));
1861
1823
  process.exit(1);
1862
1824
  }
1863
- log5(`Pushing to ${cyan(policyId)}...`);
1864
- log5(` ${dim("File:")} ${file}`);
1865
- log5(` ${dim("Name:")} ${policy.name || "Unnamed"}`);
1866
- log5(` ${dim("Rules:")} ${(policy.rules || []).length}`);
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
- log5(red(`ERROR: Push failed (${res.status}): ${body}`));
1850
+ log4(red(`ERROR: Push failed (${res.status}): ${body}`));
1889
1851
  process.exit(1);
1890
1852
  }
1891
1853
  const data = await res.json();
1892
- log5("");
1893
- log5(green(` Pushed to cloud: v${data._version ?? "created"}`));
1894
- log5(` ${dim("Policy ID:")} ${policyId}`);
1895
- log5(` ${dim("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
1896
- log5("");
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
- log5(red(`Unknown command: ${command}`));
1909
- log5("");
1910
- log5(bold("Usage:"));
1911
- log5(" solongate-proxy list List all policies");
1912
- log5(" solongate-proxy list --policy-id <ID> Show policy details");
1913
- log5(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
1914
- log5(" solongate-proxy push --policy-id <ID> Push local file to cloud");
1915
- log5("");
1916
- log5(bold("Flags:"));
1917
- log5(" --policy-id, --id <ID> Cloud policy ID (required for push)");
1918
- log5(" --file, -f <path> Local file path (default: policy.json)");
1919
- log5(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
1920
- log5("");
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
- log5(red(`ERROR: ${err instanceof Error ? err.message : String(err)}`));
1886
+ log4(red(`ERROR: ${err instanceof Error ? err.message : String(err)}`));
1925
1887
  process.exit(1);
1926
1888
  }
1927
1889
  }
1928
- var log5, dim, bold, green, red, yellow, cyan, magenta, API_URL;
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
- log5 = (...args) => process.stderr.write(`${args.map(String).join(" ")}
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)\s+(instructions?|rules?|constraints?|guidelines?)\b/i,
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
- /\bignor(iere|a|e[zs]?)\s+(alle|todas?|toutes?|tüm|все)\b/i,
2112
- /\bигнорируйте\b/i,
2113
- /\byoksay\b/i,
2114
- /\bvorherigen?\s+Anweisungen\b/i,
2115
- /\binstrucciones\s+anteriores\b/i,
2116
- /\binstructions?\s+pr[eé]c[eé]dentes?\b/i,
2117
- /\bönceki\s+talimatlar\b/i
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
- const transformers = await getTransformers();
2246
- if (!transformers) return null;
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 (const text of texts) {
2293
- const output = await pipe(text, { pooling: "mean", normalize: true });
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 INVISIBLE_UNICODE_PATTERNS = [
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
- for (const pattern of INVISIBLE_UNICODE_PATTERNS) {
2769
- const matches = value.match(new RegExp(pattern.source, "g"));
2770
- if (matches) {
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
- if (/^https?:\/\//i.test(trimmed)) return;
3127
- if (trimmed.includes("/") || trimmed.includes("\\")) {
3128
- const normalized = trimmed.replace(/\\/g, "/");
3129
- const parts = normalized.split("/");
3130
- const basename = parts[parts.length - 1];
3131
- if (basename && basename.length > 0) {
3132
- addFilename(basename);
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
- if (trimmed.includes(" ")) {
3137
- for (const token of trimmed.split(/\s+/)) {
3138
- if (token.includes("/") || token.includes("\\")) {
3139
- const parts = token.replace(/\\/g, "/").split("/");
3140
- const basename = parts[parts.length - 1];
3141
- if (basename && looksLikeFilename(basename)) addFilename(basename);
3142
- } else if (looksLikeFilename(token)) {
3143
- addFilename(token);
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
- return;
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
- if (looksLikeFilename(trimmed)) {
3149
- addFilename(trimmed);
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
- const knownFiles = /* @__PURE__ */ new Set([
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 = [...policySet.rules].sort(
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
- const ruleResult = validatePolicyRule(rule);
3561
- warnings.push(...ruleResult.warnings);
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 decision = evaluatePolicy(this.policySet, request);
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
- const newHistory = [...history, version];
3752
- this.versions.set(id, newHistory);
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 (JSON.stringify(oldRule) !== JSON.stringify(newRule)) {
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(policySet, Object.keys(policySet).sort());
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.push({ name: toolName, timestamp: Date.now() });
3888
- while (this.recentCalls.length > CHAIN_WINDOW_SIZE) {
3889
- this.recentCalls.shift();
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
- return this.recentCalls.some(
3901
- (call) => DATA_SOURCE_TOOLS.has(call.name) && call.timestamp >= cutoff
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: (/* @__PURE__ */ new Date()).toISOString()
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: (/* @__PURE__ */ new Date()).toISOString()
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: (/* @__PURE__ */ new Date()).toISOString(),
4048
+ timestamp,
3962
4049
  evaluationTimeMs: 0
3963
4050
  },
3964
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
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: (/* @__PURE__ */ new Date()).toISOString()
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(params);
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: (/* @__PURE__ */ new Date()).toISOString()
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: (/* @__PURE__ */ new Date()).toISOString()
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(1e4)
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
- throw new LicenseError(
4567
- "Unable to reach SolonGate license server. Check your internet connection."
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
- this.pollingTimer = setInterval(async () => {
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
- const transformers = await getTransformers2();
4994
- if (!transformers) return null;
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 (const text of texts) {
5041
- const output = await pipe(text, { pooling: "mean", normalize: true });
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
- const wildcardCount = (value.match(/\*/g) || []).length;
5438
- if (wildcardCount > MAX_WILDCARDS_PER_VALUE) return true;
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
- for (const pattern of PROMPT_INJECTION_PATTERNS) {
5560
- if (pattern.test(value)) return true;
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 < MIN_LENGTH_FOR_ENTROPY_CHECK) return true;
5668
+ if (value.length < INPUT_GUARD_MIN_ENTROPY_LENGTH) return true;
5597
5669
  const entropy = calculateShannonEntropy(value);
5598
- return entropy <= ENTROPY_THRESHOLD;
5670
+ return entropy <= INPUT_GUARD_ENTROPY_THRESHOLD;
5599
5671
  }
5600
5672
  function calculateShannonEntropy(str) {
5601
- const freq = /* @__PURE__ */ new Map();
5602
- for (const char of str) {
5603
- freq.set(char, (freq.get(char) ?? 0) + 1);
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 (const count of freq.values()) {
5608
- const p = count / len;
5609
- if (p > 0) {
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 threats = [];
5753
- if (Array.isArray(obj)) {
5754
- for (let i = 0; i < obj.length; i++) {
5755
- const result = await sanitizeInputAsync(`${basePath}[${i}]`, obj[i], config);
5756
- threats.push(...result.threats);
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 INVISIBLE_UNICODE_PATTERNS2 = [
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
- for (const pattern of INVISIBLE_UNICODE_PATTERNS2) {
5868
- const matches = value.match(new RegExp(pattern.source, "g"));
5869
- if (matches) {
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
- skipNextWatch = false;
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.skipNextWatch) {
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 existingRes = await fetch(`${this.apiUrl}/api/v1/policies/${cloudId}`, {
6087
- headers: { "Authorization": `Bearer ${this.apiKey}` }
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 method = existingRes.ok ? "PUT" : "POST";
6090
- const url = existingRes.ok ? `${this.apiUrl}/api/v1/policies/${cloudId}` : `${this.apiUrl}/api/v1/policies`;
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: JSON.stringify({
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 (!res.ok) {
6106
- const body = await res.text().catch(() => "");
6107
- throw new Error(`Push failed (${res.status}): ${body}`);
6128
+ if (putRes.ok) {
6129
+ const data = await putRes.json();
6130
+ return { version: Number(data._version ?? policy.version) };
6108
6131
  }
6109
- const data = await res.json();
6110
- return { version: Number(data._version ?? policy.version) };
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.skipNextWatch = true;
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: args,
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 controller = new AbortController();
6220
- const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
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: controller.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((resolve6, reject) => {
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
- resolve6();
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 ?? "https://api.solongate.com";
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 = (await import("path")).resolve(".env");
6453
- const { existsSync: existsSync7, readFileSync: readFileSync6 } = await import("fs");
6454
- if (existsSync7(dotenvPath)) {
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 = new TextEncoder().encode(JSON.stringify(args ?? {})).length;
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 guardConfig = this.config.advancedDetection ? { ...DEFAULT_INPUT_GUARD_CONFIG2, advancedDetection: this.config.advancedDetection } : DEFAULT_INPUT_GUARD_CONFIG2;
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 ?? "https://api.solongate.com";
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 ?? "https://api.solongate.com";
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 guardConfig = this.config.advancedDetection ? { ...DEFAULT_INPUT_GUARD_CONFIG2, advancedDetection: this.config.advancedDetection } : void 0;
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 promptGuardConfig = this.config.advancedDetection ? { ...DEFAULT_INPUT_GUARD_CONFIG2, advancedDetection: this.config.advancedDetection } : void 0;
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 ?? "https://api.solongate.com";
6817
- let registered = 0;
6884
+ const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
6818
6885
  const total = this.upstreamTools.length;
6819
- for (const tool of this.upstreamTools) {
6820
- fetch(`${apiUrl}/api/v1/tools`, {
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 || res.status === 409) {
6835
- registered++;
6836
- } else {
6902
+ if (!res.ok && res.status !== 409) {
6837
6903
  const body = await res.text().catch(() => "");
6838
- log2(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
6904
+ throw new Error(`${tool.name} (${res.status}): ${body}`);
6839
6905
  }
6840
- }).catch((err) => {
6841
- log2(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
6842
- });
6843
- }
6844
- log2(`Registering ${total} tools to dashboard...`);
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 ?? "https://api.solongate.com";
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 ?? "https://api.solongate.com";
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,