@solongate/proxy 0.2.9 → 0.3.1

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 (2) hide show
  1. package/dist/index.js +307 -513
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1338,118 +1338,6 @@ var init_create = __esm({
1338
1338
  }
1339
1339
  });
1340
1340
 
1341
- // ../core/dist/index.js
1342
- import { z } from "zod";
1343
- var Permission = {
1344
- READ: "READ",
1345
- WRITE: "WRITE",
1346
- EXECUTE: "EXECUTE"
1347
- };
1348
- var PermissionSchema = z.enum(["READ", "WRITE", "EXECUTE"]);
1349
- var NO_PERMISSIONS = Object.freeze(
1350
- /* @__PURE__ */ new Set()
1351
- );
1352
- var READ_ONLY = Object.freeze(
1353
- /* @__PURE__ */ new Set([Permission.READ])
1354
- );
1355
- var PolicyRuleSchema = z.object({
1356
- id: z.string().min(1).max(256),
1357
- description: z.string().max(1024),
1358
- effect: z.enum(["ALLOW", "DENY"]),
1359
- priority: z.number().int().min(0).max(1e4).default(1e3),
1360
- toolPattern: z.string().min(1).max(512),
1361
- permission: z.enum(["READ", "WRITE", "EXECUTE"]),
1362
- minimumTrustLevel: z.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
1363
- argumentConstraints: z.record(z.unknown()).optional(),
1364
- pathConstraints: z.object({
1365
- allowed: z.array(z.string()).optional(),
1366
- denied: z.array(z.string()).optional(),
1367
- rootDirectory: z.string().optional(),
1368
- allowSymlinks: z.boolean().optional()
1369
- }).optional(),
1370
- enabled: z.boolean().default(true),
1371
- createdAt: z.string().datetime(),
1372
- updatedAt: z.string().datetime()
1373
- });
1374
- var PolicySetSchema = z.object({
1375
- id: z.string().min(1).max(256),
1376
- name: z.string().min(1).max(256),
1377
- description: z.string().max(2048),
1378
- version: z.number().int().min(0),
1379
- rules: z.array(PolicyRuleSchema),
1380
- createdAt: z.string().datetime(),
1381
- updatedAt: z.string().datetime()
1382
- });
1383
- var SECURITY_CONTEXT_TIMEOUT_MS = 5 * 60 * 1e3;
1384
- var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
1385
- pathTraversal: true,
1386
- shellInjection: true,
1387
- wildcardAbuse: true,
1388
- lengthLimit: 4096,
1389
- entropyLimit: true,
1390
- ssrf: true,
1391
- sqlInjection: true
1392
- });
1393
- var SSRF_PATTERNS = [
1394
- /^https?:\/\/localhost\b/i,
1395
- /^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
1396
- /^https?:\/\/0\.0\.0\.0/,
1397
- /^https?:\/\/\[::1\]/,
1398
- // IPv6 loopback
1399
- /^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
1400
- // 10.x.x.x
1401
- /^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
1402
- // 172.16-31.x.x
1403
- /^https?:\/\/192\.168\./,
1404
- // 192.168.x.x
1405
- /^https?:\/\/169\.254\./,
1406
- // Link-local / AWS metadata
1407
- /metadata\.google\.internal/i,
1408
- // GCP metadata
1409
- /^https?:\/\/metadata\b/i,
1410
- // Generic metadata endpoint
1411
- // IPv6 bypass patterns
1412
- /^https?:\/\/\[fe80:/i,
1413
- // IPv6 link-local
1414
- /^https?:\/\/\[fc00:/i,
1415
- // IPv6 unique local
1416
- /^https?:\/\/\[fd[0-9a-f]{2}:/i,
1417
- // IPv6 unique local (fd00::/8)
1418
- /^https?:\/\/\[::ffff:127\./i,
1419
- // IPv4-mapped IPv6 loopback
1420
- /^https?:\/\/\[::ffff:10\./i,
1421
- // IPv4-mapped IPv6 private
1422
- /^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
1423
- // IPv4-mapped IPv6 private
1424
- /^https?:\/\/\[::ffff:192\.168\./i,
1425
- // IPv4-mapped IPv6 private
1426
- /^https?:\/\/\[::ffff:169\.254\./i,
1427
- // IPv4-mapped IPv6 link-local
1428
- // Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
1429
- /^https?:\/\/0x[0-9a-f]+\b/i,
1430
- // Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
1431
- /^https?:\/\/0[0-7]{1,3}\./
1432
- ];
1433
- function detectDecimalIP(value) {
1434
- const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
1435
- if (!match || !match[1]) return false;
1436
- const decimal = parseInt(match[1], 10);
1437
- if (isNaN(decimal) || decimal > 4294967295) return false;
1438
- return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
1439
- decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
1440
- decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
1441
- decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
1442
- decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
1443
- decimal === 0;
1444
- }
1445
- function detectSSRF(value) {
1446
- for (const pattern of SSRF_PATTERNS) {
1447
- if (pattern.test(value)) return true;
1448
- }
1449
- if (detectDecimalIP(value)) return true;
1450
- return false;
1451
- }
1452
-
1453
1341
  // src/config.ts
1454
1342
  import { readFileSync, existsSync } from "fs";
1455
1343
  import { resolve } from "path";
@@ -1497,48 +1385,72 @@ var PRESETS = {
1497
1385
  restricted: {
1498
1386
  id: "restricted",
1499
1387
  name: "Restricted",
1500
- 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",
1501
1389
  version: 1,
1502
1390
  rules: [
1503
1391
  {
1504
- id: "deny-shell",
1505
- description: "Block shell execution",
1506
- effect: "DENY",
1507
- priority: 100,
1508
- toolPattern: "*shell*",
1509
- permission: "EXECUTE",
1510
- minimumTrustLevel: "UNTRUSTED",
1511
- enabled: true,
1512
- createdAt: "",
1513
- updatedAt: ""
1514
- },
1515
- {
1516
- id: "deny-exec",
1517
- description: "Block command execution",
1392
+ id: "deny-dangerous-commands",
1393
+ description: "Block dangerous shell commands",
1518
1394
  effect: "DENY",
1519
- priority: 101,
1520
- toolPattern: "*exec*",
1395
+ priority: 50,
1396
+ toolPattern: "*",
1521
1397
  permission: "EXECUTE",
1522
1398
  minimumTrustLevel: "UNTRUSTED",
1523
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
+ },
1524
1420
  createdAt: "",
1525
1421
  updatedAt: ""
1526
1422
  },
1527
1423
  {
1528
- id: "deny-eval",
1529
- description: "Block code eval",
1424
+ id: "deny-sensitive-paths",
1425
+ description: "Block access to sensitive files",
1530
1426
  effect: "DENY",
1531
- priority: 102,
1532
- toolPattern: "*eval*",
1427
+ priority: 51,
1428
+ toolPattern: "*",
1533
1429
  permission: "EXECUTE",
1534
1430
  minimumTrustLevel: "UNTRUSTED",
1535
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
+ },
1536
1448
  createdAt: "",
1537
1449
  updatedAt: ""
1538
1450
  },
1539
1451
  {
1540
1452
  id: "allow-rest",
1541
- description: "Allow all other tools",
1453
+ description: "Allow all other tool calls",
1542
1454
  effect: "ALLOW",
1543
1455
  priority: 1e3,
1544
1456
  toolPattern: "*",
@@ -1555,7 +1467,7 @@ var PRESETS = {
1555
1467
  "read-only": {
1556
1468
  id: "read-only",
1557
1469
  name: "Read Only",
1558
- description: "Only allows read operations, blocks writes and execution",
1470
+ description: "Only allows read/list/get/search/query tools",
1559
1471
  version: 1,
1560
1472
  rules: [
1561
1473
  {
@@ -1622,6 +1534,110 @@ var PRESETS = {
1622
1534
  createdAt: "",
1623
1535
  updatedAt: ""
1624
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
+ },
1625
1641
  permissive: {
1626
1642
  id: "permissive",
1627
1643
  name: "Permissive",
@@ -1671,7 +1687,6 @@ function parseArgs(argv) {
1671
1687
  let policySource = "restricted";
1672
1688
  let name = "solongate-proxy";
1673
1689
  let verbose = false;
1674
- let validateInput = true;
1675
1690
  let rateLimitPerTool;
1676
1691
  let globalRateLimit;
1677
1692
  let configFile;
@@ -1694,9 +1709,6 @@ function parseArgs(argv) {
1694
1709
  case "--verbose":
1695
1710
  verbose = true;
1696
1711
  break;
1697
- case "--no-input-guard":
1698
- validateInput = false;
1699
- break;
1700
1712
  case "--rate-limit":
1701
1713
  rateLimitPerTool = parseInt(flags[++i], 10);
1702
1714
  break;
@@ -1745,17 +1757,11 @@ function parseArgs(argv) {
1745
1757
  if (!fileConfig.upstream) {
1746
1758
  throw new Error('Config file must include "upstream" with at least "command" or "url"');
1747
1759
  }
1748
- if (fileConfig.upstream.url && detectSSRF(fileConfig.upstream.url)) {
1749
- throw new Error(
1750
- `Upstream URL blocked: "${fileConfig.upstream.url}" points to an internal or private network address.`
1751
- );
1752
- }
1753
1760
  return {
1754
1761
  upstream: fileConfig.upstream,
1755
1762
  policy: loadPolicy(fileConfig.policy ?? policySource),
1756
1763
  name: fileConfig.name ?? name,
1757
1764
  verbose: fileConfig.verbose ?? verbose,
1758
- validateInput: fileConfig.validateInput ?? validateInput,
1759
1765
  rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
1760
1766
  globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
1761
1767
  apiKey: apiKey ?? fileConfig.apiKey,
@@ -1764,12 +1770,6 @@ function parseArgs(argv) {
1764
1770
  };
1765
1771
  }
1766
1772
  if (upstreamUrl) {
1767
- if (detectSSRF(upstreamUrl)) {
1768
- throw new Error(
1769
- `Upstream URL blocked: "${upstreamUrl}" points to an internal or private network address.
1770
- SSRF protection prevents connecting to localhost, private IPs, or cloud metadata endpoints.`
1771
- );
1772
- }
1773
1773
  const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
1774
1774
  return {
1775
1775
  upstream: {
@@ -1781,7 +1781,6 @@ SSRF protection prevents connecting to localhost, private IPs, or cloud metadata
1781
1781
  policy: loadPolicy(policySource),
1782
1782
  name,
1783
1783
  verbose,
1784
- validateInput,
1785
1784
  rateLimitPerTool,
1786
1785
  globalRateLimit,
1787
1786
  apiKey,
@@ -1805,7 +1804,6 @@ SSRF protection prevents connecting to localhost, private IPs, or cloud metadata
1805
1804
  policy: loadPolicy(policySource),
1806
1805
  name,
1807
1806
  verbose,
1808
- validateInput,
1809
1807
  rateLimitPerTool,
1810
1808
  globalRateLimit,
1811
1809
  apiKey,
@@ -1834,7 +1832,7 @@ import {
1834
1832
  import { createServer as createHttpServer } from "http";
1835
1833
 
1836
1834
  // ../sdk-ts/dist/index.js
1837
- import { z as z2 } from "zod";
1835
+ import { z } from "zod";
1838
1836
  import { createHash, randomUUID, createHmac } from "crypto";
1839
1837
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1840
1838
  var SolonGateError = class extends Error {
@@ -1873,16 +1871,6 @@ var PolicyDeniedError = class extends SolonGateError {
1873
1871
  this.name = "PolicyDeniedError";
1874
1872
  }
1875
1873
  };
1876
- var SchemaValidationError = class extends SolonGateError {
1877
- constructor(toolName, validationErrors) {
1878
- super(
1879
- `Schema validation failed for tool "${toolName}": ${validationErrors.join("; ")}`,
1880
- "SCHEMA_VALIDATION_FAILED",
1881
- { toolName, validationErrors }
1882
- );
1883
- this.name = "SchemaValidationError";
1884
- }
1885
- };
1886
1874
  var RateLimitError = class extends SolonGateError {
1887
1875
  constructor(toolName, limitPerMinute) {
1888
1876
  super(
@@ -1898,49 +1886,53 @@ var TrustLevel = {
1898
1886
  VERIFIED: "VERIFIED",
1899
1887
  TRUSTED: "TRUSTED"
1900
1888
  };
1901
- var Permission2 = {
1889
+ var Permission = {
1902
1890
  READ: "READ",
1903
1891
  WRITE: "WRITE",
1904
1892
  EXECUTE: "EXECUTE"
1905
1893
  };
1906
- z2.enum(["READ", "WRITE", "EXECUTE"]);
1894
+ z.enum(["READ", "WRITE", "EXECUTE"]);
1907
1895
  Object.freeze(
1908
1896
  /* @__PURE__ */ new Set()
1909
1897
  );
1910
1898
  Object.freeze(
1911
- /* @__PURE__ */ new Set([Permission2.READ])
1899
+ /* @__PURE__ */ new Set([Permission.READ])
1912
1900
  );
1913
1901
  var PolicyEffect = {
1914
1902
  ALLOW: "ALLOW",
1915
1903
  DENY: "DENY"
1916
1904
  };
1917
- var PolicyRuleSchema2 = z2.object({
1918
- id: z2.string().min(1).max(256),
1919
- description: z2.string().max(1024),
1920
- effect: z2.enum(["ALLOW", "DENY"]),
1921
- priority: z2.number().int().min(0).max(1e4).default(1e3),
1922
- toolPattern: z2.string().min(1).max(512),
1923
- permission: z2.enum(["READ", "WRITE", "EXECUTE"]),
1924
- minimumTrustLevel: z2.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
1925
- argumentConstraints: z2.record(z2.unknown()).optional(),
1926
- pathConstraints: z2.object({
1927
- allowed: z2.array(z2.string()).optional(),
1928
- denied: z2.array(z2.string()).optional(),
1929
- rootDirectory: z2.string().optional(),
1930
- 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()
1931
1923
  }).optional(),
1932
- enabled: z2.boolean().default(true),
1933
- createdAt: z2.string().datetime(),
1934
- updatedAt: z2.string().datetime()
1924
+ enabled: z.boolean().default(true),
1925
+ createdAt: z.string().datetime(),
1926
+ updatedAt: z.string().datetime()
1935
1927
  });
1936
- var PolicySetSchema2 = z2.object({
1937
- id: z2.string().min(1).max(256),
1938
- name: z2.string().min(1).max(256),
1939
- description: z2.string().max(2048),
1940
- version: z2.number().int().min(0),
1941
- rules: z2.array(PolicyRuleSchema2),
1942
- createdAt: z2.string().datetime(),
1943
- 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()
1944
1936
  });
1945
1937
  function createSecurityContext(params) {
1946
1938
  return {
@@ -1979,7 +1971,7 @@ function createDeniedToolResult(reason) {
1979
1971
  isError: true
1980
1972
  };
1981
1973
  }
1982
- var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
1974
+ var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
1983
1975
  pathTraversal: true,
1984
1976
  shellInjection: true,
1985
1977
  wildcardAbuse: true,
@@ -1988,296 +1980,6 @@ var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
1988
1980
  ssrf: true,
1989
1981
  sqlInjection: true
1990
1982
  });
1991
- var PATH_TRAVERSAL_PATTERNS = [
1992
- /\.\.\//,
1993
- // ../
1994
- /\.\.\\/,
1995
- // ..\
1996
- /%2e%2e/i,
1997
- // URL-encoded ..
1998
- /%2e\./i,
1999
- // partial URL-encoded
2000
- /\.%2e/i,
2001
- // partial URL-encoded
2002
- /%252e%252e/i,
2003
- // double URL-encoded
2004
- /\.\.\0/
2005
- // null byte variant
2006
- ];
2007
- var SENSITIVE_PATHS = [
2008
- /\/etc\/passwd/i,
2009
- /\/etc\/shadow/i,
2010
- /\/proc\//i,
2011
- /\/dev\//i,
2012
- /c:\\windows\\system32/i,
2013
- /c:\\windows\\syswow64/i,
2014
- /\/root\//i,
2015
- /~\//,
2016
- /\.env(\.|$)/i,
2017
- // .env, .env.local, .env.production
2018
- /\.aws\/credentials/i,
2019
- // AWS credentials
2020
- /\.ssh\/id_/i,
2021
- // SSH keys
2022
- /\.kube\/config/i,
2023
- // Kubernetes config
2024
- /wp-config\.php/i,
2025
- // WordPress config
2026
- /\.git\/config/i,
2027
- // Git config
2028
- /\.npmrc/i,
2029
- // npm credentials
2030
- /\.pypirc/i
2031
- // PyPI credentials
2032
- ];
2033
- function detectPathTraversal(value) {
2034
- for (const pattern of PATH_TRAVERSAL_PATTERNS) {
2035
- if (pattern.test(value)) return true;
2036
- }
2037
- for (const pattern of SENSITIVE_PATHS) {
2038
- if (pattern.test(value)) return true;
2039
- }
2040
- return false;
2041
- }
2042
- var SHELL_INJECTION_PATTERNS = [
2043
- /[;|&`]/,
2044
- // Command separators and backtick execution
2045
- /\$\(/,
2046
- // Command substitution $(...)
2047
- /\$\{/,
2048
- // Variable expansion ${...}
2049
- />\s*/,
2050
- // Output redirect
2051
- /<\s*/,
2052
- // Input redirect
2053
- /&&/,
2054
- // AND chaining
2055
- /\|\|/,
2056
- // OR chaining
2057
- /\beval\b/i,
2058
- // eval command
2059
- /\bexec\b/i,
2060
- // exec command
2061
- /\bsystem\b/i,
2062
- // system call
2063
- /%0a/i,
2064
- // URL-encoded newline
2065
- /%0d/i,
2066
- // URL-encoded carriage return
2067
- /%09/i,
2068
- // URL-encoded tab
2069
- /\r\n/,
2070
- // CRLF injection
2071
- /\n/
2072
- // Newline (command separator on Unix)
2073
- ];
2074
- function detectShellInjection(value) {
2075
- for (const pattern of SHELL_INJECTION_PATTERNS) {
2076
- if (pattern.test(value)) return true;
2077
- }
2078
- return false;
2079
- }
2080
- var MAX_WILDCARDS_PER_VALUE = 3;
2081
- function detectWildcardAbuse(value) {
2082
- if (value.includes("**")) return true;
2083
- const wildcardCount = (value.match(/\*/g) || []).length;
2084
- if (wildcardCount > MAX_WILDCARDS_PER_VALUE) return true;
2085
- return false;
2086
- }
2087
- var SSRF_PATTERNS2 = [
2088
- /^https?:\/\/localhost\b/i,
2089
- /^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
2090
- /^https?:\/\/0\.0\.0\.0/,
2091
- /^https?:\/\/\[::1\]/,
2092
- // IPv6 loopback
2093
- /^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
2094
- // 10.x.x.x
2095
- /^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
2096
- // 172.16-31.x.x
2097
- /^https?:\/\/192\.168\./,
2098
- // 192.168.x.x
2099
- /^https?:\/\/169\.254\./,
2100
- // Link-local / AWS metadata
2101
- /metadata\.google\.internal/i,
2102
- // GCP metadata
2103
- /^https?:\/\/metadata\b/i,
2104
- // Generic metadata endpoint
2105
- // IPv6 bypass patterns
2106
- /^https?:\/\/\[fe80:/i,
2107
- // IPv6 link-local
2108
- /^https?:\/\/\[fc00:/i,
2109
- // IPv6 unique local
2110
- /^https?:\/\/\[fd[0-9a-f]{2}:/i,
2111
- // IPv6 unique local (fd00::/8)
2112
- /^https?:\/\/\[::ffff:127\./i,
2113
- // IPv4-mapped IPv6 loopback
2114
- /^https?:\/\/\[::ffff:10\./i,
2115
- // IPv4-mapped IPv6 private
2116
- /^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
2117
- // IPv4-mapped IPv6 private
2118
- /^https?:\/\/\[::ffff:192\.168\./i,
2119
- // IPv4-mapped IPv6 private
2120
- /^https?:\/\/\[::ffff:169\.254\./i,
2121
- // IPv4-mapped IPv6 link-local
2122
- // Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
2123
- /^https?:\/\/0x[0-9a-f]+\b/i,
2124
- // Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
2125
- /^https?:\/\/0[0-7]{1,3}\./
2126
- ];
2127
- function detectDecimalIP2(value) {
2128
- const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
2129
- if (!match || !match[1]) return false;
2130
- const decimal = parseInt(match[1], 10);
2131
- if (isNaN(decimal) || decimal > 4294967295) return false;
2132
- return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
2133
- decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
2134
- decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
2135
- decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
2136
- decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
2137
- decimal === 0;
2138
- }
2139
- function detectSSRF2(value) {
2140
- for (const pattern of SSRF_PATTERNS2) {
2141
- if (pattern.test(value)) return true;
2142
- }
2143
- if (detectDecimalIP2(value)) return true;
2144
- return false;
2145
- }
2146
- var SQL_INJECTION_PATTERNS = [
2147
- /'\s{0,20}(OR|AND)\s{0,20}'.{0,200}'/i,
2148
- // ' OR '1'='1 — bounded to prevent ReDoS
2149
- /'\s{0,10};\s{0,10}(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|EXEC)/i,
2150
- // '; DROP TABLE
2151
- /UNION\s+(ALL\s+)?SELECT/i,
2152
- // UNION SELECT
2153
- /--\s*$/m,
2154
- // SQL comment at end of line
2155
- /\/\*.{0,500}?\*\//,
2156
- // SQL block comment — bounded + non-greedy
2157
- /\bSLEEP\s*\(/i,
2158
- // Time-based injection
2159
- /\bBENCHMARK\s*\(/i,
2160
- // MySQL benchmark
2161
- /\bWAITFOR\s+DELAY/i,
2162
- // MSSQL delay
2163
- /\b(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)\b/i
2164
- // File operations
2165
- ];
2166
- function detectSQLInjection(value) {
2167
- for (const pattern of SQL_INJECTION_PATTERNS) {
2168
- if (pattern.test(value)) return true;
2169
- }
2170
- return false;
2171
- }
2172
- function checkLengthLimits(value, maxLength = 4096) {
2173
- return value.length <= maxLength;
2174
- }
2175
- var ENTROPY_THRESHOLD = 4.5;
2176
- var MIN_LENGTH_FOR_ENTROPY_CHECK = 32;
2177
- function checkEntropyLimits(value) {
2178
- if (value.length < MIN_LENGTH_FOR_ENTROPY_CHECK) return true;
2179
- const entropy = calculateShannonEntropy(value);
2180
- return entropy <= ENTROPY_THRESHOLD;
2181
- }
2182
- function calculateShannonEntropy(str) {
2183
- const freq = /* @__PURE__ */ new Map();
2184
- for (const char of str) {
2185
- freq.set(char, (freq.get(char) ?? 0) + 1);
2186
- }
2187
- let entropy = 0;
2188
- const len = str.length;
2189
- for (const count of freq.values()) {
2190
- const p = count / len;
2191
- if (p > 0) {
2192
- entropy -= p * Math.log2(p);
2193
- }
2194
- }
2195
- return entropy;
2196
- }
2197
- function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG2) {
2198
- const threats = [];
2199
- if (typeof value !== "string") {
2200
- if (typeof value === "object" && value !== null) {
2201
- return sanitizeObject(field, value, config);
2202
- }
2203
- return { safe: true, threats: [] };
2204
- }
2205
- if (config.pathTraversal && detectPathTraversal(value)) {
2206
- threats.push({
2207
- type: "PATH_TRAVERSAL",
2208
- field,
2209
- value: truncate(value, 100),
2210
- description: "Path traversal pattern detected"
2211
- });
2212
- }
2213
- if (config.shellInjection && detectShellInjection(value)) {
2214
- threats.push({
2215
- type: "SHELL_INJECTION",
2216
- field,
2217
- value: truncate(value, 100),
2218
- description: "Shell injection pattern detected"
2219
- });
2220
- }
2221
- if (config.wildcardAbuse && detectWildcardAbuse(value)) {
2222
- threats.push({
2223
- type: "WILDCARD_ABUSE",
2224
- field,
2225
- value: truncate(value, 100),
2226
- description: "Wildcard abuse pattern detected"
2227
- });
2228
- }
2229
- if (!checkLengthLimits(value, config.lengthLimit)) {
2230
- threats.push({
2231
- type: "LENGTH_EXCEEDED",
2232
- field,
2233
- value: `[${value.length} chars]`,
2234
- description: `Value exceeds maximum length of ${config.lengthLimit}`
2235
- });
2236
- }
2237
- if (config.entropyLimit && !checkEntropyLimits(value)) {
2238
- threats.push({
2239
- type: "HIGH_ENTROPY",
2240
- field,
2241
- value: truncate(value, 100),
2242
- description: "High entropy string detected - possible encoded payload"
2243
- });
2244
- }
2245
- if (config.ssrf && detectSSRF2(value)) {
2246
- threats.push({
2247
- type: "SSRF",
2248
- field,
2249
- value: truncate(value, 100),
2250
- description: "Server-side request forgery pattern detected \u2014 internal/metadata URL blocked"
2251
- });
2252
- }
2253
- if (config.sqlInjection && detectSQLInjection(value)) {
2254
- threats.push({
2255
- type: "SQL_INJECTION",
2256
- field,
2257
- value: truncate(value, 100),
2258
- description: "SQL injection pattern detected"
2259
- });
2260
- }
2261
- return { safe: threats.length === 0, threats };
2262
- }
2263
- function sanitizeObject(basePath, obj, config) {
2264
- const threats = [];
2265
- if (Array.isArray(obj)) {
2266
- for (let i = 0; i < obj.length; i++) {
2267
- const result = sanitizeInput(`${basePath}[${i}]`, obj[i], config);
2268
- threats.push(...result.threats);
2269
- }
2270
- } else {
2271
- for (const [key, val] of Object.entries(obj)) {
2272
- const result = sanitizeInput(`${basePath}.${key}`, val, config);
2273
- threats.push(...result.threats);
2274
- }
2275
- }
2276
- return { safe: threats.length === 0, threats };
2277
- }
2278
- function truncate(str, maxLen) {
2279
- return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
2280
- }
2281
1983
  var DEFAULT_TOKEN_TTL_SECONDS = 30;
2282
1984
  var TOKEN_ALGORITHM = "HS256";
2283
1985
  var MIN_SECRET_LENGTH = 32;
@@ -2335,6 +2037,14 @@ function matchParts(pathParts, pi, patternParts, qi) {
2335
2037
  qi++;
2336
2038
  continue;
2337
2039
  }
2040
+ if (pattern.includes("*")) {
2041
+ if (!matchSegmentGlob(pathParts[pi], pattern)) {
2042
+ return false;
2043
+ }
2044
+ pi++;
2045
+ qi++;
2046
+ continue;
2047
+ }
2338
2048
  if (pattern !== pathParts[pi]) {
2339
2049
  return false;
2340
2050
  }
@@ -2371,6 +2081,31 @@ function isPathAllowed(path, constraints) {
2371
2081
  }
2372
2082
  return true;
2373
2083
  }
2084
+ function matchSegmentGlob(segment, pattern) {
2085
+ const startsWithStar = pattern.startsWith("*");
2086
+ const endsWithStar = pattern.endsWith("*");
2087
+ if (pattern === "*") return true;
2088
+ if (startsWithStar && endsWithStar) {
2089
+ const infix = pattern.slice(1, -1);
2090
+ return segment.toLowerCase().includes(infix.toLowerCase());
2091
+ }
2092
+ if (startsWithStar) {
2093
+ const suffix = pattern.slice(1);
2094
+ return segment.toLowerCase().endsWith(suffix.toLowerCase());
2095
+ }
2096
+ if (endsWithStar) {
2097
+ const prefix = pattern.slice(0, -1);
2098
+ return segment.toLowerCase().startsWith(prefix.toLowerCase());
2099
+ }
2100
+ const starIdx = pattern.indexOf("*");
2101
+ if (starIdx !== -1) {
2102
+ const prefix = pattern.slice(0, starIdx);
2103
+ const suffix = pattern.slice(starIdx + 1);
2104
+ const seg = segment.toLowerCase();
2105
+ return seg.startsWith(prefix.toLowerCase()) && seg.endsWith(suffix.toLowerCase()) && seg.length >= prefix.length + suffix.length;
2106
+ }
2107
+ return segment === pattern;
2108
+ }
2374
2109
  function extractPathArguments(args) {
2375
2110
  const paths = [];
2376
2111
  for (const value of Object.values(args)) {
@@ -2380,6 +2115,69 @@ function extractPathArguments(args) {
2380
2115
  }
2381
2116
  return paths;
2382
2117
  }
2118
+ var COMMAND_FIELDS = /* @__PURE__ */ new Set([
2119
+ "command",
2120
+ "cmd",
2121
+ "query",
2122
+ "code",
2123
+ "script",
2124
+ "shell",
2125
+ "exec",
2126
+ "sql",
2127
+ "expression"
2128
+ ]);
2129
+ function extractCommandArguments(args) {
2130
+ const commands = [];
2131
+ for (const [key, value] of Object.entries(args)) {
2132
+ if (typeof value !== "string") continue;
2133
+ if (COMMAND_FIELDS.has(key.toLowerCase())) {
2134
+ commands.push(value);
2135
+ }
2136
+ }
2137
+ return commands;
2138
+ }
2139
+ function matchCommandPattern(command, pattern) {
2140
+ if (pattern === "*") return true;
2141
+ const normalizedCommand = command.trim().toLowerCase();
2142
+ const normalizedPattern = pattern.trim().toLowerCase();
2143
+ if (normalizedPattern === normalizedCommand) return true;
2144
+ const startsWithStar = normalizedPattern.startsWith("*");
2145
+ const endsWithStar = normalizedPattern.endsWith("*");
2146
+ if (startsWithStar && endsWithStar) {
2147
+ const infix = normalizedPattern.slice(1, -1);
2148
+ return infix.length > 0 && normalizedCommand.includes(infix);
2149
+ }
2150
+ if (endsWithStar) {
2151
+ const prefix = normalizedPattern.slice(0, -1);
2152
+ return normalizedCommand.startsWith(prefix);
2153
+ }
2154
+ if (startsWithStar) {
2155
+ const suffix = normalizedPattern.slice(1);
2156
+ return normalizedCommand.endsWith(suffix);
2157
+ }
2158
+ const commandName = normalizedCommand.split(/\s+/)[0] ?? "";
2159
+ return commandName === normalizedPattern;
2160
+ }
2161
+ function isCommandAllowed(command, constraints) {
2162
+ if (constraints.denied && constraints.denied.length > 0) {
2163
+ for (const pattern of constraints.denied) {
2164
+ if (matchCommandPattern(command, pattern)) {
2165
+ return false;
2166
+ }
2167
+ }
2168
+ }
2169
+ if (constraints.allowed && constraints.allowed.length > 0) {
2170
+ let matchesAllowed = false;
2171
+ for (const pattern of constraints.allowed) {
2172
+ if (matchCommandPattern(command, pattern)) {
2173
+ matchesAllowed = true;
2174
+ break;
2175
+ }
2176
+ }
2177
+ if (!matchesAllowed) return false;
2178
+ }
2179
+ return true;
2180
+ }
2383
2181
  function ruleMatchesRequest(rule, request) {
2384
2182
  if (!rule.enabled) return false;
2385
2183
  if (rule.permission !== request.requiredPermission) return false;
@@ -2393,8 +2191,19 @@ function ruleMatchesRequest(rule, request) {
2393
2191
  }
2394
2192
  }
2395
2193
  if (rule.pathConstraints) {
2396
- if (!pathConstraintsMatch(rule.pathConstraints, request.arguments)) {
2397
- return false;
2194
+ const satisfied = pathConstraintsMatch(rule.pathConstraints, request.arguments);
2195
+ if (rule.effect === "DENY") {
2196
+ if (satisfied) return false;
2197
+ } else {
2198
+ if (!satisfied) return false;
2199
+ }
2200
+ }
2201
+ if (rule.commandConstraints) {
2202
+ const satisfied = commandConstraintsMatch(rule.commandConstraints, request.arguments);
2203
+ if (rule.effect === "DENY") {
2204
+ if (satisfied) return false;
2205
+ } else {
2206
+ if (!satisfied) return false;
2398
2207
  }
2399
2208
  }
2400
2209
  return true;
@@ -2482,6 +2291,11 @@ function pathConstraintsMatch(constraints, args) {
2482
2291
  if (paths.length === 0) return true;
2483
2292
  return paths.every((path) => isPathAllowed(path, constraints));
2484
2293
  }
2294
+ function commandConstraintsMatch(constraints, args) {
2295
+ const commands = extractCommandArguments(args);
2296
+ if (commands.length === 0) return true;
2297
+ return commands.every((cmd) => isCommandAllowed(cmd, constraints));
2298
+ }
2485
2299
  function evaluatePolicy(policySet, request) {
2486
2300
  const startTime = performance.now();
2487
2301
  const sortedRules = [...policySet.rules].sort(
@@ -2519,7 +2333,7 @@ function evaluatePolicy(policySet, request) {
2519
2333
  function validatePolicyRule(input) {
2520
2334
  const errors = [];
2521
2335
  const warnings = [];
2522
- const result = PolicyRuleSchema2.safeParse(input);
2336
+ const result = PolicyRuleSchema.safeParse(input);
2523
2337
  if (!result.success) {
2524
2338
  return {
2525
2339
  valid: false,
@@ -2544,7 +2358,7 @@ function validatePolicyRule(input) {
2544
2358
  function validatePolicySet(input) {
2545
2359
  const errors = [];
2546
2360
  const warnings = [];
2547
- const result = PolicySetSchema2.safeParse(input);
2361
+ const result = PolicySetSchema.safeParse(input);
2548
2362
  if (!result.success) {
2549
2363
  return {
2550
2364
  valid: false,
@@ -2638,7 +2452,7 @@ function createDefaultDenyPolicySet() {
2638
2452
  effect: PolicyEffect.DENY,
2639
2453
  priority: 1e4,
2640
2454
  toolPattern: "*",
2641
- permission: Permission2.EXECUTE,
2455
+ permission: Permission.EXECUTE,
2642
2456
  minimumTrustLevel: TrustLevel.UNTRUSTED,
2643
2457
  enabled: true,
2644
2458
  createdAt: now,
@@ -2650,7 +2464,7 @@ function createDefaultDenyPolicySet() {
2650
2464
  effect: PolicyEffect.DENY,
2651
2465
  priority: 1e4,
2652
2466
  toolPattern: "*",
2653
- permission: Permission2.WRITE,
2467
+ permission: Permission.WRITE,
2654
2468
  minimumTrustLevel: TrustLevel.UNTRUSTED,
2655
2469
  enabled: true,
2656
2470
  createdAt: now,
@@ -2662,7 +2476,7 @@ function createDefaultDenyPolicySet() {
2662
2476
  effect: PolicyEffect.DENY,
2663
2477
  priority: 1e4,
2664
2478
  toolPattern: "*",
2665
- permission: Permission2.READ,
2479
+ permission: Permission.READ,
2666
2480
  minimumTrustLevel: TrustLevel.UNTRUSTED,
2667
2481
  enabled: true,
2668
2482
  createdAt: now,
@@ -2841,7 +2655,6 @@ var DEFAULT_CONFIG = Object.freeze({
2841
2655
  globalRateLimitPerMinute: 600,
2842
2656
  rateLimitPerTool: 60,
2843
2657
  tokenTtlSeconds: 30,
2844
- inputGuardConfig: DEFAULT_INPUT_GUARD_CONFIG2,
2845
2658
  enableVersionedPolicies: true
2846
2659
  });
2847
2660
  function resolveConfig(userConfig) {
@@ -2874,7 +2687,7 @@ async function interceptToolCall(params, upstreamCall, options) {
2874
2687
  toolName: params.name,
2875
2688
  serverName: "default",
2876
2689
  arguments: params.arguments ?? {},
2877
- requiredPermission: Permission2.EXECUTE,
2690
+ requiredPermission: Permission.EXECUTE,
2878
2691
  timestamp
2879
2692
  };
2880
2693
  if (options.rateLimiter) {
@@ -2912,24 +2725,6 @@ async function interceptToolCall(params, upstreamCall, options) {
2912
2725
  }
2913
2726
  }
2914
2727
  }
2915
- if (options.validateSchemas && params.arguments) {
2916
- const guardConfig = options.inputGuardConfig ?? DEFAULT_INPUT_GUARD_CONFIG2;
2917
- const sanitization = sanitizeInput("arguments", params.arguments, guardConfig);
2918
- if (!sanitization.safe) {
2919
- const threatDescriptions = sanitization.threats.map(
2920
- (t) => `${t.type}: ${t.description} (field: ${t.field})`
2921
- );
2922
- const result = {
2923
- status: "ERROR",
2924
- request,
2925
- error: new SchemaValidationError(params.name, threatDescriptions),
2926
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2927
- };
2928
- options.onDecision?.(result);
2929
- const reason = options.verboseErrors ? `Input validation failed: ${sanitization.threats.length} threat(s) detected` : "Input validation failed.";
2930
- return createDeniedToolResult(reason);
2931
- }
2932
- }
2933
2728
  const decision = options.policyEngine.evaluate(request);
2934
2729
  if (decision.effect === "DENY") {
2935
2730
  const result = {
@@ -2946,7 +2741,7 @@ async function interceptToolCall(params, upstreamCall, options) {
2946
2741
  if (options.tokenIssuer) {
2947
2742
  capabilityToken = options.tokenIssuer.issue(
2948
2743
  requestId,
2949
- [Permission2.EXECUTE],
2744
+ [Permission.EXECUTE],
2950
2745
  [params.name]
2951
2746
  );
2952
2747
  }
@@ -3523,7 +3318,6 @@ var SolonGate = class {
3523
3318
  tokenIssuer: this.tokenIssuer ?? void 0,
3524
3319
  serverVerifier: this.serverVerifier ?? void 0,
3525
3320
  rateLimiter: this.rateLimiter,
3526
- inputGuardConfig: this.config.inputGuardConfig,
3527
3321
  rateLimitPerTool: this.config.rateLimitPerTool,
3528
3322
  globalRateLimitPerMinute: this.config.globalRateLimitPerMinute
3529
3323
  });
@@ -3591,7 +3385,7 @@ var SolonGateProxy = class {
3591
3385
  apiKey: "sg_test_proxy_internal_00000000",
3592
3386
  policySet: config.policy,
3593
3387
  config: {
3594
- validateSchemas: config.validateInput ?? true,
3388
+ validateSchemas: true,
3595
3389
  verboseErrors: config.verbose ?? false,
3596
3390
  rateLimitPerTool: config.rateLimitPerTool,
3597
3391
  globalRateLimitPerMinute: config.globalRateLimit
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.2.9",
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.1",
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",