@solongate/proxy 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +279 -513
  2. package/dist/init.js +5 -0
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -456,6 +456,11 @@ const SSRF_IN_CMD = [
456
456
  /https?:\\/\\/10\\./, /https?:\\/\\/172\\.(1[6-9]|2\\d|3[01])\\./,
457
457
  /https?:\\/\\/192\\.168\\./, /https?:\\/\\/169\\.254\\./,
458
458
  /metadata\\.google\\.internal/i,
459
+ /\\b127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/, /\\b0\\.0\\.0\\.0\\b/,
460
+ /\\b10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/,
461
+ /\\b172\\.(1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}\\b/,
462
+ /\\b192\\.168\\.\\d{1,3}\\.\\d{1,3}\\b/,
463
+ /\\b169\\.254\\.\\d{1,3}\\.\\d{1,3}\\b/,
459
464
  ];
460
465
  const SQL_INJECTION = [
461
466
  /'\\s{0,20}(OR|AND)\\s{0,20}'.{0,200}'/i,
@@ -1333,118 +1338,6 @@ var init_create = __esm({
1333
1338
  }
1334
1339
  });
1335
1340
 
1336
- // ../core/dist/index.js
1337
- import { z } from "zod";
1338
- var Permission = {
1339
- READ: "READ",
1340
- WRITE: "WRITE",
1341
- EXECUTE: "EXECUTE"
1342
- };
1343
- var PermissionSchema = z.enum(["READ", "WRITE", "EXECUTE"]);
1344
- var NO_PERMISSIONS = Object.freeze(
1345
- /* @__PURE__ */ new Set()
1346
- );
1347
- var READ_ONLY = Object.freeze(
1348
- /* @__PURE__ */ new Set([Permission.READ])
1349
- );
1350
- var PolicyRuleSchema = z.object({
1351
- id: z.string().min(1).max(256),
1352
- description: z.string().max(1024),
1353
- effect: z.enum(["ALLOW", "DENY"]),
1354
- priority: z.number().int().min(0).max(1e4).default(1e3),
1355
- toolPattern: z.string().min(1).max(512),
1356
- permission: z.enum(["READ", "WRITE", "EXECUTE"]),
1357
- minimumTrustLevel: z.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
1358
- argumentConstraints: z.record(z.unknown()).optional(),
1359
- pathConstraints: z.object({
1360
- allowed: z.array(z.string()).optional(),
1361
- denied: z.array(z.string()).optional(),
1362
- rootDirectory: z.string().optional(),
1363
- allowSymlinks: z.boolean().optional()
1364
- }).optional(),
1365
- enabled: z.boolean().default(true),
1366
- createdAt: z.string().datetime(),
1367
- updatedAt: z.string().datetime()
1368
- });
1369
- var PolicySetSchema = z.object({
1370
- id: z.string().min(1).max(256),
1371
- name: z.string().min(1).max(256),
1372
- description: z.string().max(2048),
1373
- version: z.number().int().min(0),
1374
- rules: z.array(PolicyRuleSchema),
1375
- createdAt: z.string().datetime(),
1376
- updatedAt: z.string().datetime()
1377
- });
1378
- var SECURITY_CONTEXT_TIMEOUT_MS = 5 * 60 * 1e3;
1379
- var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
1380
- pathTraversal: true,
1381
- shellInjection: true,
1382
- wildcardAbuse: true,
1383
- lengthLimit: 4096,
1384
- entropyLimit: true,
1385
- ssrf: true,
1386
- sqlInjection: true
1387
- });
1388
- var SSRF_PATTERNS = [
1389
- /^https?:\/\/localhost\b/i,
1390
- /^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
1391
- /^https?:\/\/0\.0\.0\.0/,
1392
- /^https?:\/\/\[::1\]/,
1393
- // IPv6 loopback
1394
- /^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
1395
- // 10.x.x.x
1396
- /^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
1397
- // 172.16-31.x.x
1398
- /^https?:\/\/192\.168\./,
1399
- // 192.168.x.x
1400
- /^https?:\/\/169\.254\./,
1401
- // Link-local / AWS metadata
1402
- /metadata\.google\.internal/i,
1403
- // GCP metadata
1404
- /^https?:\/\/metadata\b/i,
1405
- // Generic metadata endpoint
1406
- // IPv6 bypass patterns
1407
- /^https?:\/\/\[fe80:/i,
1408
- // IPv6 link-local
1409
- /^https?:\/\/\[fc00:/i,
1410
- // IPv6 unique local
1411
- /^https?:\/\/\[fd[0-9a-f]{2}:/i,
1412
- // IPv6 unique local (fd00::/8)
1413
- /^https?:\/\/\[::ffff:127\./i,
1414
- // IPv4-mapped IPv6 loopback
1415
- /^https?:\/\/\[::ffff:10\./i,
1416
- // IPv4-mapped IPv6 private
1417
- /^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
1418
- // IPv4-mapped IPv6 private
1419
- /^https?:\/\/\[::ffff:192\.168\./i,
1420
- // IPv4-mapped IPv6 private
1421
- /^https?:\/\/\[::ffff:169\.254\./i,
1422
- // IPv4-mapped IPv6 link-local
1423
- // Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
1424
- /^https?:\/\/0x[0-9a-f]+\b/i,
1425
- // Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
1426
- /^https?:\/\/0[0-7]{1,3}\./
1427
- ];
1428
- function detectDecimalIP(value) {
1429
- const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
1430
- if (!match || !match[1]) return false;
1431
- const decimal = parseInt(match[1], 10);
1432
- if (isNaN(decimal) || decimal > 4294967295) return false;
1433
- return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
1434
- decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
1435
- decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
1436
- decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
1437
- decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
1438
- decimal === 0;
1439
- }
1440
- function detectSSRF(value) {
1441
- for (const pattern of SSRF_PATTERNS) {
1442
- if (pattern.test(value)) return true;
1443
- }
1444
- if (detectDecimalIP(value)) return true;
1445
- return false;
1446
- }
1447
-
1448
1341
  // src/config.ts
1449
1342
  import { readFileSync, existsSync } from "fs";
1450
1343
  import { resolve } from "path";
@@ -1492,48 +1385,72 @@ var PRESETS = {
1492
1385
  restricted: {
1493
1386
  id: "restricted",
1494
1387
  name: "Restricted",
1495
- description: "Blocks dangerous tools (shell, web), allows safe tools",
1388
+ description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
1496
1389
  version: 1,
1497
1390
  rules: [
1498
1391
  {
1499
- id: "deny-shell",
1500
- description: "Block shell execution",
1501
- effect: "DENY",
1502
- priority: 100,
1503
- toolPattern: "*shell*",
1504
- permission: "EXECUTE",
1505
- minimumTrustLevel: "UNTRUSTED",
1506
- enabled: true,
1507
- createdAt: "",
1508
- updatedAt: ""
1509
- },
1510
- {
1511
- id: "deny-exec",
1512
- description: "Block command execution",
1392
+ id: "deny-dangerous-commands",
1393
+ description: "Block dangerous shell commands",
1513
1394
  effect: "DENY",
1514
- priority: 101,
1515
- toolPattern: "*exec*",
1395
+ priority: 50,
1396
+ toolPattern: "*",
1516
1397
  permission: "EXECUTE",
1517
1398
  minimumTrustLevel: "UNTRUSTED",
1518
1399
  enabled: true,
1400
+ commandConstraints: {
1401
+ denied: [
1402
+ "rm -rf *",
1403
+ "rm -r /*",
1404
+ "mkfs*",
1405
+ "dd if=*",
1406
+ "curl*|*bash*",
1407
+ "curl*|*sh*",
1408
+ "wget*|*bash*",
1409
+ "wget*|*sh*",
1410
+ "shutdown*",
1411
+ "reboot*",
1412
+ "kill -9*",
1413
+ "chmod*777*",
1414
+ "iptables*",
1415
+ "passwd*",
1416
+ "useradd*",
1417
+ "userdel*"
1418
+ ]
1419
+ },
1519
1420
  createdAt: "",
1520
1421
  updatedAt: ""
1521
1422
  },
1522
1423
  {
1523
- id: "deny-eval",
1524
- description: "Block code eval",
1424
+ id: "deny-sensitive-paths",
1425
+ description: "Block access to sensitive files",
1525
1426
  effect: "DENY",
1526
- priority: 102,
1527
- toolPattern: "*eval*",
1427
+ priority: 51,
1428
+ toolPattern: "*",
1528
1429
  permission: "EXECUTE",
1529
1430
  minimumTrustLevel: "UNTRUSTED",
1530
1431
  enabled: true,
1432
+ pathConstraints: {
1433
+ denied: [
1434
+ "**/.env*",
1435
+ "**/.ssh/**",
1436
+ "**/.aws/**",
1437
+ "**/.kube/**",
1438
+ "**/credentials*",
1439
+ "**/secrets*",
1440
+ "**/*.pem",
1441
+ "**/*.key",
1442
+ "/etc/passwd",
1443
+ "/etc/shadow",
1444
+ "/proc/**",
1445
+ "/dev/**"
1446
+ ]
1447
+ },
1531
1448
  createdAt: "",
1532
1449
  updatedAt: ""
1533
1450
  },
1534
1451
  {
1535
1452
  id: "allow-rest",
1536
- description: "Allow all other tools",
1453
+ description: "Allow all other tool calls",
1537
1454
  effect: "ALLOW",
1538
1455
  priority: 1e3,
1539
1456
  toolPattern: "*",
@@ -1550,7 +1467,7 @@ var PRESETS = {
1550
1467
  "read-only": {
1551
1468
  id: "read-only",
1552
1469
  name: "Read Only",
1553
- description: "Only allows read operations, blocks writes and execution",
1470
+ description: "Only allows read/list/get/search/query tools",
1554
1471
  version: 1,
1555
1472
  rules: [
1556
1473
  {
@@ -1617,6 +1534,110 @@ var PRESETS = {
1617
1534
  createdAt: "",
1618
1535
  updatedAt: ""
1619
1536
  },
1537
+ sandbox: {
1538
+ id: "sandbox",
1539
+ name: "Sandbox",
1540
+ description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
1541
+ version: 1,
1542
+ rules: [
1543
+ {
1544
+ id: "deny-dangerous-commands",
1545
+ description: "Block dangerous shell commands",
1546
+ effect: "DENY",
1547
+ priority: 50,
1548
+ toolPattern: "*",
1549
+ permission: "EXECUTE",
1550
+ minimumTrustLevel: "UNTRUSTED",
1551
+ enabled: true,
1552
+ commandConstraints: {
1553
+ denied: [
1554
+ "rm -rf *",
1555
+ "rm -r /*",
1556
+ "mkfs*",
1557
+ "dd if=*",
1558
+ "curl*|*bash*",
1559
+ "wget*|*sh*",
1560
+ "shutdown*",
1561
+ "reboot*",
1562
+ "chmod*777*"
1563
+ ]
1564
+ },
1565
+ createdAt: "",
1566
+ updatedAt: ""
1567
+ },
1568
+ {
1569
+ id: "allow-safe-commands",
1570
+ description: "Allow safe shell commands only",
1571
+ effect: "ALLOW",
1572
+ priority: 100,
1573
+ toolPattern: "*shell*",
1574
+ permission: "EXECUTE",
1575
+ minimumTrustLevel: "UNTRUSTED",
1576
+ enabled: true,
1577
+ commandConstraints: {
1578
+ allowed: [
1579
+ "ls*",
1580
+ "cat*",
1581
+ "head*",
1582
+ "tail*",
1583
+ "wc*",
1584
+ "grep*",
1585
+ "find*",
1586
+ "echo*",
1587
+ "pwd",
1588
+ "whoami",
1589
+ "date",
1590
+ "env",
1591
+ "git*",
1592
+ "npm*",
1593
+ "pnpm*",
1594
+ "yarn*",
1595
+ "node*",
1596
+ "python*",
1597
+ "pip*"
1598
+ ]
1599
+ },
1600
+ createdAt: "",
1601
+ updatedAt: ""
1602
+ },
1603
+ {
1604
+ id: "deny-sensitive-paths",
1605
+ description: "Block access to sensitive files",
1606
+ effect: "DENY",
1607
+ priority: 51,
1608
+ toolPattern: "*",
1609
+ permission: "EXECUTE",
1610
+ minimumTrustLevel: "UNTRUSTED",
1611
+ enabled: true,
1612
+ pathConstraints: {
1613
+ denied: [
1614
+ "**/.env*",
1615
+ "**/.ssh/**",
1616
+ "**/.aws/**",
1617
+ "**/credentials*",
1618
+ "**/*.pem",
1619
+ "**/*.key"
1620
+ ]
1621
+ },
1622
+ createdAt: "",
1623
+ updatedAt: ""
1624
+ },
1625
+ {
1626
+ id: "allow-rest",
1627
+ description: "Allow all other tools",
1628
+ effect: "ALLOW",
1629
+ priority: 1e3,
1630
+ toolPattern: "*",
1631
+ permission: "EXECUTE",
1632
+ minimumTrustLevel: "UNTRUSTED",
1633
+ enabled: true,
1634
+ createdAt: "",
1635
+ updatedAt: ""
1636
+ }
1637
+ ],
1638
+ createdAt: "",
1639
+ updatedAt: ""
1640
+ },
1620
1641
  permissive: {
1621
1642
  id: "permissive",
1622
1643
  name: "Permissive",
@@ -1666,7 +1687,6 @@ function parseArgs(argv) {
1666
1687
  let policySource = "restricted";
1667
1688
  let name = "solongate-proxy";
1668
1689
  let verbose = false;
1669
- let validateInput = true;
1670
1690
  let rateLimitPerTool;
1671
1691
  let globalRateLimit;
1672
1692
  let configFile;
@@ -1689,9 +1709,6 @@ function parseArgs(argv) {
1689
1709
  case "--verbose":
1690
1710
  verbose = true;
1691
1711
  break;
1692
- case "--no-input-guard":
1693
- validateInput = false;
1694
- break;
1695
1712
  case "--rate-limit":
1696
1713
  rateLimitPerTool = parseInt(flags[++i], 10);
1697
1714
  break;
@@ -1740,17 +1757,11 @@ function parseArgs(argv) {
1740
1757
  if (!fileConfig.upstream) {
1741
1758
  throw new Error('Config file must include "upstream" with at least "command" or "url"');
1742
1759
  }
1743
- if (fileConfig.upstream.url && detectSSRF(fileConfig.upstream.url)) {
1744
- throw new Error(
1745
- `Upstream URL blocked: "${fileConfig.upstream.url}" points to an internal or private network address.`
1746
- );
1747
- }
1748
1760
  return {
1749
1761
  upstream: fileConfig.upstream,
1750
1762
  policy: loadPolicy(fileConfig.policy ?? policySource),
1751
1763
  name: fileConfig.name ?? name,
1752
1764
  verbose: fileConfig.verbose ?? verbose,
1753
- validateInput: fileConfig.validateInput ?? validateInput,
1754
1765
  rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
1755
1766
  globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
1756
1767
  apiKey: apiKey ?? fileConfig.apiKey,
@@ -1759,12 +1770,6 @@ function parseArgs(argv) {
1759
1770
  };
1760
1771
  }
1761
1772
  if (upstreamUrl) {
1762
- if (detectSSRF(upstreamUrl)) {
1763
- throw new Error(
1764
- `Upstream URL blocked: "${upstreamUrl}" points to an internal or private network address.
1765
- SSRF protection prevents connecting to localhost, private IPs, or cloud metadata endpoints.`
1766
- );
1767
- }
1768
1773
  const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
1769
1774
  return {
1770
1775
  upstream: {
@@ -1776,7 +1781,6 @@ SSRF protection prevents connecting to localhost, private IPs, or cloud metadata
1776
1781
  policy: loadPolicy(policySource),
1777
1782
  name,
1778
1783
  verbose,
1779
- validateInput,
1780
1784
  rateLimitPerTool,
1781
1785
  globalRateLimit,
1782
1786
  apiKey,
@@ -1800,7 +1804,6 @@ SSRF protection prevents connecting to localhost, private IPs, or cloud metadata
1800
1804
  policy: loadPolicy(policySource),
1801
1805
  name,
1802
1806
  verbose,
1803
- validateInput,
1804
1807
  rateLimitPerTool,
1805
1808
  globalRateLimit,
1806
1809
  apiKey,
@@ -1829,7 +1832,7 @@ import {
1829
1832
  import { createServer as createHttpServer } from "http";
1830
1833
 
1831
1834
  // ../sdk-ts/dist/index.js
1832
- import { z as z2 } from "zod";
1835
+ import { z } from "zod";
1833
1836
  import { createHash, randomUUID, createHmac } from "crypto";
1834
1837
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1835
1838
  var SolonGateError = class extends Error {
@@ -1868,16 +1871,6 @@ var PolicyDeniedError = class extends SolonGateError {
1868
1871
  this.name = "PolicyDeniedError";
1869
1872
  }
1870
1873
  };
1871
- var SchemaValidationError = class extends SolonGateError {
1872
- constructor(toolName, validationErrors) {
1873
- super(
1874
- `Schema validation failed for tool "${toolName}": ${validationErrors.join("; ")}`,
1875
- "SCHEMA_VALIDATION_FAILED",
1876
- { toolName, validationErrors }
1877
- );
1878
- this.name = "SchemaValidationError";
1879
- }
1880
- };
1881
1874
  var RateLimitError = class extends SolonGateError {
1882
1875
  constructor(toolName, limitPerMinute) {
1883
1876
  super(
@@ -1893,49 +1886,53 @@ var TrustLevel = {
1893
1886
  VERIFIED: "VERIFIED",
1894
1887
  TRUSTED: "TRUSTED"
1895
1888
  };
1896
- var Permission2 = {
1889
+ var Permission = {
1897
1890
  READ: "READ",
1898
1891
  WRITE: "WRITE",
1899
1892
  EXECUTE: "EXECUTE"
1900
1893
  };
1901
- z2.enum(["READ", "WRITE", "EXECUTE"]);
1894
+ z.enum(["READ", "WRITE", "EXECUTE"]);
1902
1895
  Object.freeze(
1903
1896
  /* @__PURE__ */ new Set()
1904
1897
  );
1905
1898
  Object.freeze(
1906
- /* @__PURE__ */ new Set([Permission2.READ])
1899
+ /* @__PURE__ */ new Set([Permission.READ])
1907
1900
  );
1908
1901
  var PolicyEffect = {
1909
1902
  ALLOW: "ALLOW",
1910
1903
  DENY: "DENY"
1911
1904
  };
1912
- var PolicyRuleSchema2 = z2.object({
1913
- id: z2.string().min(1).max(256),
1914
- description: z2.string().max(1024),
1915
- effect: z2.enum(["ALLOW", "DENY"]),
1916
- priority: z2.number().int().min(0).max(1e4).default(1e3),
1917
- toolPattern: z2.string().min(1).max(512),
1918
- permission: z2.enum(["READ", "WRITE", "EXECUTE"]),
1919
- minimumTrustLevel: z2.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
1920
- argumentConstraints: z2.record(z2.unknown()).optional(),
1921
- pathConstraints: z2.object({
1922
- allowed: z2.array(z2.string()).optional(),
1923
- denied: z2.array(z2.string()).optional(),
1924
- rootDirectory: z2.string().optional(),
1925
- allowSymlinks: z2.boolean().optional()
1905
+ var PolicyRuleSchema = z.object({
1906
+ id: z.string().min(1).max(256),
1907
+ description: z.string().max(1024),
1908
+ effect: z.enum(["ALLOW", "DENY"]),
1909
+ priority: z.number().int().min(0).max(1e4).default(1e3),
1910
+ toolPattern: z.string().min(1).max(512),
1911
+ permission: z.enum(["READ", "WRITE", "EXECUTE"]),
1912
+ minimumTrustLevel: z.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
1913
+ argumentConstraints: z.record(z.unknown()).optional(),
1914
+ pathConstraints: z.object({
1915
+ allowed: z.array(z.string()).optional(),
1916
+ denied: z.array(z.string()).optional(),
1917
+ rootDirectory: z.string().optional(),
1918
+ allowSymlinks: z.boolean().optional()
1919
+ }).optional(),
1920
+ commandConstraints: z.object({
1921
+ allowed: z.array(z.string()).optional(),
1922
+ denied: z.array(z.string()).optional()
1926
1923
  }).optional(),
1927
- enabled: z2.boolean().default(true),
1928
- createdAt: z2.string().datetime(),
1929
- updatedAt: z2.string().datetime()
1924
+ enabled: z.boolean().default(true),
1925
+ createdAt: z.string().datetime(),
1926
+ updatedAt: z.string().datetime()
1930
1927
  });
1931
- var PolicySetSchema2 = z2.object({
1932
- id: z2.string().min(1).max(256),
1933
- name: z2.string().min(1).max(256),
1934
- description: z2.string().max(2048),
1935
- version: z2.number().int().min(0),
1936
- rules: z2.array(PolicyRuleSchema2),
1937
- createdAt: z2.string().datetime(),
1938
- updatedAt: z2.string().datetime()
1928
+ var PolicySetSchema = z.object({
1929
+ id: z.string().min(1).max(256),
1930
+ name: z.string().min(1).max(256),
1931
+ description: z.string().max(2048),
1932
+ version: z.number().int().min(0),
1933
+ rules: z.array(PolicyRuleSchema),
1934
+ createdAt: z.string().datetime(),
1935
+ updatedAt: z.string().datetime()
1939
1936
  });
1940
1937
  function createSecurityContext(params) {
1941
1938
  return {
@@ -1974,7 +1971,7 @@ function createDeniedToolResult(reason) {
1974
1971
  isError: true
1975
1972
  };
1976
1973
  }
1977
- var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
1974
+ var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
1978
1975
  pathTraversal: true,
1979
1976
  shellInjection: true,
1980
1977
  wildcardAbuse: true,
@@ -1983,296 +1980,6 @@ var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
1983
1980
  ssrf: true,
1984
1981
  sqlInjection: true
1985
1982
  });
1986
- var PATH_TRAVERSAL_PATTERNS = [
1987
- /\.\.\//,
1988
- // ../
1989
- /\.\.\\/,
1990
- // ..\
1991
- /%2e%2e/i,
1992
- // URL-encoded ..
1993
- /%2e\./i,
1994
- // partial URL-encoded
1995
- /\.%2e/i,
1996
- // partial URL-encoded
1997
- /%252e%252e/i,
1998
- // double URL-encoded
1999
- /\.\.\0/
2000
- // null byte variant
2001
- ];
2002
- var SENSITIVE_PATHS = [
2003
- /\/etc\/passwd/i,
2004
- /\/etc\/shadow/i,
2005
- /\/proc\//i,
2006
- /\/dev\//i,
2007
- /c:\\windows\\system32/i,
2008
- /c:\\windows\\syswow64/i,
2009
- /\/root\//i,
2010
- /~\//,
2011
- /\.env(\.|$)/i,
2012
- // .env, .env.local, .env.production
2013
- /\.aws\/credentials/i,
2014
- // AWS credentials
2015
- /\.ssh\/id_/i,
2016
- // SSH keys
2017
- /\.kube\/config/i,
2018
- // Kubernetes config
2019
- /wp-config\.php/i,
2020
- // WordPress config
2021
- /\.git\/config/i,
2022
- // Git config
2023
- /\.npmrc/i,
2024
- // npm credentials
2025
- /\.pypirc/i
2026
- // PyPI credentials
2027
- ];
2028
- function detectPathTraversal(value) {
2029
- for (const pattern of PATH_TRAVERSAL_PATTERNS) {
2030
- if (pattern.test(value)) return true;
2031
- }
2032
- for (const pattern of SENSITIVE_PATHS) {
2033
- if (pattern.test(value)) return true;
2034
- }
2035
- return false;
2036
- }
2037
- var SHELL_INJECTION_PATTERNS = [
2038
- /[;|&`]/,
2039
- // Command separators and backtick execution
2040
- /\$\(/,
2041
- // Command substitution $(...)
2042
- /\$\{/,
2043
- // Variable expansion ${...}
2044
- />\s*/,
2045
- // Output redirect
2046
- /<\s*/,
2047
- // Input redirect
2048
- /&&/,
2049
- // AND chaining
2050
- /\|\|/,
2051
- // OR chaining
2052
- /\beval\b/i,
2053
- // eval command
2054
- /\bexec\b/i,
2055
- // exec command
2056
- /\bsystem\b/i,
2057
- // system call
2058
- /%0a/i,
2059
- // URL-encoded newline
2060
- /%0d/i,
2061
- // URL-encoded carriage return
2062
- /%09/i,
2063
- // URL-encoded tab
2064
- /\r\n/,
2065
- // CRLF injection
2066
- /\n/
2067
- // Newline (command separator on Unix)
2068
- ];
2069
- function detectShellInjection(value) {
2070
- for (const pattern of SHELL_INJECTION_PATTERNS) {
2071
- if (pattern.test(value)) return true;
2072
- }
2073
- return false;
2074
- }
2075
- var MAX_WILDCARDS_PER_VALUE = 3;
2076
- function detectWildcardAbuse(value) {
2077
- if (value.includes("**")) return true;
2078
- const wildcardCount = (value.match(/\*/g) || []).length;
2079
- if (wildcardCount > MAX_WILDCARDS_PER_VALUE) return true;
2080
- return false;
2081
- }
2082
- var SSRF_PATTERNS2 = [
2083
- /^https?:\/\/localhost\b/i,
2084
- /^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
2085
- /^https?:\/\/0\.0\.0\.0/,
2086
- /^https?:\/\/\[::1\]/,
2087
- // IPv6 loopback
2088
- /^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
2089
- // 10.x.x.x
2090
- /^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
2091
- // 172.16-31.x.x
2092
- /^https?:\/\/192\.168\./,
2093
- // 192.168.x.x
2094
- /^https?:\/\/169\.254\./,
2095
- // Link-local / AWS metadata
2096
- /metadata\.google\.internal/i,
2097
- // GCP metadata
2098
- /^https?:\/\/metadata\b/i,
2099
- // Generic metadata endpoint
2100
- // IPv6 bypass patterns
2101
- /^https?:\/\/\[fe80:/i,
2102
- // IPv6 link-local
2103
- /^https?:\/\/\[fc00:/i,
2104
- // IPv6 unique local
2105
- /^https?:\/\/\[fd[0-9a-f]{2}:/i,
2106
- // IPv6 unique local (fd00::/8)
2107
- /^https?:\/\/\[::ffff:127\./i,
2108
- // IPv4-mapped IPv6 loopback
2109
- /^https?:\/\/\[::ffff:10\./i,
2110
- // IPv4-mapped IPv6 private
2111
- /^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
2112
- // IPv4-mapped IPv6 private
2113
- /^https?:\/\/\[::ffff:192\.168\./i,
2114
- // IPv4-mapped IPv6 private
2115
- /^https?:\/\/\[::ffff:169\.254\./i,
2116
- // IPv4-mapped IPv6 link-local
2117
- // Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
2118
- /^https?:\/\/0x[0-9a-f]+\b/i,
2119
- // Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
2120
- /^https?:\/\/0[0-7]{1,3}\./
2121
- ];
2122
- function detectDecimalIP2(value) {
2123
- const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
2124
- if (!match || !match[1]) return false;
2125
- const decimal = parseInt(match[1], 10);
2126
- if (isNaN(decimal) || decimal > 4294967295) return false;
2127
- return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
2128
- decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
2129
- decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
2130
- decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
2131
- decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
2132
- decimal === 0;
2133
- }
2134
- function detectSSRF2(value) {
2135
- for (const pattern of SSRF_PATTERNS2) {
2136
- if (pattern.test(value)) return true;
2137
- }
2138
- if (detectDecimalIP2(value)) return true;
2139
- return false;
2140
- }
2141
- var SQL_INJECTION_PATTERNS = [
2142
- /'\s{0,20}(OR|AND)\s{0,20}'.{0,200}'/i,
2143
- // ' OR '1'='1 — bounded to prevent ReDoS
2144
- /'\s{0,10};\s{0,10}(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|EXEC)/i,
2145
- // '; DROP TABLE
2146
- /UNION\s+(ALL\s+)?SELECT/i,
2147
- // UNION SELECT
2148
- /--\s*$/m,
2149
- // SQL comment at end of line
2150
- /\/\*.{0,500}?\*\//,
2151
- // SQL block comment — bounded + non-greedy
2152
- /\bSLEEP\s*\(/i,
2153
- // Time-based injection
2154
- /\bBENCHMARK\s*\(/i,
2155
- // MySQL benchmark
2156
- /\bWAITFOR\s+DELAY/i,
2157
- // MSSQL delay
2158
- /\b(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)\b/i
2159
- // File operations
2160
- ];
2161
- function detectSQLInjection(value) {
2162
- for (const pattern of SQL_INJECTION_PATTERNS) {
2163
- if (pattern.test(value)) return true;
2164
- }
2165
- return false;
2166
- }
2167
- function checkLengthLimits(value, maxLength = 4096) {
2168
- return value.length <= maxLength;
2169
- }
2170
- var ENTROPY_THRESHOLD = 4.5;
2171
- var MIN_LENGTH_FOR_ENTROPY_CHECK = 32;
2172
- function checkEntropyLimits(value) {
2173
- if (value.length < MIN_LENGTH_FOR_ENTROPY_CHECK) return true;
2174
- const entropy = calculateShannonEntropy(value);
2175
- return entropy <= ENTROPY_THRESHOLD;
2176
- }
2177
- function calculateShannonEntropy(str) {
2178
- const freq = /* @__PURE__ */ new Map();
2179
- for (const char of str) {
2180
- freq.set(char, (freq.get(char) ?? 0) + 1);
2181
- }
2182
- let entropy = 0;
2183
- const len = str.length;
2184
- for (const count of freq.values()) {
2185
- const p = count / len;
2186
- if (p > 0) {
2187
- entropy -= p * Math.log2(p);
2188
- }
2189
- }
2190
- return entropy;
2191
- }
2192
- function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG2) {
2193
- const threats = [];
2194
- if (typeof value !== "string") {
2195
- if (typeof value === "object" && value !== null) {
2196
- return sanitizeObject(field, value, config);
2197
- }
2198
- return { safe: true, threats: [] };
2199
- }
2200
- if (config.pathTraversal && detectPathTraversal(value)) {
2201
- threats.push({
2202
- type: "PATH_TRAVERSAL",
2203
- field,
2204
- value: truncate(value, 100),
2205
- description: "Path traversal pattern detected"
2206
- });
2207
- }
2208
- if (config.shellInjection && detectShellInjection(value)) {
2209
- threats.push({
2210
- type: "SHELL_INJECTION",
2211
- field,
2212
- value: truncate(value, 100),
2213
- description: "Shell injection pattern detected"
2214
- });
2215
- }
2216
- if (config.wildcardAbuse && detectWildcardAbuse(value)) {
2217
- threats.push({
2218
- type: "WILDCARD_ABUSE",
2219
- field,
2220
- value: truncate(value, 100),
2221
- description: "Wildcard abuse pattern detected"
2222
- });
2223
- }
2224
- if (!checkLengthLimits(value, config.lengthLimit)) {
2225
- threats.push({
2226
- type: "LENGTH_EXCEEDED",
2227
- field,
2228
- value: `[${value.length} chars]`,
2229
- description: `Value exceeds maximum length of ${config.lengthLimit}`
2230
- });
2231
- }
2232
- if (config.entropyLimit && !checkEntropyLimits(value)) {
2233
- threats.push({
2234
- type: "HIGH_ENTROPY",
2235
- field,
2236
- value: truncate(value, 100),
2237
- description: "High entropy string detected - possible encoded payload"
2238
- });
2239
- }
2240
- if (config.ssrf && detectSSRF2(value)) {
2241
- threats.push({
2242
- type: "SSRF",
2243
- field,
2244
- value: truncate(value, 100),
2245
- description: "Server-side request forgery pattern detected \u2014 internal/metadata URL blocked"
2246
- });
2247
- }
2248
- if (config.sqlInjection && detectSQLInjection(value)) {
2249
- threats.push({
2250
- type: "SQL_INJECTION",
2251
- field,
2252
- value: truncate(value, 100),
2253
- description: "SQL injection pattern detected"
2254
- });
2255
- }
2256
- return { safe: threats.length === 0, threats };
2257
- }
2258
- function sanitizeObject(basePath, obj, config) {
2259
- const threats = [];
2260
- if (Array.isArray(obj)) {
2261
- for (let i = 0; i < obj.length; i++) {
2262
- const result = sanitizeInput(`${basePath}[${i}]`, obj[i], config);
2263
- threats.push(...result.threats);
2264
- }
2265
- } else {
2266
- for (const [key, val] of Object.entries(obj)) {
2267
- const result = sanitizeInput(`${basePath}.${key}`, val, config);
2268
- threats.push(...result.threats);
2269
- }
2270
- }
2271
- return { safe: threats.length === 0, threats };
2272
- }
2273
- function truncate(str, maxLen) {
2274
- return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
2275
- }
2276
1983
  var DEFAULT_TOKEN_TTL_SECONDS = 30;
2277
1984
  var TOKEN_ALGORITHM = "HS256";
2278
1985
  var MIN_SECRET_LENGTH = 32;
@@ -2375,6 +2082,69 @@ function extractPathArguments(args) {
2375
2082
  }
2376
2083
  return paths;
2377
2084
  }
2085
+ var COMMAND_FIELDS = /* @__PURE__ */ new Set([
2086
+ "command",
2087
+ "cmd",
2088
+ "query",
2089
+ "code",
2090
+ "script",
2091
+ "shell",
2092
+ "exec",
2093
+ "sql",
2094
+ "expression"
2095
+ ]);
2096
+ function extractCommandArguments(args) {
2097
+ const commands = [];
2098
+ for (const [key, value] of Object.entries(args)) {
2099
+ if (typeof value !== "string") continue;
2100
+ if (COMMAND_FIELDS.has(key.toLowerCase())) {
2101
+ commands.push(value);
2102
+ }
2103
+ }
2104
+ return commands;
2105
+ }
2106
+ function matchCommandPattern(command, pattern) {
2107
+ if (pattern === "*") return true;
2108
+ const normalizedCommand = command.trim().toLowerCase();
2109
+ const normalizedPattern = pattern.trim().toLowerCase();
2110
+ if (normalizedPattern === normalizedCommand) return true;
2111
+ const startsWithStar = normalizedPattern.startsWith("*");
2112
+ const endsWithStar = normalizedPattern.endsWith("*");
2113
+ if (startsWithStar && endsWithStar) {
2114
+ const infix = normalizedPattern.slice(1, -1);
2115
+ return infix.length > 0 && normalizedCommand.includes(infix);
2116
+ }
2117
+ if (endsWithStar) {
2118
+ const prefix = normalizedPattern.slice(0, -1);
2119
+ return normalizedCommand.startsWith(prefix);
2120
+ }
2121
+ if (startsWithStar) {
2122
+ const suffix = normalizedPattern.slice(1);
2123
+ return normalizedCommand.endsWith(suffix);
2124
+ }
2125
+ const commandName = normalizedCommand.split(/\s+/)[0] ?? "";
2126
+ return commandName === normalizedPattern;
2127
+ }
2128
+ function isCommandAllowed(command, constraints) {
2129
+ if (constraints.denied && constraints.denied.length > 0) {
2130
+ for (const pattern of constraints.denied) {
2131
+ if (matchCommandPattern(command, pattern)) {
2132
+ return false;
2133
+ }
2134
+ }
2135
+ }
2136
+ if (constraints.allowed && constraints.allowed.length > 0) {
2137
+ let matchesAllowed = false;
2138
+ for (const pattern of constraints.allowed) {
2139
+ if (matchCommandPattern(command, pattern)) {
2140
+ matchesAllowed = true;
2141
+ break;
2142
+ }
2143
+ }
2144
+ if (!matchesAllowed) return false;
2145
+ }
2146
+ return true;
2147
+ }
2378
2148
  function ruleMatchesRequest(rule, request) {
2379
2149
  if (!rule.enabled) return false;
2380
2150
  if (rule.permission !== request.requiredPermission) return false;
@@ -2388,8 +2158,19 @@ function ruleMatchesRequest(rule, request) {
2388
2158
  }
2389
2159
  }
2390
2160
  if (rule.pathConstraints) {
2391
- if (!pathConstraintsMatch(rule.pathConstraints, request.arguments)) {
2392
- return false;
2161
+ const satisfied = pathConstraintsMatch(rule.pathConstraints, request.arguments);
2162
+ if (rule.effect === "DENY") {
2163
+ if (satisfied) return false;
2164
+ } else {
2165
+ if (!satisfied) return false;
2166
+ }
2167
+ }
2168
+ if (rule.commandConstraints) {
2169
+ const satisfied = commandConstraintsMatch(rule.commandConstraints, request.arguments);
2170
+ if (rule.effect === "DENY") {
2171
+ if (satisfied) return false;
2172
+ } else {
2173
+ if (!satisfied) return false;
2393
2174
  }
2394
2175
  }
2395
2176
  return true;
@@ -2477,6 +2258,11 @@ function pathConstraintsMatch(constraints, args) {
2477
2258
  if (paths.length === 0) return true;
2478
2259
  return paths.every((path) => isPathAllowed(path, constraints));
2479
2260
  }
2261
+ function commandConstraintsMatch(constraints, args) {
2262
+ const commands = extractCommandArguments(args);
2263
+ if (commands.length === 0) return true;
2264
+ return commands.every((cmd) => isCommandAllowed(cmd, constraints));
2265
+ }
2480
2266
  function evaluatePolicy(policySet, request) {
2481
2267
  const startTime = performance.now();
2482
2268
  const sortedRules = [...policySet.rules].sort(
@@ -2514,7 +2300,7 @@ function evaluatePolicy(policySet, request) {
2514
2300
  function validatePolicyRule(input) {
2515
2301
  const errors = [];
2516
2302
  const warnings = [];
2517
- const result = PolicyRuleSchema2.safeParse(input);
2303
+ const result = PolicyRuleSchema.safeParse(input);
2518
2304
  if (!result.success) {
2519
2305
  return {
2520
2306
  valid: false,
@@ -2539,7 +2325,7 @@ function validatePolicyRule(input) {
2539
2325
  function validatePolicySet(input) {
2540
2326
  const errors = [];
2541
2327
  const warnings = [];
2542
- const result = PolicySetSchema2.safeParse(input);
2328
+ const result = PolicySetSchema.safeParse(input);
2543
2329
  if (!result.success) {
2544
2330
  return {
2545
2331
  valid: false,
@@ -2633,7 +2419,7 @@ function createDefaultDenyPolicySet() {
2633
2419
  effect: PolicyEffect.DENY,
2634
2420
  priority: 1e4,
2635
2421
  toolPattern: "*",
2636
- permission: Permission2.EXECUTE,
2422
+ permission: Permission.EXECUTE,
2637
2423
  minimumTrustLevel: TrustLevel.UNTRUSTED,
2638
2424
  enabled: true,
2639
2425
  createdAt: now,
@@ -2645,7 +2431,7 @@ function createDefaultDenyPolicySet() {
2645
2431
  effect: PolicyEffect.DENY,
2646
2432
  priority: 1e4,
2647
2433
  toolPattern: "*",
2648
- permission: Permission2.WRITE,
2434
+ permission: Permission.WRITE,
2649
2435
  minimumTrustLevel: TrustLevel.UNTRUSTED,
2650
2436
  enabled: true,
2651
2437
  createdAt: now,
@@ -2657,7 +2443,7 @@ function createDefaultDenyPolicySet() {
2657
2443
  effect: PolicyEffect.DENY,
2658
2444
  priority: 1e4,
2659
2445
  toolPattern: "*",
2660
- permission: Permission2.READ,
2446
+ permission: Permission.READ,
2661
2447
  minimumTrustLevel: TrustLevel.UNTRUSTED,
2662
2448
  enabled: true,
2663
2449
  createdAt: now,
@@ -2836,7 +2622,6 @@ var DEFAULT_CONFIG = Object.freeze({
2836
2622
  globalRateLimitPerMinute: 600,
2837
2623
  rateLimitPerTool: 60,
2838
2624
  tokenTtlSeconds: 30,
2839
- inputGuardConfig: DEFAULT_INPUT_GUARD_CONFIG2,
2840
2625
  enableVersionedPolicies: true
2841
2626
  });
2842
2627
  function resolveConfig(userConfig) {
@@ -2869,7 +2654,7 @@ async function interceptToolCall(params, upstreamCall, options) {
2869
2654
  toolName: params.name,
2870
2655
  serverName: "default",
2871
2656
  arguments: params.arguments ?? {},
2872
- requiredPermission: Permission2.EXECUTE,
2657
+ requiredPermission: Permission.EXECUTE,
2873
2658
  timestamp
2874
2659
  };
2875
2660
  if (options.rateLimiter) {
@@ -2907,24 +2692,6 @@ async function interceptToolCall(params, upstreamCall, options) {
2907
2692
  }
2908
2693
  }
2909
2694
  }
2910
- if (options.validateSchemas && params.arguments) {
2911
- const guardConfig = options.inputGuardConfig ?? DEFAULT_INPUT_GUARD_CONFIG2;
2912
- const sanitization = sanitizeInput("arguments", params.arguments, guardConfig);
2913
- if (!sanitization.safe) {
2914
- const threatDescriptions = sanitization.threats.map(
2915
- (t) => `${t.type}: ${t.description} (field: ${t.field})`
2916
- );
2917
- const result = {
2918
- status: "ERROR",
2919
- request,
2920
- error: new SchemaValidationError(params.name, threatDescriptions),
2921
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2922
- };
2923
- options.onDecision?.(result);
2924
- const reason = options.verboseErrors ? `Input validation failed: ${sanitization.threats.length} threat(s) detected` : "Input validation failed.";
2925
- return createDeniedToolResult(reason);
2926
- }
2927
- }
2928
2695
  const decision = options.policyEngine.evaluate(request);
2929
2696
  if (decision.effect === "DENY") {
2930
2697
  const result = {
@@ -2941,7 +2708,7 @@ async function interceptToolCall(params, upstreamCall, options) {
2941
2708
  if (options.tokenIssuer) {
2942
2709
  capabilityToken = options.tokenIssuer.issue(
2943
2710
  requestId,
2944
- [Permission2.EXECUTE],
2711
+ [Permission.EXECUTE],
2945
2712
  [params.name]
2946
2713
  );
2947
2714
  }
@@ -3518,7 +3285,6 @@ var SolonGate = class {
3518
3285
  tokenIssuer: this.tokenIssuer ?? void 0,
3519
3286
  serverVerifier: this.serverVerifier ?? void 0,
3520
3287
  rateLimiter: this.rateLimiter,
3521
- inputGuardConfig: this.config.inputGuardConfig,
3522
3288
  rateLimitPerTool: this.config.rateLimitPerTool,
3523
3289
  globalRateLimitPerMinute: this.config.globalRateLimitPerMinute
3524
3290
  });
@@ -3586,7 +3352,7 @@ var SolonGateProxy = class {
3586
3352
  apiKey: "sg_test_proxy_internal_00000000",
3587
3353
  policySet: config.policy,
3588
3354
  config: {
3589
- validateSchemas: config.validateInput ?? true,
3355
+ validateSchemas: true,
3590
3356
  verboseErrors: config.verbose ?? false,
3591
3357
  rateLimitPerTool: config.rateLimitPerTool,
3592
3358
  globalRateLimitPerMinute: config.globalRateLimit
package/dist/init.js CHANGED
@@ -175,6 +175,11 @@ const SSRF_IN_CMD = [
175
175
  /https?:\\/\\/10\\./, /https?:\\/\\/172\\.(1[6-9]|2\\d|3[01])\\./,
176
176
  /https?:\\/\\/192\\.168\\./, /https?:\\/\\/169\\.254\\./,
177
177
  /metadata\\.google\\.internal/i,
178
+ /\\b127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/, /\\b0\\.0\\.0\\.0\\b/,
179
+ /\\b10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/,
180
+ /\\b172\\.(1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}\\b/,
181
+ /\\b192\\.168\\.\\d{1,3}\\.\\d{1,3}\\b/,
182
+ /\\b169\\.254\\.\\d{1,3}\\.\\d{1,3}\\b/,
178
183
  ];
179
184
  const SQL_INJECTION = [
180
185
  /'\\s{0,20}(OR|AND)\\s{0,20}'.{0,200}'/i,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.2.8",
4
- "description": "MCP security proxy \u00e2\u20ac\u201d protect any MCP server with policies, input validation, rate limiting, and audit logging. Zero code changes required.",
3
+ "version": "0.3.0",
4
+ "description": "MCP security proxy protect any MCP server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "solongate-proxy": "./dist/index.js",