@solongate/proxy 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create.js +32 -16
- package/dist/index.js +668 -547
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -820,14 +820,28 @@ dist/
|
|
|
820
820
|
`
|
|
821
821
|
);
|
|
822
822
|
}
|
|
823
|
-
function
|
|
824
|
-
|
|
823
|
+
function withSpinner(message, fn) {
|
|
824
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
825
|
+
let i = 0;
|
|
826
|
+
const id = setInterval(() => {
|
|
827
|
+
process.stderr.write(`\r ${frames[i++ % frames.length]} ${message}`);
|
|
828
|
+
}, 80);
|
|
825
829
|
try {
|
|
826
|
-
|
|
830
|
+
fn();
|
|
831
|
+
clearInterval(id);
|
|
832
|
+
process.stderr.write(`\r \u2713 ${message}
|
|
833
|
+
`);
|
|
827
834
|
} catch {
|
|
828
|
-
|
|
835
|
+
clearInterval(id);
|
|
836
|
+
process.stderr.write(`\r \u2717 ${message} \u2014 failed
|
|
837
|
+
`);
|
|
829
838
|
}
|
|
830
839
|
}
|
|
840
|
+
function installDeps(dir) {
|
|
841
|
+
withSpinner("Installing dependencies...", () => {
|
|
842
|
+
execSync2("npm install", { cwd: dir, stdio: "pipe" });
|
|
843
|
+
});
|
|
844
|
+
}
|
|
831
845
|
async function main3() {
|
|
832
846
|
const opts = parseCreateArgs(process.argv);
|
|
833
847
|
const dir = resolve4(opts.name);
|
|
@@ -857,18 +871,20 @@ async function main3() {
|
|
|
857
871
|
installDeps(dir);
|
|
858
872
|
log3("");
|
|
859
873
|
}
|
|
860
|
-
|
|
861
|
-
log3(
|
|
862
|
-
log3(
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
874
|
+
const W = 46;
|
|
875
|
+
const line = (s) => log3(` \u2502 ${s.padEnd(W)} \u2502`);
|
|
876
|
+
log3(` \u250C${"\u2500".repeat(W + 2)}\u2510`);
|
|
877
|
+
line("Project created!");
|
|
878
|
+
line("");
|
|
879
|
+
line(`cd ${opts.name}`);
|
|
880
|
+
line("");
|
|
881
|
+
line("npm run build # Build");
|
|
882
|
+
line("npm run dev # Dev mode (tsx)");
|
|
883
|
+
line("npm start # Run built server");
|
|
884
|
+
line("");
|
|
885
|
+
line("Set your API key:");
|
|
886
|
+
line("export SOLONGATE_API_KEY=sg_live_xxx");
|
|
887
|
+
log3(` \u2514${"\u2500".repeat(W + 2)}\u2518`);
|
|
872
888
|
log3("");
|
|
873
889
|
}
|
|
874
890
|
var init_create = __esm({
|
|
@@ -883,67 +899,6 @@ var init_create = __esm({
|
|
|
883
899
|
|
|
884
900
|
// ../core/dist/index.js
|
|
885
901
|
import { z } from "zod";
|
|
886
|
-
var SolonGateError = class extends Error {
|
|
887
|
-
code;
|
|
888
|
-
timestamp;
|
|
889
|
-
details;
|
|
890
|
-
constructor(message, code, details = {}) {
|
|
891
|
-
super(message);
|
|
892
|
-
this.name = "SolonGateError";
|
|
893
|
-
this.code = code;
|
|
894
|
-
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
895
|
-
this.details = Object.freeze({ ...details });
|
|
896
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Serializable representation for logging and API responses.
|
|
900
|
-
* Never includes stack traces (information leakage prevention).
|
|
901
|
-
*/
|
|
902
|
-
toJSON() {
|
|
903
|
-
return {
|
|
904
|
-
name: this.name,
|
|
905
|
-
code: this.code,
|
|
906
|
-
message: this.message,
|
|
907
|
-
timestamp: this.timestamp,
|
|
908
|
-
details: this.details
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
var PolicyDeniedError = class extends SolonGateError {
|
|
913
|
-
constructor(toolName, reason, details = {}) {
|
|
914
|
-
super(
|
|
915
|
-
`Policy denied execution of tool "${toolName}": ${reason}`,
|
|
916
|
-
"POLICY_DENIED",
|
|
917
|
-
{ toolName, reason, ...details }
|
|
918
|
-
);
|
|
919
|
-
this.name = "PolicyDeniedError";
|
|
920
|
-
}
|
|
921
|
-
};
|
|
922
|
-
var SchemaValidationError = class extends SolonGateError {
|
|
923
|
-
constructor(toolName, validationErrors) {
|
|
924
|
-
super(
|
|
925
|
-
`Schema validation failed for tool "${toolName}": ${validationErrors.join("; ")}`,
|
|
926
|
-
"SCHEMA_VALIDATION_FAILED",
|
|
927
|
-
{ toolName, validationErrors }
|
|
928
|
-
);
|
|
929
|
-
this.name = "SchemaValidationError";
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
var RateLimitError = class extends SolonGateError {
|
|
933
|
-
constructor(toolName, limitPerMinute) {
|
|
934
|
-
super(
|
|
935
|
-
`Rate limit exceeded for tool "${toolName}": max ${limitPerMinute}/min`,
|
|
936
|
-
"RATE_LIMIT_EXCEEDED",
|
|
937
|
-
{ toolName, limitPerMinute }
|
|
938
|
-
);
|
|
939
|
-
this.name = "RateLimitError";
|
|
940
|
-
}
|
|
941
|
-
};
|
|
942
|
-
var TrustLevel = {
|
|
943
|
-
UNTRUSTED: "UNTRUSTED",
|
|
944
|
-
VERIFIED: "VERIFIED",
|
|
945
|
-
TRUSTED: "TRUSTED"
|
|
946
|
-
};
|
|
947
902
|
var Permission = {
|
|
948
903
|
READ: "READ",
|
|
949
904
|
WRITE: "WRITE",
|
|
@@ -956,10 +911,6 @@ var NO_PERMISSIONS = Object.freeze(
|
|
|
956
911
|
var READ_ONLY = Object.freeze(
|
|
957
912
|
/* @__PURE__ */ new Set([Permission.READ])
|
|
958
913
|
);
|
|
959
|
-
var PolicyEffect = {
|
|
960
|
-
ALLOW: "ALLOW",
|
|
961
|
-
DENY: "DENY"
|
|
962
|
-
};
|
|
963
914
|
var PolicyRuleSchema = z.object({
|
|
964
915
|
id: z.string().min(1).max(256),
|
|
965
916
|
description: z.string().max(1024),
|
|
@@ -988,45 +939,7 @@ var PolicySetSchema = z.object({
|
|
|
988
939
|
createdAt: z.string().datetime(),
|
|
989
940
|
updatedAt: z.string().datetime()
|
|
990
941
|
});
|
|
991
|
-
function createSecurityContext(params) {
|
|
992
|
-
return {
|
|
993
|
-
trustLevel: "UNTRUSTED",
|
|
994
|
-
grantedPermissions: /* @__PURE__ */ new Set(),
|
|
995
|
-
sessionId: null,
|
|
996
|
-
metadata: {},
|
|
997
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
998
|
-
...params
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
var DEFAULT_POLICY_EFFECT = "DENY";
|
|
1002
|
-
var MAX_RULES_PER_POLICY_SET = 1e3;
|
|
1003
942
|
var SECURITY_CONTEXT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1004
|
-
var POLICY_EVALUATION_TIMEOUT_MS = 100;
|
|
1005
|
-
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1006
|
-
var RATE_LIMIT_MAX_ENTRIES = 1e4;
|
|
1007
|
-
var UNSAFE_CONFIGURATION_WARNINGS = {
|
|
1008
|
-
WILDCARD_ALLOW: "Wildcard ALLOW rules grant permission to ALL tools. This bypasses the default-deny model.",
|
|
1009
|
-
TRUSTED_LEVEL_EXTERNAL: "Setting trust level to TRUSTED for external requests bypasses all security checks.",
|
|
1010
|
-
WRITE_WITHOUT_READ: "Granting WRITE without READ is unusual and may indicate a misconfiguration.",
|
|
1011
|
-
EXECUTE_WITHOUT_REVIEW: "EXECUTE permission allows tools to perform arbitrary actions. Review carefully.",
|
|
1012
|
-
RATE_LIMIT_ZERO: "A rate limit of 0 means unlimited calls. This removes protection against runaway loops.",
|
|
1013
|
-
DISABLED_VALIDATION: "Disabling schema validation removes input sanitization protections."
|
|
1014
|
-
};
|
|
1015
|
-
function createDeniedToolResult(reason) {
|
|
1016
|
-
return {
|
|
1017
|
-
content: [
|
|
1018
|
-
{
|
|
1019
|
-
type: "text",
|
|
1020
|
-
text: JSON.stringify({
|
|
1021
|
-
error: "POLICY_DENIED",
|
|
1022
|
-
message: reason,
|
|
1023
|
-
hint: "This tool call was blocked by SolonGate security policy. Check your policy configuration."
|
|
1024
|
-
})
|
|
1025
|
-
}
|
|
1026
|
-
],
|
|
1027
|
-
isError: true
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
943
|
var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
|
|
1031
944
|
pathTraversal: true,
|
|
1032
945
|
shellInjection: true,
|
|
@@ -1036,102 +949,6 @@ var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
|
|
|
1036
949
|
ssrf: true,
|
|
1037
950
|
sqlInjection: true
|
|
1038
951
|
});
|
|
1039
|
-
var PATH_TRAVERSAL_PATTERNS = [
|
|
1040
|
-
/\.\.\//,
|
|
1041
|
-
// ../
|
|
1042
|
-
/\.\.\\/,
|
|
1043
|
-
// ..\
|
|
1044
|
-
/%2e%2e/i,
|
|
1045
|
-
// URL-encoded ..
|
|
1046
|
-
/%2e\./i,
|
|
1047
|
-
// partial URL-encoded
|
|
1048
|
-
/\.%2e/i,
|
|
1049
|
-
// partial URL-encoded
|
|
1050
|
-
/%252e%252e/i,
|
|
1051
|
-
// double URL-encoded
|
|
1052
|
-
/\.\.\0/
|
|
1053
|
-
// null byte variant
|
|
1054
|
-
];
|
|
1055
|
-
var SENSITIVE_PATHS = [
|
|
1056
|
-
/\/etc\/passwd/i,
|
|
1057
|
-
/\/etc\/shadow/i,
|
|
1058
|
-
/\/proc\//i,
|
|
1059
|
-
/\/dev\//i,
|
|
1060
|
-
/c:\\windows\\system32/i,
|
|
1061
|
-
/c:\\windows\\syswow64/i,
|
|
1062
|
-
/\/root\//i,
|
|
1063
|
-
/~\//,
|
|
1064
|
-
/\.env(\.|$)/i,
|
|
1065
|
-
// .env, .env.local, .env.production
|
|
1066
|
-
/\.aws\/credentials/i,
|
|
1067
|
-
// AWS credentials
|
|
1068
|
-
/\.ssh\/id_/i,
|
|
1069
|
-
// SSH keys
|
|
1070
|
-
/\.kube\/config/i,
|
|
1071
|
-
// Kubernetes config
|
|
1072
|
-
/wp-config\.php/i,
|
|
1073
|
-
// WordPress config
|
|
1074
|
-
/\.git\/config/i,
|
|
1075
|
-
// Git config
|
|
1076
|
-
/\.npmrc/i,
|
|
1077
|
-
// npm credentials
|
|
1078
|
-
/\.pypirc/i
|
|
1079
|
-
// PyPI credentials
|
|
1080
|
-
];
|
|
1081
|
-
function detectPathTraversal(value) {
|
|
1082
|
-
for (const pattern of PATH_TRAVERSAL_PATTERNS) {
|
|
1083
|
-
if (pattern.test(value)) return true;
|
|
1084
|
-
}
|
|
1085
|
-
for (const pattern of SENSITIVE_PATHS) {
|
|
1086
|
-
if (pattern.test(value)) return true;
|
|
1087
|
-
}
|
|
1088
|
-
return false;
|
|
1089
|
-
}
|
|
1090
|
-
var SHELL_INJECTION_PATTERNS = [
|
|
1091
|
-
/[;|&`]/,
|
|
1092
|
-
// Command separators and backtick execution
|
|
1093
|
-
/\$\(/,
|
|
1094
|
-
// Command substitution $(...)
|
|
1095
|
-
/\$\{/,
|
|
1096
|
-
// Variable expansion ${...}
|
|
1097
|
-
/>\s*/,
|
|
1098
|
-
// Output redirect
|
|
1099
|
-
/<\s*/,
|
|
1100
|
-
// Input redirect
|
|
1101
|
-
/&&/,
|
|
1102
|
-
// AND chaining
|
|
1103
|
-
/\|\|/,
|
|
1104
|
-
// OR chaining
|
|
1105
|
-
/\beval\b/i,
|
|
1106
|
-
// eval command
|
|
1107
|
-
/\bexec\b/i,
|
|
1108
|
-
// exec command
|
|
1109
|
-
/\bsystem\b/i,
|
|
1110
|
-
// system call
|
|
1111
|
-
/%0a/i,
|
|
1112
|
-
// URL-encoded newline
|
|
1113
|
-
/%0d/i,
|
|
1114
|
-
// URL-encoded carriage return
|
|
1115
|
-
/%09/i,
|
|
1116
|
-
// URL-encoded tab
|
|
1117
|
-
/\r\n/,
|
|
1118
|
-
// CRLF injection
|
|
1119
|
-
/\n/
|
|
1120
|
-
// Newline (command separator on Unix)
|
|
1121
|
-
];
|
|
1122
|
-
function detectShellInjection(value) {
|
|
1123
|
-
for (const pattern of SHELL_INJECTION_PATTERNS) {
|
|
1124
|
-
if (pattern.test(value)) return true;
|
|
1125
|
-
}
|
|
1126
|
-
return false;
|
|
1127
|
-
}
|
|
1128
|
-
var MAX_WILDCARDS_PER_VALUE = 3;
|
|
1129
|
-
function detectWildcardAbuse(value) {
|
|
1130
|
-
if (value.includes("**")) return true;
|
|
1131
|
-
const wildcardCount = (value.match(/\*/g) || []).length;
|
|
1132
|
-
if (wildcardCount > MAX_WILDCARDS_PER_VALUE) return true;
|
|
1133
|
-
return false;
|
|
1134
|
-
}
|
|
1135
952
|
var SSRF_PATTERNS = [
|
|
1136
953
|
/^https?:\/\/localhost\b/i,
|
|
1137
954
|
/^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
|
|
@@ -1191,166 +1008,28 @@ function detectSSRF(value) {
|
|
|
1191
1008
|
if (detectDecimalIP(value)) return true;
|
|
1192
1009
|
return false;
|
|
1193
1010
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
/
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
// Time-based injection
|
|
1207
|
-
/\bBENCHMARK\s*\(/i,
|
|
1208
|
-
// MySQL benchmark
|
|
1209
|
-
/\bWAITFOR\s+DELAY/i,
|
|
1210
|
-
// MSSQL delay
|
|
1211
|
-
/\b(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)\b/i
|
|
1212
|
-
// File operations
|
|
1213
|
-
];
|
|
1214
|
-
function detectSQLInjection(value) {
|
|
1215
|
-
for (const pattern of SQL_INJECTION_PATTERNS) {
|
|
1216
|
-
if (pattern.test(value)) return true;
|
|
1011
|
+
|
|
1012
|
+
// src/config.ts
|
|
1013
|
+
import { readFileSync, existsSync } from "fs";
|
|
1014
|
+
import { resolve } from "path";
|
|
1015
|
+
async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
1016
|
+
const url = `${apiUrl}/api/v1/policies/${policyId ?? "default"}`;
|
|
1017
|
+
const res = await fetch(url, {
|
|
1018
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
1019
|
+
});
|
|
1020
|
+
if (!res.ok) {
|
|
1021
|
+
const body = await res.text().catch(() => "");
|
|
1022
|
+
throw new Error(`Failed to fetch policy from cloud (${res.status}): ${body}`);
|
|
1217
1023
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
const entropy = calculateShannonEntropy(value);
|
|
1228
|
-
return entropy <= ENTROPY_THRESHOLD;
|
|
1229
|
-
}
|
|
1230
|
-
function calculateShannonEntropy(str) {
|
|
1231
|
-
const freq = /* @__PURE__ */ new Map();
|
|
1232
|
-
for (const char of str) {
|
|
1233
|
-
freq.set(char, (freq.get(char) ?? 0) + 1);
|
|
1234
|
-
}
|
|
1235
|
-
let entropy = 0;
|
|
1236
|
-
const len = str.length;
|
|
1237
|
-
for (const count of freq.values()) {
|
|
1238
|
-
const p = count / len;
|
|
1239
|
-
if (p > 0) {
|
|
1240
|
-
entropy -= p * Math.log2(p);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
return entropy;
|
|
1244
|
-
}
|
|
1245
|
-
function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG) {
|
|
1246
|
-
const threats = [];
|
|
1247
|
-
if (typeof value !== "string") {
|
|
1248
|
-
if (typeof value === "object" && value !== null) {
|
|
1249
|
-
return sanitizeObject(field, value, config);
|
|
1250
|
-
}
|
|
1251
|
-
return { safe: true, threats: [] };
|
|
1252
|
-
}
|
|
1253
|
-
if (config.pathTraversal && detectPathTraversal(value)) {
|
|
1254
|
-
threats.push({
|
|
1255
|
-
type: "PATH_TRAVERSAL",
|
|
1256
|
-
field,
|
|
1257
|
-
value: truncate(value, 100),
|
|
1258
|
-
description: "Path traversal pattern detected"
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
if (config.shellInjection && detectShellInjection(value)) {
|
|
1262
|
-
threats.push({
|
|
1263
|
-
type: "SHELL_INJECTION",
|
|
1264
|
-
field,
|
|
1265
|
-
value: truncate(value, 100),
|
|
1266
|
-
description: "Shell injection pattern detected"
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
|
-
if (config.wildcardAbuse && detectWildcardAbuse(value)) {
|
|
1270
|
-
threats.push({
|
|
1271
|
-
type: "WILDCARD_ABUSE",
|
|
1272
|
-
field,
|
|
1273
|
-
value: truncate(value, 100),
|
|
1274
|
-
description: "Wildcard abuse pattern detected"
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
if (!checkLengthLimits(value, config.lengthLimit)) {
|
|
1278
|
-
threats.push({
|
|
1279
|
-
type: "LENGTH_EXCEEDED",
|
|
1280
|
-
field,
|
|
1281
|
-
value: `[${value.length} chars]`,
|
|
1282
|
-
description: `Value exceeds maximum length of ${config.lengthLimit}`
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
if (config.entropyLimit && !checkEntropyLimits(value)) {
|
|
1286
|
-
threats.push({
|
|
1287
|
-
type: "HIGH_ENTROPY",
|
|
1288
|
-
field,
|
|
1289
|
-
value: truncate(value, 100),
|
|
1290
|
-
description: "High entropy string detected - possible encoded payload"
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
if (config.ssrf && detectSSRF(value)) {
|
|
1294
|
-
threats.push({
|
|
1295
|
-
type: "SSRF",
|
|
1296
|
-
field,
|
|
1297
|
-
value: truncate(value, 100),
|
|
1298
|
-
description: "Server-side request forgery pattern detected \u2014 internal/metadata URL blocked"
|
|
1299
|
-
});
|
|
1300
|
-
}
|
|
1301
|
-
if (config.sqlInjection && detectSQLInjection(value)) {
|
|
1302
|
-
threats.push({
|
|
1303
|
-
type: "SQL_INJECTION",
|
|
1304
|
-
field,
|
|
1305
|
-
value: truncate(value, 100),
|
|
1306
|
-
description: "SQL injection pattern detected"
|
|
1307
|
-
});
|
|
1308
|
-
}
|
|
1309
|
-
return { safe: threats.length === 0, threats };
|
|
1310
|
-
}
|
|
1311
|
-
function sanitizeObject(basePath, obj, config) {
|
|
1312
|
-
const threats = [];
|
|
1313
|
-
if (Array.isArray(obj)) {
|
|
1314
|
-
for (let i = 0; i < obj.length; i++) {
|
|
1315
|
-
const result = sanitizeInput(`${basePath}[${i}]`, obj[i], config);
|
|
1316
|
-
threats.push(...result.threats);
|
|
1317
|
-
}
|
|
1318
|
-
} else {
|
|
1319
|
-
for (const [key, val] of Object.entries(obj)) {
|
|
1320
|
-
const result = sanitizeInput(`${basePath}.${key}`, val, config);
|
|
1321
|
-
threats.push(...result.threats);
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
return { safe: threats.length === 0, threats };
|
|
1325
|
-
}
|
|
1326
|
-
function truncate(str, maxLen) {
|
|
1327
|
-
return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
|
|
1328
|
-
}
|
|
1329
|
-
var DEFAULT_TOKEN_TTL_SECONDS = 30;
|
|
1330
|
-
var TOKEN_ALGORITHM = "HS256";
|
|
1331
|
-
var MIN_SECRET_LENGTH = 32;
|
|
1332
|
-
|
|
1333
|
-
// src/config.ts
|
|
1334
|
-
import { readFileSync, existsSync } from "fs";
|
|
1335
|
-
import { resolve } from "path";
|
|
1336
|
-
async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
1337
|
-
const url = `${apiUrl}/api/v1/policies/${policyId ?? "default"}`;
|
|
1338
|
-
const res = await fetch(url, {
|
|
1339
|
-
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
1340
|
-
});
|
|
1341
|
-
if (!res.ok) {
|
|
1342
|
-
const body = await res.text().catch(() => "");
|
|
1343
|
-
throw new Error(`Failed to fetch policy from cloud (${res.status}): ${body}`);
|
|
1344
|
-
}
|
|
1345
|
-
const data = await res.json();
|
|
1346
|
-
return {
|
|
1347
|
-
id: String(data.id ?? "cloud"),
|
|
1348
|
-
name: String(data.name ?? "Cloud Policy"),
|
|
1349
|
-
version: Number(data._version ?? 1),
|
|
1350
|
-
rules: data.rules ?? [],
|
|
1351
|
-
createdAt: String(data._created_at ?? ""),
|
|
1352
|
-
updatedAt: ""
|
|
1353
|
-
};
|
|
1024
|
+
const data = await res.json();
|
|
1025
|
+
return {
|
|
1026
|
+
id: String(data.id ?? "cloud"),
|
|
1027
|
+
name: String(data.name ?? "Cloud Policy"),
|
|
1028
|
+
version: Number(data._version ?? 1),
|
|
1029
|
+
rules: data.rules ?? [],
|
|
1030
|
+
createdAt: String(data._created_at ?? ""),
|
|
1031
|
+
updatedAt: ""
|
|
1032
|
+
};
|
|
1354
1033
|
}
|
|
1355
1034
|
async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
1356
1035
|
try {
|
|
@@ -1538,175 +1217,621 @@ function loadPolicy(source) {
|
|
|
1538
1217
|
`Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
|
|
1539
1218
|
);
|
|
1540
1219
|
}
|
|
1541
|
-
function parseArgs(argv) {
|
|
1542
|
-
const args = argv.slice(2);
|
|
1543
|
-
let policySource = "restricted";
|
|
1544
|
-
let name = "solongate-proxy";
|
|
1545
|
-
let verbose = false;
|
|
1546
|
-
let validateInput = true;
|
|
1547
|
-
let rateLimitPerTool;
|
|
1548
|
-
let globalRateLimit;
|
|
1549
|
-
let configFile;
|
|
1550
|
-
let apiKey;
|
|
1551
|
-
let apiUrl;
|
|
1552
|
-
let upstreamUrl;
|
|
1553
|
-
let upstreamTransport;
|
|
1554
|
-
let port;
|
|
1555
|
-
let separatorIndex = args.indexOf("--");
|
|
1556
|
-
const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
|
|
1557
|
-
const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
|
|
1558
|
-
for (let i = 0; i < flags.length; i++) {
|
|
1559
|
-
switch (flags[i]) {
|
|
1560
|
-
case "--policy":
|
|
1561
|
-
policySource = flags[++i];
|
|
1562
|
-
break;
|
|
1563
|
-
case "--name":
|
|
1564
|
-
name = flags[++i];
|
|
1565
|
-
break;
|
|
1566
|
-
case "--verbose":
|
|
1567
|
-
verbose = true;
|
|
1568
|
-
break;
|
|
1569
|
-
case "--no-input-guard":
|
|
1570
|
-
validateInput = false;
|
|
1571
|
-
break;
|
|
1572
|
-
case "--rate-limit":
|
|
1573
|
-
rateLimitPerTool = parseInt(flags[++i], 10);
|
|
1574
|
-
break;
|
|
1575
|
-
case "--global-rate-limit":
|
|
1576
|
-
globalRateLimit = parseInt(flags[++i], 10);
|
|
1577
|
-
break;
|
|
1578
|
-
case "--config":
|
|
1579
|
-
configFile = flags[++i];
|
|
1580
|
-
break;
|
|
1581
|
-
case "--api-key":
|
|
1582
|
-
apiKey = flags[++i];
|
|
1583
|
-
break;
|
|
1584
|
-
case "--api-url":
|
|
1585
|
-
apiUrl = flags[++i];
|
|
1586
|
-
break;
|
|
1587
|
-
case "--upstream-url":
|
|
1588
|
-
upstreamUrl = flags[++i];
|
|
1589
|
-
break;
|
|
1590
|
-
case "--upstream-transport":
|
|
1591
|
-
upstreamTransport = flags[++i];
|
|
1592
|
-
break;
|
|
1593
|
-
case "--port":
|
|
1594
|
-
port = parseInt(flags[++i], 10);
|
|
1595
|
-
break;
|
|
1220
|
+
function parseArgs(argv) {
|
|
1221
|
+
const args = argv.slice(2);
|
|
1222
|
+
let policySource = "restricted";
|
|
1223
|
+
let name = "solongate-proxy";
|
|
1224
|
+
let verbose = false;
|
|
1225
|
+
let validateInput = true;
|
|
1226
|
+
let rateLimitPerTool;
|
|
1227
|
+
let globalRateLimit;
|
|
1228
|
+
let configFile;
|
|
1229
|
+
let apiKey;
|
|
1230
|
+
let apiUrl;
|
|
1231
|
+
let upstreamUrl;
|
|
1232
|
+
let upstreamTransport;
|
|
1233
|
+
let port;
|
|
1234
|
+
let separatorIndex = args.indexOf("--");
|
|
1235
|
+
const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
|
|
1236
|
+
const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
|
|
1237
|
+
for (let i = 0; i < flags.length; i++) {
|
|
1238
|
+
switch (flags[i]) {
|
|
1239
|
+
case "--policy":
|
|
1240
|
+
policySource = flags[++i];
|
|
1241
|
+
break;
|
|
1242
|
+
case "--name":
|
|
1243
|
+
name = flags[++i];
|
|
1244
|
+
break;
|
|
1245
|
+
case "--verbose":
|
|
1246
|
+
verbose = true;
|
|
1247
|
+
break;
|
|
1248
|
+
case "--no-input-guard":
|
|
1249
|
+
validateInput = false;
|
|
1250
|
+
break;
|
|
1251
|
+
case "--rate-limit":
|
|
1252
|
+
rateLimitPerTool = parseInt(flags[++i], 10);
|
|
1253
|
+
break;
|
|
1254
|
+
case "--global-rate-limit":
|
|
1255
|
+
globalRateLimit = parseInt(flags[++i], 10);
|
|
1256
|
+
break;
|
|
1257
|
+
case "--config":
|
|
1258
|
+
configFile = flags[++i];
|
|
1259
|
+
break;
|
|
1260
|
+
case "--api-key":
|
|
1261
|
+
apiKey = flags[++i];
|
|
1262
|
+
break;
|
|
1263
|
+
case "--api-url":
|
|
1264
|
+
apiUrl = flags[++i];
|
|
1265
|
+
break;
|
|
1266
|
+
case "--upstream-url":
|
|
1267
|
+
upstreamUrl = flags[++i];
|
|
1268
|
+
break;
|
|
1269
|
+
case "--upstream-transport":
|
|
1270
|
+
upstreamTransport = flags[++i];
|
|
1271
|
+
break;
|
|
1272
|
+
case "--port":
|
|
1273
|
+
port = parseInt(flags[++i], 10);
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (!apiKey) {
|
|
1278
|
+
const envKey = process.env.SOLONGATE_API_KEY;
|
|
1279
|
+
if (envKey) {
|
|
1280
|
+
apiKey = envKey;
|
|
1281
|
+
} else {
|
|
1282
|
+
throw new Error(
|
|
1283
|
+
"A valid SolonGate API key is required.\n\nUsage: solongate-proxy --api-key sg_live_xxx -- <command>\n or: set SOLONGATE_API_KEY=sg_live_xxx\n\nGet your API key at https://solongate.com\n"
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
"Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
if (configFile) {
|
|
1293
|
+
const filePath = resolve(configFile);
|
|
1294
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1295
|
+
const fileConfig = JSON.parse(content);
|
|
1296
|
+
if (!fileConfig.upstream) {
|
|
1297
|
+
throw new Error('Config file must include "upstream" with at least "command" or "url"');
|
|
1298
|
+
}
|
|
1299
|
+
if (fileConfig.upstream.url && detectSSRF(fileConfig.upstream.url)) {
|
|
1300
|
+
throw new Error(
|
|
1301
|
+
`Upstream URL blocked: "${fileConfig.upstream.url}" points to an internal or private network address.`
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
upstream: fileConfig.upstream,
|
|
1306
|
+
policy: loadPolicy(fileConfig.policy ?? policySource),
|
|
1307
|
+
name: fileConfig.name ?? name,
|
|
1308
|
+
verbose: fileConfig.verbose ?? verbose,
|
|
1309
|
+
validateInput: fileConfig.validateInput ?? validateInput,
|
|
1310
|
+
rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
|
|
1311
|
+
globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
|
|
1312
|
+
apiKey: apiKey ?? fileConfig.apiKey,
|
|
1313
|
+
apiUrl: apiUrl ?? fileConfig.apiUrl,
|
|
1314
|
+
port: port ?? fileConfig.port
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
if (upstreamUrl) {
|
|
1318
|
+
if (detectSSRF(upstreamUrl)) {
|
|
1319
|
+
throw new Error(
|
|
1320
|
+
`Upstream URL blocked: "${upstreamUrl}" points to an internal or private network address.
|
|
1321
|
+
SSRF protection prevents connecting to localhost, private IPs, or cloud metadata endpoints.`
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
|
|
1325
|
+
return {
|
|
1326
|
+
upstream: {
|
|
1327
|
+
transport,
|
|
1328
|
+
command: "",
|
|
1329
|
+
// not used for URL-based transports
|
|
1330
|
+
url: upstreamUrl
|
|
1331
|
+
},
|
|
1332
|
+
policy: loadPolicy(policySource),
|
|
1333
|
+
name,
|
|
1334
|
+
verbose,
|
|
1335
|
+
validateInput,
|
|
1336
|
+
rateLimitPerTool,
|
|
1337
|
+
globalRateLimit,
|
|
1338
|
+
apiKey,
|
|
1339
|
+
apiUrl,
|
|
1340
|
+
port
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
if (upstreamArgs.length === 0) {
|
|
1344
|
+
throw new Error(
|
|
1345
|
+
"No upstream server command provided.\n\nUsage: solongate-proxy [options] -- <command> [args...]\n\nExamples:\n solongate-proxy -- node my-server.js\n solongate-proxy --policy restricted -- npx @openclaw/server\n solongate-proxy --upstream-url http://localhost:3001/mcp\n solongate-proxy --config solongate.json\n"
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
const [command, ...commandArgs] = upstreamArgs;
|
|
1349
|
+
return {
|
|
1350
|
+
upstream: {
|
|
1351
|
+
transport: upstreamTransport ?? "stdio",
|
|
1352
|
+
command,
|
|
1353
|
+
args: commandArgs,
|
|
1354
|
+
env: { ...process.env }
|
|
1355
|
+
},
|
|
1356
|
+
policy: loadPolicy(policySource),
|
|
1357
|
+
name,
|
|
1358
|
+
verbose,
|
|
1359
|
+
validateInput,
|
|
1360
|
+
rateLimitPerTool,
|
|
1361
|
+
globalRateLimit,
|
|
1362
|
+
apiKey,
|
|
1363
|
+
apiUrl,
|
|
1364
|
+
port
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/proxy.ts
|
|
1369
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1370
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1371
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
1372
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1373
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1374
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1375
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1376
|
+
import {
|
|
1377
|
+
ListToolsRequestSchema,
|
|
1378
|
+
CallToolRequestSchema,
|
|
1379
|
+
ListResourcesRequestSchema,
|
|
1380
|
+
ListPromptsRequestSchema,
|
|
1381
|
+
GetPromptRequestSchema,
|
|
1382
|
+
ReadResourceRequestSchema,
|
|
1383
|
+
ListResourceTemplatesRequestSchema
|
|
1384
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1385
|
+
import { createServer as createHttpServer } from "http";
|
|
1386
|
+
|
|
1387
|
+
// ../sdk-ts/dist/index.js
|
|
1388
|
+
import { z as z2 } from "zod";
|
|
1389
|
+
import { createHash, randomUUID, createHmac } from "crypto";
|
|
1390
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1391
|
+
var SolonGateError = class extends Error {
|
|
1392
|
+
code;
|
|
1393
|
+
timestamp;
|
|
1394
|
+
details;
|
|
1395
|
+
constructor(message, code, details = {}) {
|
|
1396
|
+
super(message);
|
|
1397
|
+
this.name = "SolonGateError";
|
|
1398
|
+
this.code = code;
|
|
1399
|
+
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1400
|
+
this.details = Object.freeze({ ...details });
|
|
1401
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Serializable representation for logging and API responses.
|
|
1405
|
+
* Never includes stack traces (information leakage prevention).
|
|
1406
|
+
*/
|
|
1407
|
+
toJSON() {
|
|
1408
|
+
return {
|
|
1409
|
+
name: this.name,
|
|
1410
|
+
code: this.code,
|
|
1411
|
+
message: this.message,
|
|
1412
|
+
timestamp: this.timestamp,
|
|
1413
|
+
details: this.details
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
var PolicyDeniedError = class extends SolonGateError {
|
|
1418
|
+
constructor(toolName, reason, details = {}) {
|
|
1419
|
+
super(
|
|
1420
|
+
`Policy denied execution of tool "${toolName}": ${reason}`,
|
|
1421
|
+
"POLICY_DENIED",
|
|
1422
|
+
{ toolName, reason, ...details }
|
|
1423
|
+
);
|
|
1424
|
+
this.name = "PolicyDeniedError";
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
var SchemaValidationError = class extends SolonGateError {
|
|
1428
|
+
constructor(toolName, validationErrors) {
|
|
1429
|
+
super(
|
|
1430
|
+
`Schema validation failed for tool "${toolName}": ${validationErrors.join("; ")}`,
|
|
1431
|
+
"SCHEMA_VALIDATION_FAILED",
|
|
1432
|
+
{ toolName, validationErrors }
|
|
1433
|
+
);
|
|
1434
|
+
this.name = "SchemaValidationError";
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
var RateLimitError = class extends SolonGateError {
|
|
1438
|
+
constructor(toolName, limitPerMinute) {
|
|
1439
|
+
super(
|
|
1440
|
+
`Rate limit exceeded for tool "${toolName}": max ${limitPerMinute}/min`,
|
|
1441
|
+
"RATE_LIMIT_EXCEEDED",
|
|
1442
|
+
{ toolName, limitPerMinute }
|
|
1443
|
+
);
|
|
1444
|
+
this.name = "RateLimitError";
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
var TrustLevel = {
|
|
1448
|
+
UNTRUSTED: "UNTRUSTED",
|
|
1449
|
+
VERIFIED: "VERIFIED",
|
|
1450
|
+
TRUSTED: "TRUSTED"
|
|
1451
|
+
};
|
|
1452
|
+
var Permission2 = {
|
|
1453
|
+
READ: "READ",
|
|
1454
|
+
WRITE: "WRITE",
|
|
1455
|
+
EXECUTE: "EXECUTE"
|
|
1456
|
+
};
|
|
1457
|
+
z2.enum(["READ", "WRITE", "EXECUTE"]);
|
|
1458
|
+
Object.freeze(
|
|
1459
|
+
/* @__PURE__ */ new Set()
|
|
1460
|
+
);
|
|
1461
|
+
Object.freeze(
|
|
1462
|
+
/* @__PURE__ */ new Set([Permission2.READ])
|
|
1463
|
+
);
|
|
1464
|
+
var PolicyEffect = {
|
|
1465
|
+
ALLOW: "ALLOW",
|
|
1466
|
+
DENY: "DENY"
|
|
1467
|
+
};
|
|
1468
|
+
var PolicyRuleSchema2 = z2.object({
|
|
1469
|
+
id: z2.string().min(1).max(256),
|
|
1470
|
+
description: z2.string().max(1024),
|
|
1471
|
+
effect: z2.enum(["ALLOW", "DENY"]),
|
|
1472
|
+
priority: z2.number().int().min(0).max(1e4).default(1e3),
|
|
1473
|
+
toolPattern: z2.string().min(1).max(512),
|
|
1474
|
+
permission: z2.enum(["READ", "WRITE", "EXECUTE"]),
|
|
1475
|
+
minimumTrustLevel: z2.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
|
|
1476
|
+
argumentConstraints: z2.record(z2.unknown()).optional(),
|
|
1477
|
+
pathConstraints: z2.object({
|
|
1478
|
+
allowed: z2.array(z2.string()).optional(),
|
|
1479
|
+
denied: z2.array(z2.string()).optional(),
|
|
1480
|
+
rootDirectory: z2.string().optional(),
|
|
1481
|
+
allowSymlinks: z2.boolean().optional()
|
|
1482
|
+
}).optional(),
|
|
1483
|
+
enabled: z2.boolean().default(true),
|
|
1484
|
+
createdAt: z2.string().datetime(),
|
|
1485
|
+
updatedAt: z2.string().datetime()
|
|
1486
|
+
});
|
|
1487
|
+
var PolicySetSchema2 = z2.object({
|
|
1488
|
+
id: z2.string().min(1).max(256),
|
|
1489
|
+
name: z2.string().min(1).max(256),
|
|
1490
|
+
description: z2.string().max(2048),
|
|
1491
|
+
version: z2.number().int().min(0),
|
|
1492
|
+
rules: z2.array(PolicyRuleSchema2),
|
|
1493
|
+
createdAt: z2.string().datetime(),
|
|
1494
|
+
updatedAt: z2.string().datetime()
|
|
1495
|
+
});
|
|
1496
|
+
function createSecurityContext(params) {
|
|
1497
|
+
return {
|
|
1498
|
+
trustLevel: "UNTRUSTED",
|
|
1499
|
+
grantedPermissions: /* @__PURE__ */ new Set(),
|
|
1500
|
+
sessionId: null,
|
|
1501
|
+
metadata: {},
|
|
1502
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1503
|
+
...params
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
var DEFAULT_POLICY_EFFECT = "DENY";
|
|
1507
|
+
var MAX_RULES_PER_POLICY_SET = 1e3;
|
|
1508
|
+
var POLICY_EVALUATION_TIMEOUT_MS = 100;
|
|
1509
|
+
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1510
|
+
var RATE_LIMIT_MAX_ENTRIES = 1e4;
|
|
1511
|
+
var UNSAFE_CONFIGURATION_WARNINGS = {
|
|
1512
|
+
WILDCARD_ALLOW: "Wildcard ALLOW rules grant permission to ALL tools. This bypasses the default-deny model.",
|
|
1513
|
+
TRUSTED_LEVEL_EXTERNAL: "Setting trust level to TRUSTED for external requests bypasses all security checks.",
|
|
1514
|
+
EXECUTE_WITHOUT_REVIEW: "EXECUTE permission allows tools to perform arbitrary actions. Review carefully.",
|
|
1515
|
+
RATE_LIMIT_ZERO: "A rate limit of 0 means unlimited calls. This removes protection against runaway loops.",
|
|
1516
|
+
DISABLED_VALIDATION: "Disabling schema validation removes input sanitization protections."
|
|
1517
|
+
};
|
|
1518
|
+
function createDeniedToolResult(reason) {
|
|
1519
|
+
return {
|
|
1520
|
+
content: [
|
|
1521
|
+
{
|
|
1522
|
+
type: "text",
|
|
1523
|
+
text: JSON.stringify({
|
|
1524
|
+
error: "POLICY_DENIED",
|
|
1525
|
+
message: reason,
|
|
1526
|
+
hint: "This tool call was blocked by SolonGate security policy. Check your policy configuration."
|
|
1527
|
+
})
|
|
1528
|
+
}
|
|
1529
|
+
],
|
|
1530
|
+
isError: true
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
|
|
1534
|
+
pathTraversal: true,
|
|
1535
|
+
shellInjection: true,
|
|
1536
|
+
wildcardAbuse: true,
|
|
1537
|
+
lengthLimit: 4096,
|
|
1538
|
+
entropyLimit: true,
|
|
1539
|
+
ssrf: true,
|
|
1540
|
+
sqlInjection: true
|
|
1541
|
+
});
|
|
1542
|
+
var PATH_TRAVERSAL_PATTERNS = [
|
|
1543
|
+
/\.\.\//,
|
|
1544
|
+
// ../
|
|
1545
|
+
/\.\.\\/,
|
|
1546
|
+
// ..\
|
|
1547
|
+
/%2e%2e/i,
|
|
1548
|
+
// URL-encoded ..
|
|
1549
|
+
/%2e\./i,
|
|
1550
|
+
// partial URL-encoded
|
|
1551
|
+
/\.%2e/i,
|
|
1552
|
+
// partial URL-encoded
|
|
1553
|
+
/%252e%252e/i,
|
|
1554
|
+
// double URL-encoded
|
|
1555
|
+
/\.\.\0/
|
|
1556
|
+
// null byte variant
|
|
1557
|
+
];
|
|
1558
|
+
var SENSITIVE_PATHS = [
|
|
1559
|
+
/\/etc\/passwd/i,
|
|
1560
|
+
/\/etc\/shadow/i,
|
|
1561
|
+
/\/proc\//i,
|
|
1562
|
+
/\/dev\//i,
|
|
1563
|
+
/c:\\windows\\system32/i,
|
|
1564
|
+
/c:\\windows\\syswow64/i,
|
|
1565
|
+
/\/root\//i,
|
|
1566
|
+
/~\//,
|
|
1567
|
+
/\.env(\.|$)/i,
|
|
1568
|
+
// .env, .env.local, .env.production
|
|
1569
|
+
/\.aws\/credentials/i,
|
|
1570
|
+
// AWS credentials
|
|
1571
|
+
/\.ssh\/id_/i,
|
|
1572
|
+
// SSH keys
|
|
1573
|
+
/\.kube\/config/i,
|
|
1574
|
+
// Kubernetes config
|
|
1575
|
+
/wp-config\.php/i,
|
|
1576
|
+
// WordPress config
|
|
1577
|
+
/\.git\/config/i,
|
|
1578
|
+
// Git config
|
|
1579
|
+
/\.npmrc/i,
|
|
1580
|
+
// npm credentials
|
|
1581
|
+
/\.pypirc/i
|
|
1582
|
+
// PyPI credentials
|
|
1583
|
+
];
|
|
1584
|
+
function detectPathTraversal(value) {
|
|
1585
|
+
for (const pattern of PATH_TRAVERSAL_PATTERNS) {
|
|
1586
|
+
if (pattern.test(value)) return true;
|
|
1587
|
+
}
|
|
1588
|
+
for (const pattern of SENSITIVE_PATHS) {
|
|
1589
|
+
if (pattern.test(value)) return true;
|
|
1590
|
+
}
|
|
1591
|
+
return false;
|
|
1592
|
+
}
|
|
1593
|
+
var SHELL_INJECTION_PATTERNS = [
|
|
1594
|
+
/[;|&`]/,
|
|
1595
|
+
// Command separators and backtick execution
|
|
1596
|
+
/\$\(/,
|
|
1597
|
+
// Command substitution $(...)
|
|
1598
|
+
/\$\{/,
|
|
1599
|
+
// Variable expansion ${...}
|
|
1600
|
+
/>\s*/,
|
|
1601
|
+
// Output redirect
|
|
1602
|
+
/<\s*/,
|
|
1603
|
+
// Input redirect
|
|
1604
|
+
/&&/,
|
|
1605
|
+
// AND chaining
|
|
1606
|
+
/\|\|/,
|
|
1607
|
+
// OR chaining
|
|
1608
|
+
/\beval\b/i,
|
|
1609
|
+
// eval command
|
|
1610
|
+
/\bexec\b/i,
|
|
1611
|
+
// exec command
|
|
1612
|
+
/\bsystem\b/i,
|
|
1613
|
+
// system call
|
|
1614
|
+
/%0a/i,
|
|
1615
|
+
// URL-encoded newline
|
|
1616
|
+
/%0d/i,
|
|
1617
|
+
// URL-encoded carriage return
|
|
1618
|
+
/%09/i,
|
|
1619
|
+
// URL-encoded tab
|
|
1620
|
+
/\r\n/,
|
|
1621
|
+
// CRLF injection
|
|
1622
|
+
/\n/
|
|
1623
|
+
// Newline (command separator on Unix)
|
|
1624
|
+
];
|
|
1625
|
+
function detectShellInjection(value) {
|
|
1626
|
+
for (const pattern of SHELL_INJECTION_PATTERNS) {
|
|
1627
|
+
if (pattern.test(value)) return true;
|
|
1628
|
+
}
|
|
1629
|
+
return false;
|
|
1630
|
+
}
|
|
1631
|
+
var MAX_WILDCARDS_PER_VALUE = 3;
|
|
1632
|
+
function detectWildcardAbuse(value) {
|
|
1633
|
+
if (value.includes("**")) return true;
|
|
1634
|
+
const wildcardCount = (value.match(/\*/g) || []).length;
|
|
1635
|
+
if (wildcardCount > MAX_WILDCARDS_PER_VALUE) return true;
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
var SSRF_PATTERNS2 = [
|
|
1639
|
+
/^https?:\/\/localhost\b/i,
|
|
1640
|
+
/^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
|
|
1641
|
+
/^https?:\/\/0\.0\.0\.0/,
|
|
1642
|
+
/^https?:\/\/\[::1\]/,
|
|
1643
|
+
// IPv6 loopback
|
|
1644
|
+
/^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
|
|
1645
|
+
// 10.x.x.x
|
|
1646
|
+
/^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
|
|
1647
|
+
// 172.16-31.x.x
|
|
1648
|
+
/^https?:\/\/192\.168\./,
|
|
1649
|
+
// 192.168.x.x
|
|
1650
|
+
/^https?:\/\/169\.254\./,
|
|
1651
|
+
// Link-local / AWS metadata
|
|
1652
|
+
/metadata\.google\.internal/i,
|
|
1653
|
+
// GCP metadata
|
|
1654
|
+
/^https?:\/\/metadata\b/i,
|
|
1655
|
+
// Generic metadata endpoint
|
|
1656
|
+
// IPv6 bypass patterns
|
|
1657
|
+
/^https?:\/\/\[fe80:/i,
|
|
1658
|
+
// IPv6 link-local
|
|
1659
|
+
/^https?:\/\/\[fc00:/i,
|
|
1660
|
+
// IPv6 unique local
|
|
1661
|
+
/^https?:\/\/\[fd[0-9a-f]{2}:/i,
|
|
1662
|
+
// IPv6 unique local (fd00::/8)
|
|
1663
|
+
/^https?:\/\/\[::ffff:127\./i,
|
|
1664
|
+
// IPv4-mapped IPv6 loopback
|
|
1665
|
+
/^https?:\/\/\[::ffff:10\./i,
|
|
1666
|
+
// IPv4-mapped IPv6 private
|
|
1667
|
+
/^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
|
|
1668
|
+
// IPv4-mapped IPv6 private
|
|
1669
|
+
/^https?:\/\/\[::ffff:192\.168\./i,
|
|
1670
|
+
// IPv4-mapped IPv6 private
|
|
1671
|
+
/^https?:\/\/\[::ffff:169\.254\./i,
|
|
1672
|
+
// IPv4-mapped IPv6 link-local
|
|
1673
|
+
// Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
|
|
1674
|
+
/^https?:\/\/0x[0-9a-f]+\b/i,
|
|
1675
|
+
// Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
|
|
1676
|
+
/^https?:\/\/0[0-7]{1,3}\./
|
|
1677
|
+
];
|
|
1678
|
+
function detectDecimalIP2(value) {
|
|
1679
|
+
const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
|
|
1680
|
+
if (!match || !match[1]) return false;
|
|
1681
|
+
const decimal = parseInt(match[1], 10);
|
|
1682
|
+
if (isNaN(decimal) || decimal > 4294967295) return false;
|
|
1683
|
+
return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
|
|
1684
|
+
decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
|
|
1685
|
+
decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
|
|
1686
|
+
decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
|
|
1687
|
+
decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
|
|
1688
|
+
decimal === 0;
|
|
1689
|
+
}
|
|
1690
|
+
function detectSSRF2(value) {
|
|
1691
|
+
for (const pattern of SSRF_PATTERNS2) {
|
|
1692
|
+
if (pattern.test(value)) return true;
|
|
1693
|
+
}
|
|
1694
|
+
if (detectDecimalIP2(value)) return true;
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
var SQL_INJECTION_PATTERNS = [
|
|
1698
|
+
/'\s{0,20}(OR|AND)\s{0,20}'.{0,200}'/i,
|
|
1699
|
+
// ' OR '1'='1 — bounded to prevent ReDoS
|
|
1700
|
+
/'\s{0,10};\s{0,10}(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|EXEC)/i,
|
|
1701
|
+
// '; DROP TABLE
|
|
1702
|
+
/UNION\s+(ALL\s+)?SELECT/i,
|
|
1703
|
+
// UNION SELECT
|
|
1704
|
+
/--\s*$/m,
|
|
1705
|
+
// SQL comment at end of line
|
|
1706
|
+
/\/\*.{0,500}?\*\//,
|
|
1707
|
+
// SQL block comment — bounded + non-greedy
|
|
1708
|
+
/\bSLEEP\s*\(/i,
|
|
1709
|
+
// Time-based injection
|
|
1710
|
+
/\bBENCHMARK\s*\(/i,
|
|
1711
|
+
// MySQL benchmark
|
|
1712
|
+
/\bWAITFOR\s+DELAY/i,
|
|
1713
|
+
// MSSQL delay
|
|
1714
|
+
/\b(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)\b/i
|
|
1715
|
+
// File operations
|
|
1716
|
+
];
|
|
1717
|
+
function detectSQLInjection(value) {
|
|
1718
|
+
for (const pattern of SQL_INJECTION_PATTERNS) {
|
|
1719
|
+
if (pattern.test(value)) return true;
|
|
1720
|
+
}
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1723
|
+
function checkLengthLimits(value, maxLength = 4096) {
|
|
1724
|
+
return value.length <= maxLength;
|
|
1725
|
+
}
|
|
1726
|
+
var ENTROPY_THRESHOLD = 4.5;
|
|
1727
|
+
var MIN_LENGTH_FOR_ENTROPY_CHECK = 32;
|
|
1728
|
+
function checkEntropyLimits(value) {
|
|
1729
|
+
if (value.length < MIN_LENGTH_FOR_ENTROPY_CHECK) return true;
|
|
1730
|
+
const entropy = calculateShannonEntropy(value);
|
|
1731
|
+
return entropy <= ENTROPY_THRESHOLD;
|
|
1732
|
+
}
|
|
1733
|
+
function calculateShannonEntropy(str) {
|
|
1734
|
+
const freq = /* @__PURE__ */ new Map();
|
|
1735
|
+
for (const char of str) {
|
|
1736
|
+
freq.set(char, (freq.get(char) ?? 0) + 1);
|
|
1737
|
+
}
|
|
1738
|
+
let entropy = 0;
|
|
1739
|
+
const len = str.length;
|
|
1740
|
+
for (const count of freq.values()) {
|
|
1741
|
+
const p = count / len;
|
|
1742
|
+
if (p > 0) {
|
|
1743
|
+
entropy -= p * Math.log2(p);
|
|
1596
1744
|
}
|
|
1597
1745
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
);
|
|
1746
|
+
return entropy;
|
|
1747
|
+
}
|
|
1748
|
+
function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG2) {
|
|
1749
|
+
const threats = [];
|
|
1750
|
+
if (typeof value !== "string") {
|
|
1751
|
+
if (typeof value === "object" && value !== null) {
|
|
1752
|
+
return sanitizeObject(field, value, config);
|
|
1606
1753
|
}
|
|
1754
|
+
return { safe: true, threats: [] };
|
|
1607
1755
|
}
|
|
1608
|
-
if (
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1756
|
+
if (config.pathTraversal && detectPathTraversal(value)) {
|
|
1757
|
+
threats.push({
|
|
1758
|
+
type: "PATH_TRAVERSAL",
|
|
1759
|
+
field,
|
|
1760
|
+
value: truncate(value, 100),
|
|
1761
|
+
description: "Path traversal pattern detected"
|
|
1762
|
+
});
|
|
1612
1763
|
}
|
|
1613
|
-
if (
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
}
|
|
1620
|
-
if (fileConfig.upstream.url && detectSSRF(fileConfig.upstream.url)) {
|
|
1621
|
-
throw new Error(
|
|
1622
|
-
`Upstream URL blocked: "${fileConfig.upstream.url}" points to an internal or private network address.`
|
|
1623
|
-
);
|
|
1624
|
-
}
|
|
1625
|
-
return {
|
|
1626
|
-
upstream: fileConfig.upstream,
|
|
1627
|
-
policy: loadPolicy(fileConfig.policy ?? policySource),
|
|
1628
|
-
name: fileConfig.name ?? name,
|
|
1629
|
-
verbose: fileConfig.verbose ?? verbose,
|
|
1630
|
-
validateInput: fileConfig.validateInput ?? validateInput,
|
|
1631
|
-
rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
|
|
1632
|
-
globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
|
|
1633
|
-
apiKey: apiKey ?? fileConfig.apiKey,
|
|
1634
|
-
apiUrl: apiUrl ?? fileConfig.apiUrl,
|
|
1635
|
-
port: port ?? fileConfig.port
|
|
1636
|
-
};
|
|
1764
|
+
if (config.shellInjection && detectShellInjection(value)) {
|
|
1765
|
+
threats.push({
|
|
1766
|
+
type: "SHELL_INJECTION",
|
|
1767
|
+
field,
|
|
1768
|
+
value: truncate(value, 100),
|
|
1769
|
+
description: "Shell injection pattern detected"
|
|
1770
|
+
});
|
|
1637
1771
|
}
|
|
1638
|
-
if (
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
}
|
|
1645
|
-
const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
|
|
1646
|
-
return {
|
|
1647
|
-
upstream: {
|
|
1648
|
-
transport,
|
|
1649
|
-
command: "",
|
|
1650
|
-
// not used for URL-based transports
|
|
1651
|
-
url: upstreamUrl
|
|
1652
|
-
},
|
|
1653
|
-
policy: loadPolicy(policySource),
|
|
1654
|
-
name,
|
|
1655
|
-
verbose,
|
|
1656
|
-
validateInput,
|
|
1657
|
-
rateLimitPerTool,
|
|
1658
|
-
globalRateLimit,
|
|
1659
|
-
apiKey,
|
|
1660
|
-
apiUrl,
|
|
1661
|
-
port
|
|
1662
|
-
};
|
|
1772
|
+
if (config.wildcardAbuse && detectWildcardAbuse(value)) {
|
|
1773
|
+
threats.push({
|
|
1774
|
+
type: "WILDCARD_ABUSE",
|
|
1775
|
+
field,
|
|
1776
|
+
value: truncate(value, 100),
|
|
1777
|
+
description: "Wildcard abuse pattern detected"
|
|
1778
|
+
});
|
|
1663
1779
|
}
|
|
1664
|
-
if (
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1780
|
+
if (!checkLengthLimits(value, config.lengthLimit)) {
|
|
1781
|
+
threats.push({
|
|
1782
|
+
type: "LENGTH_EXCEEDED",
|
|
1783
|
+
field,
|
|
1784
|
+
value: `[${value.length} chars]`,
|
|
1785
|
+
description: `Value exceeds maximum length of ${config.lengthLimit}`
|
|
1786
|
+
});
|
|
1668
1787
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1788
|
+
if (config.entropyLimit && !checkEntropyLimits(value)) {
|
|
1789
|
+
threats.push({
|
|
1790
|
+
type: "HIGH_ENTROPY",
|
|
1791
|
+
field,
|
|
1792
|
+
value: truncate(value, 100),
|
|
1793
|
+
description: "High entropy string detected - possible encoded payload"
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
if (config.ssrf && detectSSRF2(value)) {
|
|
1797
|
+
threats.push({
|
|
1798
|
+
type: "SSRF",
|
|
1799
|
+
field,
|
|
1800
|
+
value: truncate(value, 100),
|
|
1801
|
+
description: "Server-side request forgery pattern detected \u2014 internal/metadata URL blocked"
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
if (config.sqlInjection && detectSQLInjection(value)) {
|
|
1805
|
+
threats.push({
|
|
1806
|
+
type: "SQL_INJECTION",
|
|
1807
|
+
field,
|
|
1808
|
+
value: truncate(value, 100),
|
|
1809
|
+
description: "SQL injection pattern detected"
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
return { safe: threats.length === 0, threats };
|
|
1687
1813
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
import { createHash } from "crypto";
|
|
1814
|
+
function sanitizeObject(basePath, obj, config) {
|
|
1815
|
+
const threats = [];
|
|
1816
|
+
if (Array.isArray(obj)) {
|
|
1817
|
+
for (let i = 0; i < obj.length; i++) {
|
|
1818
|
+
const result = sanitizeInput(`${basePath}[${i}]`, obj[i], config);
|
|
1819
|
+
threats.push(...result.threats);
|
|
1820
|
+
}
|
|
1821
|
+
} else {
|
|
1822
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
1823
|
+
const result = sanitizeInput(`${basePath}.${key}`, val, config);
|
|
1824
|
+
threats.push(...result.threats);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
return { safe: threats.length === 0, threats };
|
|
1828
|
+
}
|
|
1829
|
+
function truncate(str, maxLen) {
|
|
1830
|
+
return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
|
|
1831
|
+
}
|
|
1832
|
+
var DEFAULT_TOKEN_TTL_SECONDS = 30;
|
|
1833
|
+
var TOKEN_ALGORITHM = "HS256";
|
|
1834
|
+
var MIN_SECRET_LENGTH = 32;
|
|
1710
1835
|
function normalizePath(path) {
|
|
1711
1836
|
let normalized = path.replace(/\\/g, "/");
|
|
1712
1837
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
@@ -1945,7 +2070,7 @@ function evaluatePolicy(policySet, request) {
|
|
|
1945
2070
|
function validatePolicyRule(input) {
|
|
1946
2071
|
const errors = [];
|
|
1947
2072
|
const warnings = [];
|
|
1948
|
-
const result =
|
|
2073
|
+
const result = PolicyRuleSchema2.safeParse(input);
|
|
1949
2074
|
if (!result.success) {
|
|
1950
2075
|
return {
|
|
1951
2076
|
valid: false,
|
|
@@ -1970,7 +2095,7 @@ function validatePolicyRule(input) {
|
|
|
1970
2095
|
function validatePolicySet(input) {
|
|
1971
2096
|
const errors = [];
|
|
1972
2097
|
const warnings = [];
|
|
1973
|
-
const result =
|
|
2098
|
+
const result = PolicySetSchema2.safeParse(input);
|
|
1974
2099
|
if (!result.success) {
|
|
1975
2100
|
return {
|
|
1976
2101
|
valid: false,
|
|
@@ -2064,7 +2189,7 @@ function createDefaultDenyPolicySet() {
|
|
|
2064
2189
|
effect: PolicyEffect.DENY,
|
|
2065
2190
|
priority: 1e4,
|
|
2066
2191
|
toolPattern: "*",
|
|
2067
|
-
permission:
|
|
2192
|
+
permission: Permission2.EXECUTE,
|
|
2068
2193
|
minimumTrustLevel: TrustLevel.UNTRUSTED,
|
|
2069
2194
|
enabled: true,
|
|
2070
2195
|
createdAt: now,
|
|
@@ -2076,7 +2201,7 @@ function createDefaultDenyPolicySet() {
|
|
|
2076
2201
|
effect: PolicyEffect.DENY,
|
|
2077
2202
|
priority: 1e4,
|
|
2078
2203
|
toolPattern: "*",
|
|
2079
|
-
permission:
|
|
2204
|
+
permission: Permission2.WRITE,
|
|
2080
2205
|
minimumTrustLevel: TrustLevel.UNTRUSTED,
|
|
2081
2206
|
enabled: true,
|
|
2082
2207
|
createdAt: now,
|
|
@@ -2088,7 +2213,7 @@ function createDefaultDenyPolicySet() {
|
|
|
2088
2213
|
effect: PolicyEffect.DENY,
|
|
2089
2214
|
priority: 1e4,
|
|
2090
2215
|
toolPattern: "*",
|
|
2091
|
-
permission:
|
|
2216
|
+
permission: Permission2.READ,
|
|
2092
2217
|
minimumTrustLevel: TrustLevel.UNTRUSTED,
|
|
2093
2218
|
enabled: true,
|
|
2094
2219
|
createdAt: now,
|
|
@@ -2258,10 +2383,6 @@ var PolicyStore = class {
|
|
|
2258
2383
|
return createHash("sha256").update(serialized).digest("hex");
|
|
2259
2384
|
}
|
|
2260
2385
|
};
|
|
2261
|
-
|
|
2262
|
-
// ../sdk-ts/dist/index.js
|
|
2263
|
-
import { randomUUID, createHmac } from "crypto";
|
|
2264
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2265
2386
|
var DEFAULT_CONFIG = Object.freeze({
|
|
2266
2387
|
validateSchemas: true,
|
|
2267
2388
|
enableLogging: true,
|
|
@@ -2271,7 +2392,7 @@ var DEFAULT_CONFIG = Object.freeze({
|
|
|
2271
2392
|
globalRateLimitPerMinute: 600,
|
|
2272
2393
|
rateLimitPerTool: 60,
|
|
2273
2394
|
tokenTtlSeconds: 30,
|
|
2274
|
-
inputGuardConfig:
|
|
2395
|
+
inputGuardConfig: DEFAULT_INPUT_GUARD_CONFIG2,
|
|
2275
2396
|
enableVersionedPolicies: true
|
|
2276
2397
|
});
|
|
2277
2398
|
function resolveConfig(userConfig) {
|
|
@@ -2304,7 +2425,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
2304
2425
|
toolName: params.name,
|
|
2305
2426
|
serverName: "default",
|
|
2306
2427
|
arguments: params.arguments ?? {},
|
|
2307
|
-
requiredPermission:
|
|
2428
|
+
requiredPermission: Permission2.EXECUTE,
|
|
2308
2429
|
timestamp
|
|
2309
2430
|
};
|
|
2310
2431
|
if (options.rateLimiter) {
|
|
@@ -2343,7 +2464,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
2343
2464
|
}
|
|
2344
2465
|
}
|
|
2345
2466
|
if (options.validateSchemas && params.arguments) {
|
|
2346
|
-
const guardConfig = options.inputGuardConfig ??
|
|
2467
|
+
const guardConfig = options.inputGuardConfig ?? DEFAULT_INPUT_GUARD_CONFIG2;
|
|
2347
2468
|
const sanitization = sanitizeInput("arguments", params.arguments, guardConfig);
|
|
2348
2469
|
if (!sanitization.safe) {
|
|
2349
2470
|
const threatDescriptions = sanitization.threats.map(
|
|
@@ -2376,7 +2497,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
2376
2497
|
if (options.tokenIssuer) {
|
|
2377
2498
|
capabilityToken = options.tokenIssuer.issue(
|
|
2378
2499
|
requestId,
|
|
2379
|
-
[
|
|
2500
|
+
[Permission2.EXECUTE],
|
|
2380
2501
|
[params.name]
|
|
2381
2502
|
);
|
|
2382
2503
|
}
|