@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.
- package/dist/index.js +279 -513
- package/dist/init.js +5 -0
- 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
|
|
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-
|
|
1500
|
-
description: "Block shell
|
|
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:
|
|
1515
|
-
toolPattern: "*
|
|
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-
|
|
1524
|
-
description: "Block
|
|
1424
|
+
id: "deny-sensitive-paths",
|
|
1425
|
+
description: "Block access to sensitive files",
|
|
1525
1426
|
effect: "DENY",
|
|
1526
|
-
priority:
|
|
1527
|
-
toolPattern: "*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1889
|
+
var Permission = {
|
|
1897
1890
|
READ: "READ",
|
|
1898
1891
|
WRITE: "WRITE",
|
|
1899
1892
|
EXECUTE: "EXECUTE"
|
|
1900
1893
|
};
|
|
1901
|
-
|
|
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([
|
|
1899
|
+
/* @__PURE__ */ new Set([Permission.READ])
|
|
1907
1900
|
);
|
|
1908
1901
|
var PolicyEffect = {
|
|
1909
1902
|
ALLOW: "ALLOW",
|
|
1910
1903
|
DENY: "DENY"
|
|
1911
1904
|
};
|
|
1912
|
-
var
|
|
1913
|
-
id:
|
|
1914
|
-
description:
|
|
1915
|
-
effect:
|
|
1916
|
-
priority:
|
|
1917
|
-
toolPattern:
|
|
1918
|
-
permission:
|
|
1919
|
-
minimumTrustLevel:
|
|
1920
|
-
argumentConstraints:
|
|
1921
|
-
pathConstraints:
|
|
1922
|
-
allowed:
|
|
1923
|
-
denied:
|
|
1924
|
-
rootDirectory:
|
|
1925
|
-
allowSymlinks:
|
|
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:
|
|
1928
|
-
createdAt:
|
|
1929
|
-
updatedAt:
|
|
1924
|
+
enabled: z.boolean().default(true),
|
|
1925
|
+
createdAt: z.string().datetime(),
|
|
1926
|
+
updatedAt: z.string().datetime()
|
|
1930
1927
|
});
|
|
1931
|
-
var
|
|
1932
|
-
id:
|
|
1933
|
-
name:
|
|
1934
|
-
description:
|
|
1935
|
-
version:
|
|
1936
|
-
rules:
|
|
1937
|
-
createdAt:
|
|
1938
|
-
updatedAt:
|
|
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
|
|
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
|
-
|
|
2392
|
-
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
[
|
|
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:
|
|
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.
|
|
4
|
-
"description": "MCP security proxy
|
|
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",
|