@solongate/proxy 0.7.0 → 0.8.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/create.js +3 -3
- package/dist/index.js +775 -328
- package/dist/init.js +9 -14
- package/package.json +1 -1
package/dist/create.js
CHANGED
|
@@ -68,7 +68,7 @@ function parseCreateArgs(argv) {
|
|
|
68
68
|
const args = argv.slice(2);
|
|
69
69
|
const opts = {
|
|
70
70
|
name: "",
|
|
71
|
-
policy: "
|
|
71
|
+
policy: "",
|
|
72
72
|
noInstall: false
|
|
73
73
|
};
|
|
74
74
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -111,13 +111,13 @@ USAGE
|
|
|
111
111
|
npx @solongate/proxy create <name> [options]
|
|
112
112
|
|
|
113
113
|
OPTIONS
|
|
114
|
-
--policy <
|
|
114
|
+
--policy <file> Policy JSON file (default: cloud-managed)
|
|
115
115
|
--no-install Skip dependency installation
|
|
116
116
|
-h, --help Show this help message
|
|
117
117
|
|
|
118
118
|
EXAMPLES
|
|
119
119
|
npx @solongate/proxy create my-server
|
|
120
|
-
npx @solongate/proxy create db-tools --policy
|
|
120
|
+
npx @solongate/proxy create db-tools --policy ./policy.json
|
|
121
121
|
`);
|
|
122
122
|
}
|
|
123
123
|
function createProject(dir, name, _policy) {
|
package/dist/index.js
CHANGED
|
@@ -65,19 +65,16 @@ async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
|
65
65
|
}
|
|
66
66
|
function loadPolicy(source) {
|
|
67
67
|
if (typeof source === "object") return source;
|
|
68
|
-
if (PRESETS[source]) return PRESETS[source];
|
|
69
68
|
const filePath = resolve(source);
|
|
70
69
|
if (existsSync(filePath)) {
|
|
71
70
|
const content = readFileSync(filePath, "utf-8");
|
|
72
71
|
return JSON.parse(content);
|
|
73
72
|
}
|
|
74
|
-
|
|
75
|
-
`Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
|
|
76
|
-
);
|
|
73
|
+
return DEFAULT_POLICY;
|
|
77
74
|
}
|
|
78
75
|
function parseArgs(argv) {
|
|
79
76
|
const args = argv.slice(2);
|
|
80
|
-
let policySource
|
|
77
|
+
let policySource;
|
|
81
78
|
let name = "solongate-proxy";
|
|
82
79
|
let verbose = false;
|
|
83
80
|
let rateLimitPerTool;
|
|
@@ -159,7 +156,7 @@ function parseArgs(argv) {
|
|
|
159
156
|
"Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
|
|
160
157
|
);
|
|
161
158
|
}
|
|
162
|
-
const resolvedPolicyPath = resolvePolicyPath(policySource);
|
|
159
|
+
const resolvedPolicyPath = policySource ? resolvePolicyPath(policySource) : null;
|
|
163
160
|
if (configFile) {
|
|
164
161
|
const filePath = resolve(configFile);
|
|
165
162
|
const content = readFileSync(filePath, "utf-8");
|
|
@@ -167,7 +164,7 @@ function parseArgs(argv) {
|
|
|
167
164
|
if (!fileConfig.upstream) {
|
|
168
165
|
throw new Error('Config file must include "upstream" with at least "command" or "url"');
|
|
169
166
|
}
|
|
170
|
-
const cfgPolicySource = fileConfig.policy ?? policySource;
|
|
167
|
+
const cfgPolicySource = fileConfig.policy ?? policySource ?? "policy.json";
|
|
171
168
|
return {
|
|
172
169
|
upstream: fileConfig.upstream,
|
|
173
170
|
policy: loadPolicy(cfgPolicySource),
|
|
@@ -191,7 +188,7 @@ function parseArgs(argv) {
|
|
|
191
188
|
// not used for URL-based transports
|
|
192
189
|
url: upstreamUrl
|
|
193
190
|
},
|
|
194
|
-
policy: loadPolicy(policySource),
|
|
191
|
+
policy: loadPolicy(policySource ?? "policy.json"),
|
|
195
192
|
name,
|
|
196
193
|
verbose,
|
|
197
194
|
rateLimitPerTool,
|
|
@@ -205,7 +202,7 @@ function parseArgs(argv) {
|
|
|
205
202
|
}
|
|
206
203
|
if (upstreamArgs.length === 0) {
|
|
207
204
|
throw new Error(
|
|
208
|
-
"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
|
|
205
|
+
"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 ./policy.json -- npx @playwright/mcp@latest\n solongate-proxy --upstream-url http://localhost:3001/mcp\n solongate-proxy --config solongate.json\n"
|
|
209
206
|
);
|
|
210
207
|
}
|
|
211
208
|
const [command, ...commandArgs] = upstreamArgs;
|
|
@@ -216,7 +213,7 @@ function parseArgs(argv) {
|
|
|
216
213
|
args: commandArgs,
|
|
217
214
|
env: { ...process.env }
|
|
218
215
|
},
|
|
219
|
-
policy: loadPolicy(policySource),
|
|
216
|
+
policy: loadPolicy(policySource ?? "policy.json"),
|
|
220
217
|
name,
|
|
221
218
|
verbose,
|
|
222
219
|
rateLimitPerTool,
|
|
@@ -229,303 +226,22 @@ function parseArgs(argv) {
|
|
|
229
226
|
};
|
|
230
227
|
}
|
|
231
228
|
function resolvePolicyPath(source) {
|
|
232
|
-
if (PRESETS[source]) return null;
|
|
233
229
|
const filePath = resolve(source);
|
|
234
230
|
if (existsSync(filePath)) return filePath;
|
|
235
231
|
return null;
|
|
236
232
|
}
|
|
237
|
-
var
|
|
233
|
+
var DEFAULT_POLICY;
|
|
238
234
|
var init_config = __esm({
|
|
239
235
|
"src/config.ts"() {
|
|
240
236
|
"use strict";
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
id: "deny-dangerous-commands",
|
|
250
|
-
description: "Block dangerous shell commands",
|
|
251
|
-
effect: "DENY",
|
|
252
|
-
priority: 50,
|
|
253
|
-
toolPattern: "*",
|
|
254
|
-
permission: "EXECUTE",
|
|
255
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
256
|
-
enabled: true,
|
|
257
|
-
commandConstraints: {
|
|
258
|
-
denied: [
|
|
259
|
-
"rm -rf *",
|
|
260
|
-
"rm -r /*",
|
|
261
|
-
"mkfs*",
|
|
262
|
-
"dd if=*",
|
|
263
|
-
"curl*|*bash*",
|
|
264
|
-
"curl*|*sh*",
|
|
265
|
-
"wget*|*bash*",
|
|
266
|
-
"wget*|*sh*",
|
|
267
|
-
"shutdown*",
|
|
268
|
-
"reboot*",
|
|
269
|
-
"kill -9*",
|
|
270
|
-
"chmod*777*",
|
|
271
|
-
"iptables*",
|
|
272
|
-
"passwd*",
|
|
273
|
-
"useradd*",
|
|
274
|
-
"userdel*"
|
|
275
|
-
]
|
|
276
|
-
},
|
|
277
|
-
createdAt: "",
|
|
278
|
-
updatedAt: ""
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
id: "deny-sensitive-paths",
|
|
282
|
-
description: "Block access to sensitive files",
|
|
283
|
-
effect: "DENY",
|
|
284
|
-
priority: 51,
|
|
285
|
-
toolPattern: "*",
|
|
286
|
-
permission: "EXECUTE",
|
|
287
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
288
|
-
enabled: true,
|
|
289
|
-
pathConstraints: {
|
|
290
|
-
denied: [
|
|
291
|
-
"**/.env*",
|
|
292
|
-
"**/.ssh/**",
|
|
293
|
-
"**/.aws/**",
|
|
294
|
-
"**/.kube/**",
|
|
295
|
-
"**/credentials*",
|
|
296
|
-
"**/secrets*",
|
|
297
|
-
"**/*.pem",
|
|
298
|
-
"**/*.key",
|
|
299
|
-
"/etc/passwd",
|
|
300
|
-
"/etc/shadow",
|
|
301
|
-
"/proc/**",
|
|
302
|
-
"/dev/**"
|
|
303
|
-
]
|
|
304
|
-
},
|
|
305
|
-
createdAt: "",
|
|
306
|
-
updatedAt: ""
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
id: "allow-rest",
|
|
310
|
-
description: "Allow all other tool calls",
|
|
311
|
-
effect: "ALLOW",
|
|
312
|
-
priority: 1e3,
|
|
313
|
-
toolPattern: "*",
|
|
314
|
-
permission: "EXECUTE",
|
|
315
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
316
|
-
enabled: true,
|
|
317
|
-
createdAt: "",
|
|
318
|
-
updatedAt: ""
|
|
319
|
-
}
|
|
320
|
-
],
|
|
321
|
-
createdAt: "",
|
|
322
|
-
updatedAt: ""
|
|
323
|
-
},
|
|
324
|
-
"read-only": {
|
|
325
|
-
id: "read-only",
|
|
326
|
-
name: "Read Only",
|
|
327
|
-
description: "Only allows read/list/get/search/query tools",
|
|
328
|
-
version: 1,
|
|
329
|
-
rules: [
|
|
330
|
-
{
|
|
331
|
-
id: "allow-read",
|
|
332
|
-
description: "Allow read tools",
|
|
333
|
-
effect: "ALLOW",
|
|
334
|
-
priority: 100,
|
|
335
|
-
toolPattern: "*read*",
|
|
336
|
-
permission: "EXECUTE",
|
|
337
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
338
|
-
enabled: true,
|
|
339
|
-
createdAt: "",
|
|
340
|
-
updatedAt: ""
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
id: "allow-list",
|
|
344
|
-
description: "Allow list tools",
|
|
345
|
-
effect: "ALLOW",
|
|
346
|
-
priority: 101,
|
|
347
|
-
toolPattern: "*list*",
|
|
348
|
-
permission: "EXECUTE",
|
|
349
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
350
|
-
enabled: true,
|
|
351
|
-
createdAt: "",
|
|
352
|
-
updatedAt: ""
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
id: "allow-get",
|
|
356
|
-
description: "Allow get tools",
|
|
357
|
-
effect: "ALLOW",
|
|
358
|
-
priority: 102,
|
|
359
|
-
toolPattern: "*get*",
|
|
360
|
-
permission: "EXECUTE",
|
|
361
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
362
|
-
enabled: true,
|
|
363
|
-
createdAt: "",
|
|
364
|
-
updatedAt: ""
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
id: "allow-search",
|
|
368
|
-
description: "Allow search tools",
|
|
369
|
-
effect: "ALLOW",
|
|
370
|
-
priority: 103,
|
|
371
|
-
toolPattern: "*search*",
|
|
372
|
-
permission: "EXECUTE",
|
|
373
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
374
|
-
enabled: true,
|
|
375
|
-
createdAt: "",
|
|
376
|
-
updatedAt: ""
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
id: "allow-query",
|
|
380
|
-
description: "Allow query tools",
|
|
381
|
-
effect: "ALLOW",
|
|
382
|
-
priority: 104,
|
|
383
|
-
toolPattern: "*query*",
|
|
384
|
-
permission: "EXECUTE",
|
|
385
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
386
|
-
enabled: true,
|
|
387
|
-
createdAt: "",
|
|
388
|
-
updatedAt: ""
|
|
389
|
-
}
|
|
390
|
-
],
|
|
391
|
-
createdAt: "",
|
|
392
|
-
updatedAt: ""
|
|
393
|
-
},
|
|
394
|
-
sandbox: {
|
|
395
|
-
id: "sandbox",
|
|
396
|
-
name: "Sandbox",
|
|
397
|
-
description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
|
|
398
|
-
version: 1,
|
|
399
|
-
rules: [
|
|
400
|
-
{
|
|
401
|
-
id: "deny-dangerous-commands",
|
|
402
|
-
description: "Block dangerous shell commands",
|
|
403
|
-
effect: "DENY",
|
|
404
|
-
priority: 50,
|
|
405
|
-
toolPattern: "*",
|
|
406
|
-
permission: "EXECUTE",
|
|
407
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
408
|
-
enabled: true,
|
|
409
|
-
commandConstraints: {
|
|
410
|
-
denied: [
|
|
411
|
-
"rm -rf *",
|
|
412
|
-
"rm -r /*",
|
|
413
|
-
"mkfs*",
|
|
414
|
-
"dd if=*",
|
|
415
|
-
"curl*|*bash*",
|
|
416
|
-
"wget*|*sh*",
|
|
417
|
-
"shutdown*",
|
|
418
|
-
"reboot*",
|
|
419
|
-
"chmod*777*"
|
|
420
|
-
]
|
|
421
|
-
},
|
|
422
|
-
createdAt: "",
|
|
423
|
-
updatedAt: ""
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
id: "allow-safe-commands",
|
|
427
|
-
description: "Allow safe shell commands only",
|
|
428
|
-
effect: "ALLOW",
|
|
429
|
-
priority: 100,
|
|
430
|
-
toolPattern: "*shell*",
|
|
431
|
-
permission: "EXECUTE",
|
|
432
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
433
|
-
enabled: true,
|
|
434
|
-
commandConstraints: {
|
|
435
|
-
allowed: [
|
|
436
|
-
"ls*",
|
|
437
|
-
"cat*",
|
|
438
|
-
"head*",
|
|
439
|
-
"tail*",
|
|
440
|
-
"wc*",
|
|
441
|
-
"grep*",
|
|
442
|
-
"find*",
|
|
443
|
-
"echo*",
|
|
444
|
-
"pwd",
|
|
445
|
-
"whoami",
|
|
446
|
-
"date",
|
|
447
|
-
"env",
|
|
448
|
-
"git*",
|
|
449
|
-
"npm*",
|
|
450
|
-
"pnpm*",
|
|
451
|
-
"yarn*",
|
|
452
|
-
"node*",
|
|
453
|
-
"python*",
|
|
454
|
-
"pip*"
|
|
455
|
-
]
|
|
456
|
-
},
|
|
457
|
-
createdAt: "",
|
|
458
|
-
updatedAt: ""
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
id: "deny-sensitive-paths",
|
|
462
|
-
description: "Block access to sensitive files",
|
|
463
|
-
effect: "DENY",
|
|
464
|
-
priority: 51,
|
|
465
|
-
toolPattern: "*",
|
|
466
|
-
permission: "EXECUTE",
|
|
467
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
468
|
-
enabled: true,
|
|
469
|
-
pathConstraints: {
|
|
470
|
-
denied: [
|
|
471
|
-
"**/.env*",
|
|
472
|
-
"**/.ssh/**",
|
|
473
|
-
"**/.aws/**",
|
|
474
|
-
"**/credentials*",
|
|
475
|
-
"**/*.pem",
|
|
476
|
-
"**/*.key"
|
|
477
|
-
]
|
|
478
|
-
},
|
|
479
|
-
createdAt: "",
|
|
480
|
-
updatedAt: ""
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
id: "allow-rest",
|
|
484
|
-
description: "Allow all other tools",
|
|
485
|
-
effect: "ALLOW",
|
|
486
|
-
priority: 1e3,
|
|
487
|
-
toolPattern: "*",
|
|
488
|
-
permission: "EXECUTE",
|
|
489
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
490
|
-
enabled: true,
|
|
491
|
-
createdAt: "",
|
|
492
|
-
updatedAt: ""
|
|
493
|
-
}
|
|
494
|
-
],
|
|
495
|
-
createdAt: "",
|
|
496
|
-
updatedAt: ""
|
|
497
|
-
},
|
|
498
|
-
permissive: {
|
|
499
|
-
id: "permissive",
|
|
500
|
-
name: "Permissive",
|
|
501
|
-
description: "Allows all tool calls (monitoring only)",
|
|
502
|
-
version: 1,
|
|
503
|
-
rules: [
|
|
504
|
-
{
|
|
505
|
-
id: "allow-all",
|
|
506
|
-
description: "Allow all",
|
|
507
|
-
effect: "ALLOW",
|
|
508
|
-
priority: 1e3,
|
|
509
|
-
toolPattern: "*",
|
|
510
|
-
permission: "EXECUTE",
|
|
511
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
512
|
-
enabled: true,
|
|
513
|
-
createdAt: "",
|
|
514
|
-
updatedAt: ""
|
|
515
|
-
}
|
|
516
|
-
],
|
|
517
|
-
createdAt: "",
|
|
518
|
-
updatedAt: ""
|
|
519
|
-
},
|
|
520
|
-
"deny-all": {
|
|
521
|
-
id: "deny-all",
|
|
522
|
-
name: "Deny All",
|
|
523
|
-
description: "Blocks all tool calls",
|
|
524
|
-
version: 1,
|
|
525
|
-
rules: [],
|
|
526
|
-
createdAt: "",
|
|
527
|
-
updatedAt: ""
|
|
528
|
-
}
|
|
237
|
+
DEFAULT_POLICY = {
|
|
238
|
+
id: "default",
|
|
239
|
+
name: "Default (Deny All)",
|
|
240
|
+
description: "Default-deny policy. Create rules in the dashboard or push a policy.json.",
|
|
241
|
+
version: 1,
|
|
242
|
+
rules: [],
|
|
243
|
+
createdAt: "",
|
|
244
|
+
updatedAt: ""
|
|
529
245
|
};
|
|
530
246
|
}
|
|
531
247
|
});
|
|
@@ -591,18 +307,14 @@ function isAlreadyProtected(server) {
|
|
|
591
307
|
function wrapServer(server, policy) {
|
|
592
308
|
const env = { ...server.env ?? {} };
|
|
593
309
|
env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
|
|
310
|
+
const proxyArgs = ["-y", "@solongate/proxy"];
|
|
311
|
+
if (policy) {
|
|
312
|
+
proxyArgs.push("--policy", policy);
|
|
313
|
+
}
|
|
314
|
+
proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
|
|
594
315
|
return {
|
|
595
316
|
command: "npx",
|
|
596
|
-
args:
|
|
597
|
-
"-y",
|
|
598
|
-
"@solongate/proxy",
|
|
599
|
-
"--policy",
|
|
600
|
-
policy,
|
|
601
|
-
"--verbose",
|
|
602
|
-
"--",
|
|
603
|
-
server.command,
|
|
604
|
-
...server.args ?? []
|
|
605
|
-
],
|
|
317
|
+
args: proxyArgs,
|
|
606
318
|
env
|
|
607
319
|
};
|
|
608
320
|
}
|
|
@@ -651,8 +363,7 @@ USAGE
|
|
|
651
363
|
|
|
652
364
|
OPTIONS
|
|
653
365
|
--config <path> Path to MCP config file (default: auto-detect)
|
|
654
|
-
--policy <file> Custom policy JSON file (
|
|
655
|
-
Auto-detects policy.json in current directory
|
|
366
|
+
--policy <file> Custom policy JSON file (auto-detects policy.json)
|
|
656
367
|
--api-key <key> SolonGate API key (sg_live_... or sg_test_...)
|
|
657
368
|
--all Protect all servers without prompting
|
|
658
369
|
-h, --help Show this help message
|
|
@@ -870,7 +581,7 @@ async function main() {
|
|
|
870
581
|
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
871
582
|
process.exit(1);
|
|
872
583
|
}
|
|
873
|
-
let policyValue
|
|
584
|
+
let policyValue;
|
|
874
585
|
if (options.policy) {
|
|
875
586
|
const policyPath = resolve2(options.policy);
|
|
876
587
|
if (existsSync3(policyPath)) {
|
|
@@ -886,7 +597,7 @@ async function main() {
|
|
|
886
597
|
policyValue = "./policy.json";
|
|
887
598
|
console.log(` Policy: ${defaultPolicy} (auto-detected)`);
|
|
888
599
|
} else {
|
|
889
|
-
console.log(` Policy:
|
|
600
|
+
console.log(` Policy: cloud-managed (fetched via API key)`);
|
|
890
601
|
}
|
|
891
602
|
}
|
|
892
603
|
await sleep(300);
|
|
@@ -1726,7 +1437,7 @@ function parseCreateArgs(argv) {
|
|
|
1726
1437
|
const args = argv.slice(2);
|
|
1727
1438
|
const opts = {
|
|
1728
1439
|
name: "",
|
|
1729
|
-
policy: "
|
|
1440
|
+
policy: "",
|
|
1730
1441
|
noInstall: false
|
|
1731
1442
|
};
|
|
1732
1443
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -1769,13 +1480,13 @@ USAGE
|
|
|
1769
1480
|
npx @solongate/proxy create <name> [options]
|
|
1770
1481
|
|
|
1771
1482
|
OPTIONS
|
|
1772
|
-
--policy <
|
|
1483
|
+
--policy <file> Policy JSON file (default: cloud-managed)
|
|
1773
1484
|
--no-install Skip dependency installation
|
|
1774
1485
|
-h, --help Show this help message
|
|
1775
1486
|
|
|
1776
1487
|
EXAMPLES
|
|
1777
1488
|
npx @solongate/proxy create my-server
|
|
1778
|
-
npx @solongate/proxy create db-tools --policy
|
|
1489
|
+
npx @solongate/proxy create db-tools --policy ./policy.json
|
|
1779
1490
|
`);
|
|
1780
1491
|
}
|
|
1781
1492
|
function createProject(dir, name, _policy) {
|
|
@@ -2440,6 +2151,14 @@ var PolicyRuleSchema = z.object({
|
|
|
2440
2151
|
allowed: z.array(z.string()).optional(),
|
|
2441
2152
|
denied: z.array(z.string()).optional()
|
|
2442
2153
|
}).optional(),
|
|
2154
|
+
filenameConstraints: z.object({
|
|
2155
|
+
allowed: z.array(z.string()).optional(),
|
|
2156
|
+
denied: z.array(z.string()).optional()
|
|
2157
|
+
}).optional(),
|
|
2158
|
+
urlConstraints: z.object({
|
|
2159
|
+
allowed: z.array(z.string()).optional(),
|
|
2160
|
+
denied: z.array(z.string()).optional()
|
|
2161
|
+
}).optional(),
|
|
2443
2162
|
enabled: z.boolean().default(true),
|
|
2444
2163
|
createdAt: z.string().datetime(),
|
|
2445
2164
|
updatedAt: z.string().datetime()
|
|
@@ -2625,11 +2344,48 @@ function matchSegmentGlob(segment, pattern) {
|
|
|
2625
2344
|
}
|
|
2626
2345
|
return segment === pattern;
|
|
2627
2346
|
}
|
|
2347
|
+
var PATH_FIELDS = /* @__PURE__ */ new Set([
|
|
2348
|
+
"path",
|
|
2349
|
+
"file",
|
|
2350
|
+
"file_path",
|
|
2351
|
+
"filepath",
|
|
2352
|
+
"filename",
|
|
2353
|
+
"directory",
|
|
2354
|
+
"dir",
|
|
2355
|
+
"folder",
|
|
2356
|
+
"source",
|
|
2357
|
+
"destination",
|
|
2358
|
+
"dest",
|
|
2359
|
+
"target",
|
|
2360
|
+
"input",
|
|
2361
|
+
"output",
|
|
2362
|
+
"cwd",
|
|
2363
|
+
"root",
|
|
2364
|
+
"notebook_path"
|
|
2365
|
+
]);
|
|
2628
2366
|
function extractPathArguments(args) {
|
|
2629
2367
|
const paths = [];
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2368
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2369
|
+
function addPath(value) {
|
|
2370
|
+
const trimmed = value.trim();
|
|
2371
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
2372
|
+
seen.add(trimmed);
|
|
2373
|
+
paths.push(trimmed);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2377
|
+
if (typeof value !== "string") continue;
|
|
2378
|
+
if (PATH_FIELDS.has(key.toLowerCase())) {
|
|
2379
|
+
addPath(value);
|
|
2380
|
+
continue;
|
|
2381
|
+
}
|
|
2382
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
2383
|
+
addPath(value);
|
|
2384
|
+
continue;
|
|
2385
|
+
}
|
|
2386
|
+
if (value.startsWith(".")) {
|
|
2387
|
+
addPath(value);
|
|
2388
|
+
continue;
|
|
2633
2389
|
}
|
|
2634
2390
|
}
|
|
2635
2391
|
return paths;
|
|
@@ -2643,16 +2399,63 @@ var COMMAND_FIELDS = /* @__PURE__ */ new Set([
|
|
|
2643
2399
|
"shell",
|
|
2644
2400
|
"exec",
|
|
2645
2401
|
"sql",
|
|
2646
|
-
"expression"
|
|
2402
|
+
"expression",
|
|
2403
|
+
"function"
|
|
2647
2404
|
]);
|
|
2405
|
+
var COMMAND_HEURISTICS = [
|
|
2406
|
+
/^(sh|bash|cmd|powershell|zsh|fish)\s+-c\s+/i,
|
|
2407
|
+
// shell -c "..."
|
|
2408
|
+
/^(sudo|doas)\s+/i,
|
|
2409
|
+
// privilege escalation
|
|
2410
|
+
/^\w+\s+&&\s+/,
|
|
2411
|
+
// cmd1 && cmd2
|
|
2412
|
+
/^\w+\s*\|\s*\w+/,
|
|
2413
|
+
// cmd1 | cmd2
|
|
2414
|
+
/^\w+\s*;\s*\w+/,
|
|
2415
|
+
// cmd1; cmd2
|
|
2416
|
+
/^(curl|wget|nc|ncat)\s+/i,
|
|
2417
|
+
// network commands
|
|
2418
|
+
/^(rm|del|rmdir)\s+/i,
|
|
2419
|
+
// destructive commands
|
|
2420
|
+
/^(cat|type|more|less)\s+.*[/\\]/i
|
|
2421
|
+
// file read commands with paths
|
|
2422
|
+
];
|
|
2648
2423
|
function extractCommandArguments(args) {
|
|
2649
2424
|
const commands = [];
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2426
|
+
function addCommand(value) {
|
|
2427
|
+
const trimmed = value.trim();
|
|
2428
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
2429
|
+
seen.add(trimmed);
|
|
2430
|
+
commands.push(trimmed);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
function scanValue(key, value) {
|
|
2434
|
+
if (typeof value === "string") {
|
|
2435
|
+
if (COMMAND_FIELDS.has(key.toLowerCase())) {
|
|
2436
|
+
addCommand(value);
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
for (const pattern of COMMAND_HEURISTICS) {
|
|
2440
|
+
if (pattern.test(value)) {
|
|
2441
|
+
addCommand(value);
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
if (Array.isArray(value)) {
|
|
2447
|
+
for (const item of value) {
|
|
2448
|
+
scanValue(key, item);
|
|
2449
|
+
}
|
|
2450
|
+
} else if (typeof value === "object" && value !== null) {
|
|
2451
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2452
|
+
scanValue(k, v);
|
|
2453
|
+
}
|
|
2654
2454
|
}
|
|
2655
2455
|
}
|
|
2456
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2457
|
+
scanValue(key, value);
|
|
2458
|
+
}
|
|
2656
2459
|
return commands;
|
|
2657
2460
|
}
|
|
2658
2461
|
function matchCommandPattern(command, pattern) {
|
|
@@ -2697,6 +2500,224 @@ function isCommandAllowed(command, constraints) {
|
|
|
2697
2500
|
}
|
|
2698
2501
|
return true;
|
|
2699
2502
|
}
|
|
2503
|
+
function extractFilenames(args) {
|
|
2504
|
+
const filenames = [];
|
|
2505
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2506
|
+
function addFilename(name) {
|
|
2507
|
+
const trimmed = name.trim();
|
|
2508
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
2509
|
+
seen.add(trimmed);
|
|
2510
|
+
filenames.push(trimmed);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
function scanValue(value) {
|
|
2514
|
+
if (typeof value === "string") {
|
|
2515
|
+
const trimmed = value.trim();
|
|
2516
|
+
if (!trimmed) return;
|
|
2517
|
+
if (/^https?:\/\//i.test(trimmed)) return;
|
|
2518
|
+
if (trimmed.includes("/") || trimmed.includes("\\")) {
|
|
2519
|
+
const normalized = trimmed.replace(/\\/g, "/");
|
|
2520
|
+
const parts = normalized.split("/");
|
|
2521
|
+
const basename = parts[parts.length - 1];
|
|
2522
|
+
if (basename && basename.length > 0) {
|
|
2523
|
+
addFilename(basename);
|
|
2524
|
+
}
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
if (trimmed.includes(" ")) {
|
|
2528
|
+
for (const token of trimmed.split(/\s+/)) {
|
|
2529
|
+
if (token.includes("/") || token.includes("\\")) {
|
|
2530
|
+
const parts = token.replace(/\\/g, "/").split("/");
|
|
2531
|
+
const basename = parts[parts.length - 1];
|
|
2532
|
+
if (basename && looksLikeFilename(basename)) addFilename(basename);
|
|
2533
|
+
} else if (looksLikeFilename(token)) {
|
|
2534
|
+
addFilename(token);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
if (looksLikeFilename(trimmed)) {
|
|
2540
|
+
addFilename(trimmed);
|
|
2541
|
+
}
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
if (Array.isArray(value)) {
|
|
2545
|
+
for (const item of value) {
|
|
2546
|
+
scanValue(item);
|
|
2547
|
+
}
|
|
2548
|
+
} else if (typeof value === "object" && value !== null) {
|
|
2549
|
+
for (const v of Object.values(value)) {
|
|
2550
|
+
scanValue(v);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
for (const value of Object.values(args)) {
|
|
2555
|
+
scanValue(value);
|
|
2556
|
+
}
|
|
2557
|
+
return filenames;
|
|
2558
|
+
}
|
|
2559
|
+
function looksLikeFilename(s) {
|
|
2560
|
+
if (s.startsWith(".")) return true;
|
|
2561
|
+
if (/\.\w+$/.test(s)) return true;
|
|
2562
|
+
const knownFiles = /* @__PURE__ */ new Set([
|
|
2563
|
+
"id_rsa",
|
|
2564
|
+
"id_dsa",
|
|
2565
|
+
"id_ecdsa",
|
|
2566
|
+
"id_ed25519",
|
|
2567
|
+
"authorized_keys",
|
|
2568
|
+
"known_hosts",
|
|
2569
|
+
"makefile",
|
|
2570
|
+
"dockerfile",
|
|
2571
|
+
"vagrantfile",
|
|
2572
|
+
"gemfile",
|
|
2573
|
+
"rakefile",
|
|
2574
|
+
"procfile"
|
|
2575
|
+
]);
|
|
2576
|
+
if (knownFiles.has(s.toLowerCase())) return true;
|
|
2577
|
+
return false;
|
|
2578
|
+
}
|
|
2579
|
+
function matchFilenamePattern(filename, pattern) {
|
|
2580
|
+
if (pattern === "*") return true;
|
|
2581
|
+
const normalizedFilename = filename.toLowerCase();
|
|
2582
|
+
const normalizedPattern = pattern.toLowerCase();
|
|
2583
|
+
if (normalizedFilename === normalizedPattern) return true;
|
|
2584
|
+
const startsWithStar = normalizedPattern.startsWith("*");
|
|
2585
|
+
const endsWithStar = normalizedPattern.endsWith("*");
|
|
2586
|
+
if (startsWithStar && endsWithStar) {
|
|
2587
|
+
const infix = normalizedPattern.slice(1, -1);
|
|
2588
|
+
return infix.length > 0 && normalizedFilename.includes(infix);
|
|
2589
|
+
}
|
|
2590
|
+
if (startsWithStar) {
|
|
2591
|
+
const suffix = normalizedPattern.slice(1);
|
|
2592
|
+
return normalizedFilename.endsWith(suffix);
|
|
2593
|
+
}
|
|
2594
|
+
if (endsWithStar) {
|
|
2595
|
+
const prefix = normalizedPattern.slice(0, -1);
|
|
2596
|
+
return normalizedFilename.startsWith(prefix);
|
|
2597
|
+
}
|
|
2598
|
+
const starIdx = normalizedPattern.indexOf("*");
|
|
2599
|
+
if (starIdx !== -1) {
|
|
2600
|
+
const prefix = normalizedPattern.slice(0, starIdx);
|
|
2601
|
+
const suffix = normalizedPattern.slice(starIdx + 1);
|
|
2602
|
+
return normalizedFilename.startsWith(prefix) && normalizedFilename.endsWith(suffix) && normalizedFilename.length >= prefix.length + suffix.length;
|
|
2603
|
+
}
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2606
|
+
function isFilenameAllowed(filename, constraints) {
|
|
2607
|
+
if (constraints.denied && constraints.denied.length > 0) {
|
|
2608
|
+
for (const pattern of constraints.denied) {
|
|
2609
|
+
if (matchFilenamePattern(filename, pattern)) {
|
|
2610
|
+
return false;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
if (constraints.allowed && constraints.allowed.length > 0) {
|
|
2615
|
+
let matchesAllowed = false;
|
|
2616
|
+
for (const pattern of constraints.allowed) {
|
|
2617
|
+
if (matchFilenamePattern(filename, pattern)) {
|
|
2618
|
+
matchesAllowed = true;
|
|
2619
|
+
break;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
if (!matchesAllowed) return false;
|
|
2623
|
+
}
|
|
2624
|
+
return true;
|
|
2625
|
+
}
|
|
2626
|
+
var URL_FIELDS = /* @__PURE__ */ new Set([
|
|
2627
|
+
"url",
|
|
2628
|
+
"href",
|
|
2629
|
+
"uri",
|
|
2630
|
+
"endpoint",
|
|
2631
|
+
"link",
|
|
2632
|
+
"src",
|
|
2633
|
+
"source",
|
|
2634
|
+
"target",
|
|
2635
|
+
"redirect",
|
|
2636
|
+
"callback",
|
|
2637
|
+
"webhook"
|
|
2638
|
+
]);
|
|
2639
|
+
function extractUrlArguments(args) {
|
|
2640
|
+
const urls = [];
|
|
2641
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2642
|
+
function addUrl(value) {
|
|
2643
|
+
const trimmed = value.trim();
|
|
2644
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
2645
|
+
seen.add(trimmed);
|
|
2646
|
+
urls.push(trimmed);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
function scanValue(key, value) {
|
|
2650
|
+
if (typeof value === "string") {
|
|
2651
|
+
const lower = key.toLowerCase();
|
|
2652
|
+
if (URL_FIELDS.has(lower)) {
|
|
2653
|
+
addUrl(value);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (/https?:\/\//i.test(value)) {
|
|
2657
|
+
addUrl(value);
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
if (/^[a-zA-Z0-9]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}(\/.*)?$/.test(value)) {
|
|
2661
|
+
addUrl(value);
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
if (Array.isArray(value)) {
|
|
2666
|
+
for (const item of value) {
|
|
2667
|
+
scanValue(key, item);
|
|
2668
|
+
}
|
|
2669
|
+
} else if (typeof value === "object" && value !== null) {
|
|
2670
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2671
|
+
scanValue(k, v);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2676
|
+
scanValue(key, value);
|
|
2677
|
+
}
|
|
2678
|
+
return urls;
|
|
2679
|
+
}
|
|
2680
|
+
function matchUrlPattern(url, pattern) {
|
|
2681
|
+
if (pattern === "*") return true;
|
|
2682
|
+
const normalizedUrl = url.trim().toLowerCase();
|
|
2683
|
+
const normalizedPattern = pattern.trim().toLowerCase();
|
|
2684
|
+
if (normalizedPattern === normalizedUrl) return true;
|
|
2685
|
+
const startsWithStar = normalizedPattern.startsWith("*");
|
|
2686
|
+
const endsWithStar = normalizedPattern.endsWith("*");
|
|
2687
|
+
if (startsWithStar && endsWithStar) {
|
|
2688
|
+
const infix = normalizedPattern.slice(1, -1);
|
|
2689
|
+
return infix.length > 0 && normalizedUrl.includes(infix);
|
|
2690
|
+
}
|
|
2691
|
+
if (endsWithStar) {
|
|
2692
|
+
const prefix = normalizedPattern.slice(0, -1);
|
|
2693
|
+
return normalizedUrl.startsWith(prefix);
|
|
2694
|
+
}
|
|
2695
|
+
if (startsWithStar) {
|
|
2696
|
+
const suffix = normalizedPattern.slice(1);
|
|
2697
|
+
return normalizedUrl.endsWith(suffix);
|
|
2698
|
+
}
|
|
2699
|
+
return false;
|
|
2700
|
+
}
|
|
2701
|
+
function isUrlAllowed(url, constraints) {
|
|
2702
|
+
if (constraints.denied && constraints.denied.length > 0) {
|
|
2703
|
+
for (const pattern of constraints.denied) {
|
|
2704
|
+
if (matchUrlPattern(url, pattern)) {
|
|
2705
|
+
return false;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
if (constraints.allowed && constraints.allowed.length > 0) {
|
|
2710
|
+
let matchesAllowed = false;
|
|
2711
|
+
for (const pattern of constraints.allowed) {
|
|
2712
|
+
if (matchUrlPattern(url, pattern)) {
|
|
2713
|
+
matchesAllowed = true;
|
|
2714
|
+
break;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
if (!matchesAllowed) return false;
|
|
2718
|
+
}
|
|
2719
|
+
return true;
|
|
2720
|
+
}
|
|
2700
2721
|
function ruleMatchesRequest(rule, request) {
|
|
2701
2722
|
if (!rule.enabled) return false;
|
|
2702
2723
|
if (rule.permission !== request.requiredPermission) return false;
|
|
@@ -2725,6 +2746,22 @@ function ruleMatchesRequest(rule, request) {
|
|
|
2725
2746
|
if (!satisfied) return false;
|
|
2726
2747
|
}
|
|
2727
2748
|
}
|
|
2749
|
+
if (rule.filenameConstraints) {
|
|
2750
|
+
const satisfied = filenameConstraintsMatch(rule.filenameConstraints, request.arguments);
|
|
2751
|
+
if (rule.effect === "DENY") {
|
|
2752
|
+
if (satisfied) return false;
|
|
2753
|
+
} else {
|
|
2754
|
+
if (!satisfied) return false;
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
if (rule.urlConstraints) {
|
|
2758
|
+
const satisfied = urlConstraintsMatch(rule.urlConstraints, request.arguments);
|
|
2759
|
+
if (rule.effect === "DENY") {
|
|
2760
|
+
if (satisfied) return false;
|
|
2761
|
+
} else {
|
|
2762
|
+
if (!satisfied) return false;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2728
2765
|
return true;
|
|
2729
2766
|
}
|
|
2730
2767
|
function toolPatternMatches(pattern, toolName) {
|
|
@@ -2815,6 +2852,16 @@ function commandConstraintsMatch(constraints, args) {
|
|
|
2815
2852
|
if (commands.length === 0) return true;
|
|
2816
2853
|
return commands.every((cmd) => isCommandAllowed(cmd, constraints));
|
|
2817
2854
|
}
|
|
2855
|
+
function filenameConstraintsMatch(constraints, args) {
|
|
2856
|
+
const filenames = extractFilenames(args);
|
|
2857
|
+
if (filenames.length === 0) return true;
|
|
2858
|
+
return filenames.every((name) => isFilenameAllowed(name, constraints));
|
|
2859
|
+
}
|
|
2860
|
+
function urlConstraintsMatch(constraints, args) {
|
|
2861
|
+
const urls = extractUrlArguments(args);
|
|
2862
|
+
if (urls.length === 0) return true;
|
|
2863
|
+
return urls.every((url) => isUrlAllowed(url, constraints));
|
|
2864
|
+
}
|
|
2818
2865
|
function evaluatePolicy(policySet, request) {
|
|
2819
2866
|
const startTime = performance.now();
|
|
2820
2867
|
const sortedRules = [...policySet.rules].sort(
|
|
@@ -3195,6 +3242,11 @@ function resolveConfig(userConfig) {
|
|
|
3195
3242
|
"Token secret is shorter than 32 characters. Use a longer secret for production."
|
|
3196
3243
|
);
|
|
3197
3244
|
}
|
|
3245
|
+
if (config.apiUrl && config.apiUrl.startsWith("http://") && !config.apiUrl.startsWith("http://localhost") && !config.apiUrl.startsWith("http://127.0.0.1")) {
|
|
3246
|
+
warnings.push(
|
|
3247
|
+
"API URL uses plaintext HTTP. API keys will be sent unencrypted. Use HTTPS in production."
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3198
3250
|
return { config, warnings };
|
|
3199
3251
|
}
|
|
3200
3252
|
async function interceptToolCall(params, upstreamCall, options) {
|
|
@@ -3700,6 +3752,12 @@ var SolonGate = class {
|
|
|
3700
3752
|
async validateLicense() {
|
|
3701
3753
|
if (this.licenseValidated) return;
|
|
3702
3754
|
if (this.apiKey.startsWith("sg_test_")) {
|
|
3755
|
+
const nodeEnv = typeof process !== "undefined" ? process.env.NODE_ENV : "";
|
|
3756
|
+
if (nodeEnv === "production") {
|
|
3757
|
+
throw new LicenseError(
|
|
3758
|
+
"Test API keys (sg_test_) cannot be used in production. Use a sg_live_ key instead."
|
|
3759
|
+
);
|
|
3760
|
+
}
|
|
3703
3761
|
this.licenseValidated = true;
|
|
3704
3762
|
return;
|
|
3705
3763
|
}
|
|
@@ -3866,6 +3924,361 @@ var SolonGate = class {
|
|
|
3866
3924
|
}
|
|
3867
3925
|
};
|
|
3868
3926
|
|
|
3927
|
+
// ../core/dist/index.js
|
|
3928
|
+
import { z as z2 } from "zod";
|
|
3929
|
+
var Permission2 = {
|
|
3930
|
+
READ: "READ",
|
|
3931
|
+
WRITE: "WRITE",
|
|
3932
|
+
EXECUTE: "EXECUTE"
|
|
3933
|
+
};
|
|
3934
|
+
var PermissionSchema = z2.enum(["READ", "WRITE", "EXECUTE"]);
|
|
3935
|
+
var NO_PERMISSIONS = Object.freeze(
|
|
3936
|
+
/* @__PURE__ */ new Set()
|
|
3937
|
+
);
|
|
3938
|
+
var READ_ONLY = Object.freeze(
|
|
3939
|
+
/* @__PURE__ */ new Set([Permission2.READ])
|
|
3940
|
+
);
|
|
3941
|
+
var PolicyRuleSchema2 = z2.object({
|
|
3942
|
+
id: z2.string().min(1).max(256),
|
|
3943
|
+
description: z2.string().max(1024),
|
|
3944
|
+
effect: z2.enum(["ALLOW", "DENY"]),
|
|
3945
|
+
priority: z2.number().int().min(0).max(1e4).default(1e3),
|
|
3946
|
+
toolPattern: z2.string().min(1).max(512),
|
|
3947
|
+
permission: z2.enum(["READ", "WRITE", "EXECUTE"]),
|
|
3948
|
+
minimumTrustLevel: z2.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
|
|
3949
|
+
argumentConstraints: z2.record(z2.unknown()).optional(),
|
|
3950
|
+
pathConstraints: z2.object({
|
|
3951
|
+
allowed: z2.array(z2.string()).optional(),
|
|
3952
|
+
denied: z2.array(z2.string()).optional(),
|
|
3953
|
+
rootDirectory: z2.string().optional(),
|
|
3954
|
+
allowSymlinks: z2.boolean().optional()
|
|
3955
|
+
}).optional(),
|
|
3956
|
+
commandConstraints: z2.object({
|
|
3957
|
+
allowed: z2.array(z2.string()).optional(),
|
|
3958
|
+
denied: z2.array(z2.string()).optional()
|
|
3959
|
+
}).optional(),
|
|
3960
|
+
filenameConstraints: z2.object({
|
|
3961
|
+
allowed: z2.array(z2.string()).optional(),
|
|
3962
|
+
denied: z2.array(z2.string()).optional()
|
|
3963
|
+
}).optional(),
|
|
3964
|
+
urlConstraints: z2.object({
|
|
3965
|
+
allowed: z2.array(z2.string()).optional(),
|
|
3966
|
+
denied: z2.array(z2.string()).optional()
|
|
3967
|
+
}).optional(),
|
|
3968
|
+
enabled: z2.boolean().default(true),
|
|
3969
|
+
createdAt: z2.string().datetime(),
|
|
3970
|
+
updatedAt: z2.string().datetime()
|
|
3971
|
+
});
|
|
3972
|
+
var PolicySetSchema2 = z2.object({
|
|
3973
|
+
id: z2.string().min(1).max(256),
|
|
3974
|
+
name: z2.string().min(1).max(256),
|
|
3975
|
+
description: z2.string().max(2048),
|
|
3976
|
+
version: z2.number().int().min(0),
|
|
3977
|
+
rules: z2.array(PolicyRuleSchema2),
|
|
3978
|
+
createdAt: z2.string().datetime(),
|
|
3979
|
+
updatedAt: z2.string().datetime()
|
|
3980
|
+
});
|
|
3981
|
+
var SECURITY_CONTEXT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3982
|
+
var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
|
|
3983
|
+
pathTraversal: true,
|
|
3984
|
+
shellInjection: true,
|
|
3985
|
+
wildcardAbuse: true,
|
|
3986
|
+
lengthLimit: 4096,
|
|
3987
|
+
entropyLimit: true,
|
|
3988
|
+
ssrf: true,
|
|
3989
|
+
sqlInjection: true
|
|
3990
|
+
});
|
|
3991
|
+
var PATH_TRAVERSAL_PATTERNS = [
|
|
3992
|
+
/\.\.\//,
|
|
3993
|
+
// ../
|
|
3994
|
+
/\.\.\\/,
|
|
3995
|
+
// ..\
|
|
3996
|
+
/%2e%2e/i,
|
|
3997
|
+
// URL-encoded ..
|
|
3998
|
+
/%2e\./i,
|
|
3999
|
+
// partial URL-encoded
|
|
4000
|
+
/\.%2e/i,
|
|
4001
|
+
// partial URL-encoded
|
|
4002
|
+
/%252e%252e/i,
|
|
4003
|
+
// double URL-encoded
|
|
4004
|
+
/\.\.\0/
|
|
4005
|
+
// null byte variant
|
|
4006
|
+
];
|
|
4007
|
+
var SENSITIVE_PATHS = [
|
|
4008
|
+
/\/etc\/passwd/i,
|
|
4009
|
+
/\/etc\/shadow/i,
|
|
4010
|
+
/\/proc\//i,
|
|
4011
|
+
/\/dev\//i,
|
|
4012
|
+
/c:\\windows\\system32/i,
|
|
4013
|
+
/c:\\windows\\syswow64/i,
|
|
4014
|
+
/\/root\//i,
|
|
4015
|
+
/~\//,
|
|
4016
|
+
/\.env(\.|$)/i,
|
|
4017
|
+
// .env, .env.local, .env.production
|
|
4018
|
+
/\.aws\/credentials/i,
|
|
4019
|
+
// AWS credentials
|
|
4020
|
+
/\.ssh\/id_/i,
|
|
4021
|
+
// SSH keys
|
|
4022
|
+
/\.kube\/config/i,
|
|
4023
|
+
// Kubernetes config
|
|
4024
|
+
/wp-config\.php/i,
|
|
4025
|
+
// WordPress config
|
|
4026
|
+
/\.git\/config/i,
|
|
4027
|
+
// Git config
|
|
4028
|
+
/\.npmrc/i,
|
|
4029
|
+
// npm credentials
|
|
4030
|
+
/\.pypirc/i
|
|
4031
|
+
// PyPI credentials
|
|
4032
|
+
];
|
|
4033
|
+
function detectPathTraversal(value) {
|
|
4034
|
+
for (const pattern of PATH_TRAVERSAL_PATTERNS) {
|
|
4035
|
+
if (pattern.test(value)) return true;
|
|
4036
|
+
}
|
|
4037
|
+
for (const pattern of SENSITIVE_PATHS) {
|
|
4038
|
+
if (pattern.test(value)) return true;
|
|
4039
|
+
}
|
|
4040
|
+
return false;
|
|
4041
|
+
}
|
|
4042
|
+
var SHELL_INJECTION_PATTERNS = [
|
|
4043
|
+
/[;|&`]/,
|
|
4044
|
+
// Command separators and backtick execution
|
|
4045
|
+
/\$\(/,
|
|
4046
|
+
// Command substitution $(...)
|
|
4047
|
+
/\$\{/,
|
|
4048
|
+
// Variable expansion ${...}
|
|
4049
|
+
/>\s*/,
|
|
4050
|
+
// Output redirect
|
|
4051
|
+
/<\s*/,
|
|
4052
|
+
// Input redirect
|
|
4053
|
+
/&&/,
|
|
4054
|
+
// AND chaining
|
|
4055
|
+
/\|\|/,
|
|
4056
|
+
// OR chaining
|
|
4057
|
+
/\beval\b/i,
|
|
4058
|
+
// eval command
|
|
4059
|
+
/\bexec\b/i,
|
|
4060
|
+
// exec command
|
|
4061
|
+
/\bsystem\b/i,
|
|
4062
|
+
// system call
|
|
4063
|
+
/%0a/i,
|
|
4064
|
+
// URL-encoded newline
|
|
4065
|
+
/%0d/i,
|
|
4066
|
+
// URL-encoded carriage return
|
|
4067
|
+
/%09/i,
|
|
4068
|
+
// URL-encoded tab
|
|
4069
|
+
/\r\n/,
|
|
4070
|
+
// CRLF injection
|
|
4071
|
+
/\n/
|
|
4072
|
+
// Newline (command separator on Unix)
|
|
4073
|
+
];
|
|
4074
|
+
function detectShellInjection(value) {
|
|
4075
|
+
for (const pattern of SHELL_INJECTION_PATTERNS) {
|
|
4076
|
+
if (pattern.test(value)) return true;
|
|
4077
|
+
}
|
|
4078
|
+
return false;
|
|
4079
|
+
}
|
|
4080
|
+
var MAX_WILDCARDS_PER_VALUE = 3;
|
|
4081
|
+
function detectWildcardAbuse(value) {
|
|
4082
|
+
if (value.includes("**")) return true;
|
|
4083
|
+
const wildcardCount = (value.match(/\*/g) || []).length;
|
|
4084
|
+
if (wildcardCount > MAX_WILDCARDS_PER_VALUE) return true;
|
|
4085
|
+
return false;
|
|
4086
|
+
}
|
|
4087
|
+
var SSRF_PATTERNS = [
|
|
4088
|
+
/^https?:\/\/localhost\b/i,
|
|
4089
|
+
/^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
|
|
4090
|
+
/^https?:\/\/0\.0\.0\.0/,
|
|
4091
|
+
/^https?:\/\/\[::1\]/,
|
|
4092
|
+
// IPv6 loopback
|
|
4093
|
+
/^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
|
|
4094
|
+
// 10.x.x.x
|
|
4095
|
+
/^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
|
|
4096
|
+
// 172.16-31.x.x
|
|
4097
|
+
/^https?:\/\/192\.168\./,
|
|
4098
|
+
// 192.168.x.x
|
|
4099
|
+
/^https?:\/\/169\.254\./,
|
|
4100
|
+
// Link-local / AWS metadata
|
|
4101
|
+
/metadata\.google\.internal/i,
|
|
4102
|
+
// GCP metadata
|
|
4103
|
+
/^https?:\/\/metadata\b/i,
|
|
4104
|
+
// Generic metadata endpoint
|
|
4105
|
+
// IPv6 bypass patterns
|
|
4106
|
+
/^https?:\/\/\[fe80:/i,
|
|
4107
|
+
// IPv6 link-local
|
|
4108
|
+
/^https?:\/\/\[fc00:/i,
|
|
4109
|
+
// IPv6 unique local
|
|
4110
|
+
/^https?:\/\/\[fd[0-9a-f]{2}:/i,
|
|
4111
|
+
// IPv6 unique local (fd00::/8)
|
|
4112
|
+
/^https?:\/\/\[::ffff:127\./i,
|
|
4113
|
+
// IPv4-mapped IPv6 loopback
|
|
4114
|
+
/^https?:\/\/\[::ffff:10\./i,
|
|
4115
|
+
// IPv4-mapped IPv6 private
|
|
4116
|
+
/^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
|
|
4117
|
+
// IPv4-mapped IPv6 private
|
|
4118
|
+
/^https?:\/\/\[::ffff:192\.168\./i,
|
|
4119
|
+
// IPv4-mapped IPv6 private
|
|
4120
|
+
/^https?:\/\/\[::ffff:169\.254\./i,
|
|
4121
|
+
// IPv4-mapped IPv6 link-local
|
|
4122
|
+
// Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
|
|
4123
|
+
/^https?:\/\/0x[0-9a-f]+\b/i,
|
|
4124
|
+
// Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
|
|
4125
|
+
/^https?:\/\/0[0-7]{1,3}\./
|
|
4126
|
+
];
|
|
4127
|
+
function detectDecimalIP(value) {
|
|
4128
|
+
const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
|
|
4129
|
+
if (!match || !match[1]) return false;
|
|
4130
|
+
const decimal = parseInt(match[1], 10);
|
|
4131
|
+
if (isNaN(decimal) || decimal > 4294967295) return false;
|
|
4132
|
+
return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
|
|
4133
|
+
decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
|
|
4134
|
+
decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
|
|
4135
|
+
decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
|
|
4136
|
+
decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
|
|
4137
|
+
decimal === 0;
|
|
4138
|
+
}
|
|
4139
|
+
function detectSSRF(value) {
|
|
4140
|
+
for (const pattern of SSRF_PATTERNS) {
|
|
4141
|
+
if (pattern.test(value)) return true;
|
|
4142
|
+
}
|
|
4143
|
+
if (detectDecimalIP(value)) return true;
|
|
4144
|
+
return false;
|
|
4145
|
+
}
|
|
4146
|
+
var SQL_INJECTION_PATTERNS = [
|
|
4147
|
+
/'\s{0,20}(OR|AND)\s{0,20}'.{0,200}'/i,
|
|
4148
|
+
// ' OR '1'='1 — bounded to prevent ReDoS
|
|
4149
|
+
/'\s{0,10};\s{0,10}(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|EXEC)/i,
|
|
4150
|
+
// '; DROP TABLE
|
|
4151
|
+
/UNION\s+(ALL\s+)?SELECT/i,
|
|
4152
|
+
// UNION SELECT
|
|
4153
|
+
/--\s*$/m,
|
|
4154
|
+
// SQL comment at end of line
|
|
4155
|
+
/\/\*.{0,500}?\*\//,
|
|
4156
|
+
// SQL block comment — bounded + non-greedy
|
|
4157
|
+
/\bSLEEP\s*\(/i,
|
|
4158
|
+
// Time-based injection
|
|
4159
|
+
/\bBENCHMARK\s*\(/i,
|
|
4160
|
+
// MySQL benchmark
|
|
4161
|
+
/\bWAITFOR\s+DELAY/i,
|
|
4162
|
+
// MSSQL delay
|
|
4163
|
+
/\b(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)\b/i
|
|
4164
|
+
// File operations
|
|
4165
|
+
];
|
|
4166
|
+
function detectSQLInjection(value) {
|
|
4167
|
+
for (const pattern of SQL_INJECTION_PATTERNS) {
|
|
4168
|
+
if (pattern.test(value)) return true;
|
|
4169
|
+
}
|
|
4170
|
+
return false;
|
|
4171
|
+
}
|
|
4172
|
+
function checkLengthLimits(value, maxLength = 4096) {
|
|
4173
|
+
return value.length <= maxLength;
|
|
4174
|
+
}
|
|
4175
|
+
var ENTROPY_THRESHOLD = 4.5;
|
|
4176
|
+
var MIN_LENGTH_FOR_ENTROPY_CHECK = 32;
|
|
4177
|
+
function checkEntropyLimits(value) {
|
|
4178
|
+
if (value.length < MIN_LENGTH_FOR_ENTROPY_CHECK) return true;
|
|
4179
|
+
const entropy = calculateShannonEntropy(value);
|
|
4180
|
+
return entropy <= ENTROPY_THRESHOLD;
|
|
4181
|
+
}
|
|
4182
|
+
function calculateShannonEntropy(str) {
|
|
4183
|
+
const freq = /* @__PURE__ */ new Map();
|
|
4184
|
+
for (const char of str) {
|
|
4185
|
+
freq.set(char, (freq.get(char) ?? 0) + 1);
|
|
4186
|
+
}
|
|
4187
|
+
let entropy = 0;
|
|
4188
|
+
const len = str.length;
|
|
4189
|
+
for (const count of freq.values()) {
|
|
4190
|
+
const p = count / len;
|
|
4191
|
+
if (p > 0) {
|
|
4192
|
+
entropy -= p * Math.log2(p);
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
return entropy;
|
|
4196
|
+
}
|
|
4197
|
+
function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG2) {
|
|
4198
|
+
const threats = [];
|
|
4199
|
+
if (typeof value !== "string") {
|
|
4200
|
+
if (typeof value === "object" && value !== null) {
|
|
4201
|
+
return sanitizeObject(field, value, config);
|
|
4202
|
+
}
|
|
4203
|
+
return { safe: true, threats: [] };
|
|
4204
|
+
}
|
|
4205
|
+
if (config.pathTraversal && detectPathTraversal(value)) {
|
|
4206
|
+
threats.push({
|
|
4207
|
+
type: "PATH_TRAVERSAL",
|
|
4208
|
+
field,
|
|
4209
|
+
value: truncate(value, 100),
|
|
4210
|
+
description: "Path traversal pattern detected"
|
|
4211
|
+
});
|
|
4212
|
+
}
|
|
4213
|
+
if (config.shellInjection && detectShellInjection(value)) {
|
|
4214
|
+
threats.push({
|
|
4215
|
+
type: "SHELL_INJECTION",
|
|
4216
|
+
field,
|
|
4217
|
+
value: truncate(value, 100),
|
|
4218
|
+
description: "Shell injection pattern detected"
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
if (config.wildcardAbuse && detectWildcardAbuse(value)) {
|
|
4222
|
+
threats.push({
|
|
4223
|
+
type: "WILDCARD_ABUSE",
|
|
4224
|
+
field,
|
|
4225
|
+
value: truncate(value, 100),
|
|
4226
|
+
description: "Wildcard abuse pattern detected"
|
|
4227
|
+
});
|
|
4228
|
+
}
|
|
4229
|
+
if (!checkLengthLimits(value, config.lengthLimit)) {
|
|
4230
|
+
threats.push({
|
|
4231
|
+
type: "LENGTH_EXCEEDED",
|
|
4232
|
+
field,
|
|
4233
|
+
value: `[${value.length} chars]`,
|
|
4234
|
+
description: `Value exceeds maximum length of ${config.lengthLimit}`
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
if (config.entropyLimit && !checkEntropyLimits(value)) {
|
|
4238
|
+
threats.push({
|
|
4239
|
+
type: "HIGH_ENTROPY",
|
|
4240
|
+
field,
|
|
4241
|
+
value: truncate(value, 100),
|
|
4242
|
+
description: "High entropy string detected - possible encoded payload"
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
if (config.ssrf && detectSSRF(value)) {
|
|
4246
|
+
threats.push({
|
|
4247
|
+
type: "SSRF",
|
|
4248
|
+
field,
|
|
4249
|
+
value: truncate(value, 100),
|
|
4250
|
+
description: "Server-side request forgery pattern detected \u2014 internal/metadata URL blocked"
|
|
4251
|
+
});
|
|
4252
|
+
}
|
|
4253
|
+
if (config.sqlInjection && detectSQLInjection(value)) {
|
|
4254
|
+
threats.push({
|
|
4255
|
+
type: "SQL_INJECTION",
|
|
4256
|
+
field,
|
|
4257
|
+
value: truncate(value, 100),
|
|
4258
|
+
description: "SQL injection pattern detected"
|
|
4259
|
+
});
|
|
4260
|
+
}
|
|
4261
|
+
return { safe: threats.length === 0, threats };
|
|
4262
|
+
}
|
|
4263
|
+
function sanitizeObject(basePath, obj, config) {
|
|
4264
|
+
const threats = [];
|
|
4265
|
+
if (Array.isArray(obj)) {
|
|
4266
|
+
for (let i = 0; i < obj.length; i++) {
|
|
4267
|
+
const result = sanitizeInput(`${basePath}[${i}]`, obj[i], config);
|
|
4268
|
+
threats.push(...result.threats);
|
|
4269
|
+
}
|
|
4270
|
+
} else {
|
|
4271
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
4272
|
+
const result = sanitizeInput(`${basePath}.${key}`, val, config);
|
|
4273
|
+
threats.push(...result.threats);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
return { safe: threats.length === 0, threats };
|
|
4277
|
+
}
|
|
4278
|
+
function truncate(str, maxLen) {
|
|
4279
|
+
return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
|
|
4280
|
+
}
|
|
4281
|
+
|
|
3869
4282
|
// src/proxy.ts
|
|
3870
4283
|
init_config();
|
|
3871
4284
|
|
|
@@ -4131,7 +4544,12 @@ var SolonGateProxy = class {
|
|
|
4131
4544
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
4132
4545
|
if (this.config.apiKey) {
|
|
4133
4546
|
if (this.config.apiKey.startsWith("sg_test_")) {
|
|
4134
|
-
|
|
4547
|
+
const nodeEnv = process.env.NODE_ENV ?? "";
|
|
4548
|
+
if (nodeEnv === "production") {
|
|
4549
|
+
log2("ERROR: Test API keys (sg_test_) cannot be used in production. Use a sg_live_ key.");
|
|
4550
|
+
process.exit(1);
|
|
4551
|
+
}
|
|
4552
|
+
log2("Using test API key \u2014 skipping online validation (non-production mode).");
|
|
4135
4553
|
} else {
|
|
4136
4554
|
log2(`Validating license with ${apiUrl}...`);
|
|
4137
4555
|
try {
|
|
@@ -4332,7 +4750,26 @@ var SolonGateProxy = class {
|
|
|
4332
4750
|
});
|
|
4333
4751
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
4334
4752
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
4335
|
-
|
|
4753
|
+
const uri = request.params.uri;
|
|
4754
|
+
const uriCheck = sanitizeInput("resource.uri", uri);
|
|
4755
|
+
if (!uriCheck.safe) {
|
|
4756
|
+
const threats = uriCheck.threats.map((t) => `${t.type}: ${t.description}`).join("; ");
|
|
4757
|
+
log2(`DENY resource read: ${uri} \u2014 ${threats}`);
|
|
4758
|
+
throw new Error(`Resource URI blocked by security policy: ${threats}`);
|
|
4759
|
+
}
|
|
4760
|
+
if (/^file:\/\//i.test(uri)) {
|
|
4761
|
+
const path = uri.replace(/^file:\/\/\/?/i, "/");
|
|
4762
|
+
if (detectPathTraversal(path)) {
|
|
4763
|
+
log2(`DENY resource read: ${uri} \u2014 path traversal in file URI`);
|
|
4764
|
+
throw new Error("Resource URI blocked: path traversal detected");
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
if (/^https?:\/\//i.test(uri) && detectSSRF(uri)) {
|
|
4768
|
+
log2(`DENY resource read: ${uri} \u2014 SSRF pattern`);
|
|
4769
|
+
throw new Error("Resource URI blocked: internal/metadata URL not allowed");
|
|
4770
|
+
}
|
|
4771
|
+
log2(`Resource read: ${uri}`);
|
|
4772
|
+
return await this.client.readResource({ uri });
|
|
4336
4773
|
});
|
|
4337
4774
|
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
4338
4775
|
if (!this.client) return { resourceTemplates: [] };
|
|
@@ -4352,9 +4789,19 @@ var SolonGateProxy = class {
|
|
|
4352
4789
|
});
|
|
4353
4790
|
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
4354
4791
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
4792
|
+
const args = request.params.arguments;
|
|
4793
|
+
if (args && typeof args === "object") {
|
|
4794
|
+
const argsCheck = sanitizeInput("prompt.arguments", args);
|
|
4795
|
+
if (!argsCheck.safe) {
|
|
4796
|
+
const threats = argsCheck.threats.map((t) => `${t.type}: ${t.description}`).join("; ");
|
|
4797
|
+
log2(`DENY prompt get: ${request.params.name} \u2014 ${threats}`);
|
|
4798
|
+
throw new Error(`Prompt arguments blocked by security policy: ${threats}`);
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
log2(`Prompt get: ${request.params.name}`);
|
|
4355
4802
|
return await this.client.getPrompt({
|
|
4356
4803
|
name: request.params.name,
|
|
4357
|
-
arguments:
|
|
4804
|
+
arguments: args
|
|
4358
4805
|
});
|
|
4359
4806
|
});
|
|
4360
4807
|
}
|
package/dist/init.js
CHANGED
|
@@ -67,18 +67,14 @@ function isAlreadyProtected(server) {
|
|
|
67
67
|
function wrapServer(server, policy) {
|
|
68
68
|
const env = { ...server.env ?? {} };
|
|
69
69
|
env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
|
|
70
|
+
const proxyArgs = ["-y", "@solongate/proxy"];
|
|
71
|
+
if (policy) {
|
|
72
|
+
proxyArgs.push("--policy", policy);
|
|
73
|
+
}
|
|
74
|
+
proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
|
|
70
75
|
return {
|
|
71
76
|
command: "npx",
|
|
72
|
-
args:
|
|
73
|
-
"-y",
|
|
74
|
-
"@solongate/proxy",
|
|
75
|
-
"--policy",
|
|
76
|
-
policy,
|
|
77
|
-
"--verbose",
|
|
78
|
-
"--",
|
|
79
|
-
server.command,
|
|
80
|
-
...server.args ?? []
|
|
81
|
-
],
|
|
77
|
+
args: proxyArgs,
|
|
82
78
|
env
|
|
83
79
|
};
|
|
84
80
|
}
|
|
@@ -127,8 +123,7 @@ USAGE
|
|
|
127
123
|
|
|
128
124
|
OPTIONS
|
|
129
125
|
--config <path> Path to MCP config file (default: auto-detect)
|
|
130
|
-
--policy <file> Custom policy JSON file (
|
|
131
|
-
Auto-detects policy.json in current directory
|
|
126
|
+
--policy <file> Custom policy JSON file (auto-detects policy.json)
|
|
132
127
|
--api-key <key> SolonGate API key (sg_live_... or sg_test_...)
|
|
133
128
|
--all Protect all servers without prompting
|
|
134
129
|
-h, --help Show this help message
|
|
@@ -685,7 +680,7 @@ async function main() {
|
|
|
685
680
|
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
686
681
|
process.exit(1);
|
|
687
682
|
}
|
|
688
|
-
let policyValue
|
|
683
|
+
let policyValue;
|
|
689
684
|
if (options.policy) {
|
|
690
685
|
const policyPath = resolve(options.policy);
|
|
691
686
|
if (existsSync(policyPath)) {
|
|
@@ -701,7 +696,7 @@ async function main() {
|
|
|
701
696
|
policyValue = "./policy.json";
|
|
702
697
|
console.log(` Policy: ${defaultPolicy} (auto-detected)`);
|
|
703
698
|
} else {
|
|
704
|
-
console.log(` Policy:
|
|
699
|
+
console.log(` Policy: cloud-managed (fetched via API key)`);
|
|
705
700
|
}
|
|
706
701
|
}
|
|
707
702
|
await sleep(300);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
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": {
|