@solongate/proxy 0.6.8 → 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/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
- throw new Error(
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 = "restricted";
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 restricted -- npx @playwright/mcp@latest\n solongate-proxy --upstream-url http://localhost:3001/mcp\n solongate-proxy --config solongate.json\n"
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 PRESETS;
233
+ var DEFAULT_POLICY;
238
234
  var init_config = __esm({
239
235
  "src/config.ts"() {
240
236
  "use strict";
241
- PRESETS = {
242
- restricted: {
243
- id: "restricted",
244
- name: "Restricted",
245
- description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
246
- version: 1,
247
- rules: [
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 (default: restricted)
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 = "restricted";
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: restricted (default)`);
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: "restricted",
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 <preset> Policy preset (default: restricted)
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 read-only
1489
+ npx @solongate/proxy create db-tools --policy ./policy.json
1779
1490
  `);
1780
1491
  }
1781
1492
  function createProject(dir, name, _policy) {
@@ -2061,7 +1772,7 @@ function parseCliArgs() {
2061
1772
  }
2062
1773
  }
2063
1774
  if (!apiKey) {
2064
- log5("ERROR: API key not found.");
1775
+ log5(red("ERROR: API key not found."));
2065
1776
  log5("");
2066
1777
  log5("Set it in .env file:");
2067
1778
  log5(" SOLONGATE_API_KEY=sg_live_...");
@@ -2071,53 +1782,169 @@ function parseCliArgs() {
2071
1782
  process.exit(1);
2072
1783
  }
2073
1784
  if (!apiKey.startsWith("sg_live_")) {
2074
- log5("ERROR: Pull/push requires a live API key (sg_live_...).");
1785
+ log5(red("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
2075
1786
  process.exit(1);
2076
1787
  }
2077
1788
  return { command, apiKey, file: resolve5(file), policyId };
2078
1789
  }
2079
1790
  async function listPolicies(apiKey) {
2080
- const res = await fetch("https://api.solongate.com/api/v1/policies", {
1791
+ const res = await fetch(`${API_URL}/api/v1/policies`, {
2081
1792
  headers: { "Authorization": `Bearer ${apiKey}` }
2082
1793
  });
2083
1794
  if (!res.ok) throw new Error(`Failed to list policies (${res.status})`);
2084
1795
  const data = await res.json();
2085
1796
  return data.policies ?? [];
2086
1797
  }
1798
+ async function list(apiKey, policyId) {
1799
+ const policies = await listPolicies(apiKey);
1800
+ if (policies.length === 0) {
1801
+ log5(yellow("No policies found. Create one in the dashboard first."));
1802
+ log5(dim(" https://dashboard.solongate.com/policies"));
1803
+ return;
1804
+ }
1805
+ if (policyId) {
1806
+ const match = policies.find((p) => p.id === policyId);
1807
+ if (!match) {
1808
+ log5(red(`Policy not found: ${policyId}`));
1809
+ log5("");
1810
+ log5("Available policies:");
1811
+ for (const p of policies) {
1812
+ log5(` ${dim("\u2022")} ${p.id}`);
1813
+ }
1814
+ process.exit(1);
1815
+ }
1816
+ const full = await fetchCloudPolicy(apiKey, API_URL, policyId);
1817
+ printPolicyDetail(full);
1818
+ return;
1819
+ }
1820
+ log5("");
1821
+ log5(bold(` Policies (${policies.length})`));
1822
+ log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1823
+ log5("");
1824
+ for (const p of policies) {
1825
+ try {
1826
+ const full = await fetchCloudPolicy(apiKey, API_URL, p.id);
1827
+ printPolicySummary(p, full.rules);
1828
+ } catch {
1829
+ printPolicySummary(p, []);
1830
+ }
1831
+ }
1832
+ log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1833
+ log5("");
1834
+ log5(` ${dim("View details:")} solongate-proxy list --policy-id <ID>`);
1835
+ log5(` ${dim("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
1836
+ log5(` ${dim("Push policy:")} solongate-proxy push --policy-id <ID>`);
1837
+ log5("");
1838
+ }
1839
+ function printPolicySummary(p, rules) {
1840
+ const ruleCount = rules.length;
1841
+ const allowCount = rules.filter((r) => r.effect === "ALLOW").length;
1842
+ const denyCount = rules.filter((r) => r.effect === "DENY").length;
1843
+ log5(` ${cyan(p.id)}`);
1844
+ log5(` ${bold(p.name)} ${dim(`v${p.version ?? "?"}`)}`);
1845
+ log5(` ${dim("Rules:")} ${ruleCount} ${green(`${allowCount} ALLOW`)} ${red(`${denyCount} DENY`)}`);
1846
+ if (p.created_at) {
1847
+ log5(` ${dim("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
1848
+ }
1849
+ log5("");
1850
+ }
1851
+ function printPolicyDetail(policy) {
1852
+ log5("");
1853
+ log5(bold(` ${policy.name}`));
1854
+ log5(` ${dim("ID:")} ${cyan(policy.id)} ${dim("Version:")} ${policy.version} ${dim("Rules:")} ${policy.rules.length}`);
1855
+ log5("");
1856
+ if (policy.rules.length === 0) {
1857
+ log5(yellow(" No rules defined."));
1858
+ log5("");
1859
+ return;
1860
+ }
1861
+ log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1862
+ for (const rule of policy.rules) {
1863
+ const effectColor = rule.effect === "ALLOW" ? green : red;
1864
+ log5("");
1865
+ log5(` ${effectColor(rule.effect.padEnd(5))} ${bold(rule.toolPattern)} ${dim(`P:${rule.priority}`)}`);
1866
+ if (rule.description) {
1867
+ log5(` ${dim(rule.description)}`);
1868
+ }
1869
+ log5(` ${dim(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
1870
+ if (rule.pathConstraints) {
1871
+ const pc = rule.pathConstraints;
1872
+ if (pc.rootDirectory) log5(` ${magenta("ROOT")} ${pc.rootDirectory}`);
1873
+ if (pc.allowed?.length) log5(` ${green("PATHS")} ${pc.allowed.join(", ")}`);
1874
+ if (pc.denied?.length) log5(` ${red("DENY")} ${pc.denied.join(", ")}`);
1875
+ }
1876
+ if (rule.commandConstraints) {
1877
+ const cc = rule.commandConstraints;
1878
+ if (cc.allowed?.length) log5(` ${green("CMDS")} ${cc.allowed.join(", ")}`);
1879
+ if (cc.denied?.length) log5(` ${red("DENY")} ${cc.denied.join(", ")}`);
1880
+ }
1881
+ if (rule.filenameConstraints) {
1882
+ const fc = rule.filenameConstraints;
1883
+ if (fc.allowed?.length) log5(` ${green("FILES")} ${fc.allowed.join(", ")}`);
1884
+ if (fc.denied?.length) log5(` ${red("DENY")} ${fc.denied.join(", ")}`);
1885
+ }
1886
+ if (rule.urlConstraints) {
1887
+ const uc = rule.urlConstraints;
1888
+ if (uc.allowed?.length) log5(` ${green("URLS")} ${uc.allowed.join(", ")}`);
1889
+ if (uc.denied?.length) log5(` ${red("DENY")} ${uc.denied.join(", ")}`);
1890
+ }
1891
+ }
1892
+ log5("");
1893
+ log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1894
+ log5("");
1895
+ }
2087
1896
  async function pull(apiKey, file, policyId) {
2088
- const apiUrl = "https://api.solongate.com";
2089
1897
  if (!policyId) {
2090
1898
  const policies = await listPolicies(apiKey);
2091
1899
  if (policies.length === 0) {
2092
- log5("No policies found. Create one in the dashboard first.");
1900
+ log5(red("No policies found. Create one in the dashboard first."));
2093
1901
  process.exit(1);
2094
1902
  }
2095
- if (policies.length > 1) {
2096
- log5(`Found ${policies.length} policies:
2097
- `);
1903
+ if (policies.length === 1) {
1904
+ policyId = policies[0].id;
1905
+ log5(dim(`Auto-selecting only policy: ${policyId}`));
1906
+ } else {
1907
+ log5(yellow(`Found ${policies.length} policies:`));
1908
+ log5("");
2098
1909
  for (const p of policies) {
2099
- log5(` ${p.id} ${p.name} (${(p.rules || []).length} rules)`);
1910
+ log5(` ${cyan(p.id)} ${p.name} ${dim(`v${p.version ?? "?"}`)}`);
2100
1911
  }
2101
1912
  log5("");
2102
1913
  log5("Use --policy-id <ID> to specify which one to pull.");
2103
1914
  process.exit(1);
2104
1915
  }
2105
1916
  }
2106
- log5(`Pulling policy from dashboard...`);
2107
- const policy = await fetchCloudPolicy(apiKey, apiUrl, policyId);
2108
- const json = JSON.stringify(policy, null, 2) + "\n";
1917
+ log5(`Pulling ${cyan(policyId)} from dashboard...`);
1918
+ const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
1919
+ const { id: _id, ...policyWithoutId } = policy;
1920
+ const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
2109
1921
  writeFileSync5(file, json, "utf-8");
2110
- log5(`Saved: ${file}`);
2111
- log5(` Name: ${policy.name}`);
2112
- log5(` Version: ${policy.version}`);
2113
- log5(` Rules: ${policy.rules.length}`);
2114
1922
  log5("");
2115
- log5("Done. Policy pulled from dashboard to local file.");
1923
+ log5(green(" Saved to: ") + file);
1924
+ log5(` ${dim("Name:")} ${policy.name}`);
1925
+ log5(` ${dim("Version:")} ${policy.version}`);
1926
+ log5(` ${dim("Rules:")} ${policy.rules.length}`);
1927
+ log5("");
1928
+ log5(dim("The policy file does not contain an ID."));
1929
+ log5(dim("Use --policy-id to specify the target when pushing/pulling."));
1930
+ log5("");
2116
1931
  }
2117
- async function push(apiKey, file) {
2118
- const apiUrl = "https://api.solongate.com";
1932
+ async function push(apiKey, file, policyId) {
2119
1933
  if (!existsSync6(file)) {
2120
- log5(`ERROR: File not found: ${file}`);
1934
+ log5(red(`ERROR: File not found: ${file}`));
1935
+ process.exit(1);
1936
+ }
1937
+ if (!policyId) {
1938
+ log5(red("ERROR: --policy-id is required for push."));
1939
+ log5("");
1940
+ log5("This determines which cloud policy to update.");
1941
+ log5("");
1942
+ log5("Usage:");
1943
+ log5(" solongate-proxy push --policy-id my-policy");
1944
+ log5(" solongate-proxy push --policy-id my-policy --file custom.json");
1945
+ log5("");
1946
+ log5("List your policies:");
1947
+ log5(" solongate-proxy list");
2121
1948
  process.exit(1);
2122
1949
  }
2123
1950
  const content = readFileSync5(file, "utf-8");
@@ -2125,21 +1952,26 @@ async function push(apiKey, file) {
2125
1952
  try {
2126
1953
  policy = JSON.parse(content);
2127
1954
  } catch {
2128
- log5(`ERROR: Invalid JSON in ${file}`);
1955
+ log5(red(`ERROR: Invalid JSON in ${file}`));
2129
1956
  process.exit(1);
2130
1957
  }
2131
- log5(`Pushing policy to dashboard...`);
2132
- log5(` File: ${file}`);
2133
- log5(` Name: ${policy.name || "Unnamed"}`);
2134
- log5(` Rules: ${(policy.rules || []).length}`);
2135
- const res = await fetch(`${apiUrl}/api/v1/policies`, {
2136
- method: "POST",
1958
+ log5(`Pushing to ${cyan(policyId)}...`);
1959
+ log5(` ${dim("File:")} ${file}`);
1960
+ log5(` ${dim("Name:")} ${policy.name || "Unnamed"}`);
1961
+ log5(` ${dim("Rules:")} ${(policy.rules || []).length}`);
1962
+ const checkRes = await fetch(`${API_URL}/api/v1/policies/${policyId}`, {
1963
+ headers: { "Authorization": `Bearer ${apiKey}` }
1964
+ });
1965
+ const method = checkRes.ok ? "PUT" : "POST";
1966
+ const url = checkRes.ok ? `${API_URL}/api/v1/policies/${policyId}` : `${API_URL}/api/v1/policies`;
1967
+ const res = await fetch(url, {
1968
+ method,
2137
1969
  headers: {
2138
1970
  "Authorization": `Bearer ${apiKey}`,
2139
1971
  "Content-Type": "application/json"
2140
1972
  },
2141
1973
  body: JSON.stringify({
2142
- id: policy.id || "default",
1974
+ id: policyId,
2143
1975
  name: policy.name || "Local Policy",
2144
1976
  description: policy.description || "Pushed from local file",
2145
1977
  version: policy.version || 1,
@@ -2148,13 +1980,15 @@ async function push(apiKey, file) {
2148
1980
  });
2149
1981
  if (!res.ok) {
2150
1982
  const body = await res.text().catch(() => "");
2151
- log5(`ERROR: Push failed (${res.status}): ${body}`);
1983
+ log5(red(`ERROR: Push failed (${res.status}): ${body}`));
2152
1984
  process.exit(1);
2153
1985
  }
2154
1986
  const data = await res.json();
2155
- log5(` Cloud version: ${data._version ?? "created"}`);
2156
1987
  log5("");
2157
- log5("Done. Policy pushed from local file to dashboard.");
1988
+ log5(green(` Pushed to cloud: v${data._version ?? "created"}`));
1989
+ log5(` ${dim("Policy ID:")} ${policyId}`);
1990
+ log5(` ${dim("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
1991
+ log5("");
2158
1992
  }
2159
1993
  async function main4() {
2160
1994
  const { command, apiKey, file, policyId } = parseCliArgs();
@@ -2162,24 +1996,45 @@ async function main4() {
2162
1996
  if (command === "pull") {
2163
1997
  await pull(apiKey, file, policyId);
2164
1998
  } else if (command === "push") {
2165
- await push(apiKey, file);
1999
+ await push(apiKey, file, policyId);
2000
+ } else if (command === "list" || command === "ls") {
2001
+ await list(apiKey, policyId);
2166
2002
  } else {
2167
- log5(`Unknown command: ${command}`);
2168
- log5("Usage: solongate-proxy pull|push [--file policy.json]");
2003
+ log5(red(`Unknown command: ${command}`));
2004
+ log5("");
2005
+ log5(bold("Usage:"));
2006
+ log5(" solongate-proxy list List all policies");
2007
+ log5(" solongate-proxy list --policy-id <ID> Show policy details");
2008
+ log5(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
2009
+ log5(" solongate-proxy push --policy-id <ID> Push local file to cloud");
2010
+ log5("");
2011
+ log5(bold("Flags:"));
2012
+ log5(" --policy-id, --id <ID> Cloud policy ID (required for push)");
2013
+ log5(" --file, -f <path> Local file path (default: policy.json)");
2014
+ log5(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
2015
+ log5("");
2169
2016
  process.exit(1);
2170
2017
  }
2171
2018
  } catch (err) {
2172
- log5(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
2019
+ log5(red(`ERROR: ${err instanceof Error ? err.message : String(err)}`));
2173
2020
  process.exit(1);
2174
2021
  }
2175
2022
  }
2176
- var log5;
2023
+ var log5, dim, bold, green, red, yellow, cyan, magenta, API_URL;
2177
2024
  var init_pull_push = __esm({
2178
2025
  "src/pull-push.ts"() {
2179
2026
  "use strict";
2180
2027
  init_config();
2181
- log5 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
2028
+ log5 = (...args) => process.stderr.write(`${args.map(String).join(" ")}
2182
2029
  `);
2030
+ dim = (s) => `\x1B[2m${s}\x1B[0m`;
2031
+ bold = (s) => `\x1B[1m${s}\x1B[0m`;
2032
+ green = (s) => `\x1B[32m${s}\x1B[0m`;
2033
+ red = (s) => `\x1B[31m${s}\x1B[0m`;
2034
+ yellow = (s) => `\x1B[33m${s}\x1B[0m`;
2035
+ cyan = (s) => `\x1B[36m${s}\x1B[0m`;
2036
+ magenta = (s) => `\x1B[35m${s}\x1B[0m`;
2037
+ API_URL = "https://api.solongate.com";
2183
2038
  main4();
2184
2039
  }
2185
2040
  });
@@ -2296,6 +2151,14 @@ var PolicyRuleSchema = z.object({
2296
2151
  allowed: z.array(z.string()).optional(),
2297
2152
  denied: z.array(z.string()).optional()
2298
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(),
2299
2162
  enabled: z.boolean().default(true),
2300
2163
  createdAt: z.string().datetime(),
2301
2164
  updatedAt: z.string().datetime()
@@ -2481,11 +2344,48 @@ function matchSegmentGlob(segment, pattern) {
2481
2344
  }
2482
2345
  return segment === pattern;
2483
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
+ ]);
2484
2366
  function extractPathArguments(args) {
2485
2367
  const paths = [];
2486
- for (const value of Object.values(args)) {
2487
- if (typeof value === "string" && (value.includes("/") || value.includes("\\"))) {
2488
- paths.push(value);
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;
2489
2389
  }
2490
2390
  }
2491
2391
  return paths;
@@ -2499,15 +2399,62 @@ var COMMAND_FIELDS = /* @__PURE__ */ new Set([
2499
2399
  "shell",
2500
2400
  "exec",
2501
2401
  "sql",
2502
- "expression"
2402
+ "expression",
2403
+ "function"
2503
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
+ ];
2504
2423
  function extractCommandArguments(args) {
2505
2424
  const commands = [];
2506
- for (const [key, value] of Object.entries(args)) {
2507
- if (typeof value !== "string") continue;
2508
- if (COMMAND_FIELDS.has(key.toLowerCase())) {
2509
- commands.push(value);
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
+ }
2510
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
+ }
2454
+ }
2455
+ }
2456
+ for (const [key, value] of Object.entries(args)) {
2457
+ scanValue(key, value);
2511
2458
  }
2512
2459
  return commands;
2513
2460
  }
@@ -2553,6 +2500,224 @@ function isCommandAllowed(command, constraints) {
2553
2500
  }
2554
2501
  return true;
2555
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
+ }
2556
2721
  function ruleMatchesRequest(rule, request) {
2557
2722
  if (!rule.enabled) return false;
2558
2723
  if (rule.permission !== request.requiredPermission) return false;
@@ -2581,6 +2746,22 @@ function ruleMatchesRequest(rule, request) {
2581
2746
  if (!satisfied) return false;
2582
2747
  }
2583
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
+ }
2584
2765
  return true;
2585
2766
  }
2586
2767
  function toolPatternMatches(pattern, toolName) {
@@ -2671,6 +2852,16 @@ function commandConstraintsMatch(constraints, args) {
2671
2852
  if (commands.length === 0) return true;
2672
2853
  return commands.every((cmd) => isCommandAllowed(cmd, constraints));
2673
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
+ }
2674
2865
  function evaluatePolicy(policySet, request) {
2675
2866
  const startTime = performance.now();
2676
2867
  const sortedRules = [...policySet.rules].sort(
@@ -3051,6 +3242,11 @@ function resolveConfig(userConfig) {
3051
3242
  "Token secret is shorter than 32 characters. Use a longer secret for production."
3052
3243
  );
3053
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
+ }
3054
3250
  return { config, warnings };
3055
3251
  }
3056
3252
  async function interceptToolCall(params, upstreamCall, options) {
@@ -3556,6 +3752,12 @@ var SolonGate = class {
3556
3752
  async validateLicense() {
3557
3753
  if (this.licenseValidated) return;
3558
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
+ }
3559
3761
  this.licenseValidated = true;
3560
3762
  return;
3561
3763
  }
@@ -3722,6 +3924,361 @@ var SolonGate = class {
3722
3924
  }
3723
3925
  };
3724
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
+
3725
4282
  // src/proxy.ts
3726
4283
  init_config();
3727
4284
 
@@ -3744,6 +4301,7 @@ var PolicySyncManager = class {
3744
4301
  pollTimer = null;
3745
4302
  watcher = null;
3746
4303
  isLiveKey;
4304
+ /** The cloud policy ID from --policy-id flag. This is the ONLY source of truth for which cloud policy to use. */
3747
4305
  policyId;
3748
4306
  constructor(opts) {
3749
4307
  this.localPath = opts.localPath;
@@ -3872,17 +4430,24 @@ var PolicySyncManager = class {
3872
4430
  }
3873
4431
  /**
3874
4432
  * Push policy to cloud API.
4433
+ * Uses this.policyId (from --policy-id CLI flag) as the cloud policy ID.
4434
+ * Falls back to policy.id from local file only if --policy-id was not set.
3875
4435
  */
3876
4436
  async pushToCloud(policy) {
3877
- const url = `${this.apiUrl}/api/v1/policies`;
4437
+ const cloudId = this.policyId || policy.id || "default";
4438
+ const existingRes = await fetch(`${this.apiUrl}/api/v1/policies/${cloudId}`, {
4439
+ headers: { "Authorization": `Bearer ${this.apiKey}` }
4440
+ });
4441
+ const method = existingRes.ok ? "PUT" : "POST";
4442
+ const url = existingRes.ok ? `${this.apiUrl}/api/v1/policies/${cloudId}` : `${this.apiUrl}/api/v1/policies`;
3878
4443
  const res = await fetch(url, {
3879
- method: "POST",
4444
+ method,
3880
4445
  headers: {
3881
4446
  "Authorization": `Bearer ${this.apiKey}`,
3882
4447
  "Content-Type": "application/json"
3883
4448
  },
3884
4449
  body: JSON.stringify({
3885
- id: policy.id || "default",
4450
+ id: cloudId,
3886
4451
  name: policy.name || "Default Policy",
3887
4452
  description: policy.description || "Synced from proxy",
3888
4453
  version: policy.version || 1,
@@ -3898,12 +4463,14 @@ var PolicySyncManager = class {
3898
4463
  }
3899
4464
  /**
3900
4465
  * Write policy to local file (with loop prevention).
4466
+ * Does NOT write the 'id' field — cloud ID is managed by --policy-id flag.
3901
4467
  */
3902
4468
  writeToFile(policy) {
3903
4469
  if (!this.localPath) return;
3904
4470
  this.skipNextWatch = true;
3905
4471
  try {
3906
- const json = JSON.stringify(policy, null, 2) + "\n";
4472
+ const { id: _id, ...rest } = policy;
4473
+ const json = JSON.stringify(rest, null, 2) + "\n";
3907
4474
  writeFileSync(this.localPath, json, "utf-8");
3908
4475
  } catch (err) {
3909
4476
  log(`File write error: ${err instanceof Error ? err.message : String(err)}`);
@@ -3911,10 +4478,10 @@ var PolicySyncManager = class {
3911
4478
  }
3912
4479
  }
3913
4480
  /**
3914
- * Compare two policies by rules content (ignoring timestamps).
4481
+ * Compare two policies by rules content (ignoring timestamps and id).
3915
4482
  */
3916
4483
  policiesEqual(a, b) {
3917
- if (a.id !== b.id || a.name !== b.name || a.rules.length !== b.rules.length) return false;
4484
+ if (a.name !== b.name || a.rules.length !== b.rules.length) return false;
3918
4485
  return JSON.stringify(a.rules) === JSON.stringify(b.rules);
3919
4486
  }
3920
4487
  };
@@ -3977,7 +4544,12 @@ var SolonGateProxy = class {
3977
4544
  const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
3978
4545
  if (this.config.apiKey) {
3979
4546
  if (this.config.apiKey.startsWith("sg_test_")) {
3980
- log2("Using test API key \u2014 skipping online validation.");
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).");
3981
4553
  } else {
3982
4554
  log2(`Validating license with ${apiUrl}...`);
3983
4555
  try {
@@ -4178,7 +4750,26 @@ var SolonGateProxy = class {
4178
4750
  });
4179
4751
  this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
4180
4752
  if (!this.client) throw new Error("Upstream client disconnected");
4181
- return await this.client.readResource({ uri: request.params.uri });
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 });
4182
4773
  });
4183
4774
  this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
4184
4775
  if (!this.client) return { resourceTemplates: [] };
@@ -4198,9 +4789,19 @@ var SolonGateProxy = class {
4198
4789
  });
4199
4790
  this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
4200
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}`);
4201
4802
  return await this.client.getPrompt({
4202
4803
  name: request.params.name,
4203
- arguments: request.params.arguments
4804
+ arguments: args
4204
4805
  });
4205
4806
  });
4206
4807
  }
@@ -4396,7 +4997,7 @@ async function main5() {
4396
4997
  await Promise.resolve().then(() => (init_create(), create_exports));
4397
4998
  return;
4398
4999
  }
4399
- if (subcommand === "pull" || subcommand === "push") {
5000
+ if (subcommand === "pull" || subcommand === "push" || subcommand === "list" || subcommand === "ls") {
4400
5001
  await Promise.resolve().then(() => (init_pull_push(), pull_push_exports));
4401
5002
  return;
4402
5003
  }