multicorn-shield 0.9.0 → 0.11.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.
@@ -22359,11 +22359,117 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22359
22359
 
22360
22360
  // package.json
22361
22361
  var package_default = {
22362
- version: "0.9.0"};
22362
+ version: "0.11.0"};
22363
22363
 
22364
22364
  // src/package-meta.ts
22365
22365
  var PACKAGE_VERSION = package_default.version;
22366
22366
 
22367
+ // src/extension/proxy-url-validator.ts
22368
+ var ProxyUrlValidationError = class extends Error {
22369
+ constructor(message) {
22370
+ super(message);
22371
+ this.name = "ProxyUrlValidationError";
22372
+ }
22373
+ };
22374
+ function isPrivateOrReservedIpv4(hostname3) {
22375
+ const parts = hostname3.split(".");
22376
+ if (parts.length !== 4) {
22377
+ return false;
22378
+ }
22379
+ const octets = [];
22380
+ for (const p of parts) {
22381
+ if (!/^\d{1,3}$/.test(p)) {
22382
+ return false;
22383
+ }
22384
+ const n = Number.parseInt(p, 10);
22385
+ if (Number.isNaN(n) || n < 0 || n > 255) {
22386
+ return false;
22387
+ }
22388
+ octets.push(n);
22389
+ }
22390
+ const [a, b] = octets;
22391
+ if (a === void 0 || b === void 0) {
22392
+ return false;
22393
+ }
22394
+ if (a === 127) {
22395
+ return true;
22396
+ }
22397
+ if (a === 10) {
22398
+ return true;
22399
+ }
22400
+ if (a === 172 && b >= 16 && b <= 31) {
22401
+ return true;
22402
+ }
22403
+ if (a === 192 && b === 168) {
22404
+ return true;
22405
+ }
22406
+ if (a === 169 && b === 254) {
22407
+ return true;
22408
+ }
22409
+ if (a === 0 && b === 0 && octets[2] === 0 && octets[3] === 0) {
22410
+ return true;
22411
+ }
22412
+ return false;
22413
+ }
22414
+ function isBlockedIpv6(host) {
22415
+ const h = host.split("%")[0]?.toLowerCase() ?? host.toLowerCase();
22416
+ if (h === "::1") {
22417
+ return true;
22418
+ }
22419
+ if (h.startsWith("fe80:")) {
22420
+ return true;
22421
+ }
22422
+ if (h.startsWith("fc") || h.startsWith("fd")) {
22423
+ return true;
22424
+ }
22425
+ return false;
22426
+ }
22427
+ function hostForValidation(hostname3) {
22428
+ if (hostname3.startsWith("[") && hostname3.endsWith("]")) {
22429
+ return hostname3.slice(1, -1);
22430
+ }
22431
+ return hostname3;
22432
+ }
22433
+ function assertSafeProxyUrl(raw, options) {
22434
+ let url2;
22435
+ try {
22436
+ url2 = new URL(raw);
22437
+ } catch {
22438
+ throw new ProxyUrlValidationError(`Invalid proxy URL: ${raw}`);
22439
+ }
22440
+ if (url2.protocol !== "https:" && url2.protocol !== "http:") {
22441
+ throw new ProxyUrlValidationError(
22442
+ `Unsupported proxy URL scheme: ${url2.protocol} - only https: and http: are allowed`
22443
+ );
22444
+ }
22445
+ const hostname3 = url2.hostname;
22446
+ if (hostname3.length === 0) {
22447
+ throw new ProxyUrlValidationError(`Proxy URL has no hostname: ${raw}`);
22448
+ }
22449
+ if (options?.allowPrivateNetworks === true) {
22450
+ return;
22451
+ }
22452
+ const host = hostForValidation(hostname3);
22453
+ if (host.toLowerCase() === "localhost") {
22454
+ throw new ProxyUrlValidationError(
22455
+ `Proxy URL points to a private/reserved network address: ${url2.hostname}`
22456
+ );
22457
+ }
22458
+ if (host.includes(":")) {
22459
+ if (isBlockedIpv6(host)) {
22460
+ throw new ProxyUrlValidationError(
22461
+ `Proxy URL points to a private/reserved network address: ${url2.hostname}`
22462
+ );
22463
+ }
22464
+ return;
22465
+ }
22466
+ if (isPrivateOrReservedIpv4(host)) {
22467
+ throw new ProxyUrlValidationError(
22468
+ `Proxy URL points to a private/reserved network address: ${url2.hostname}`
22469
+ );
22470
+ }
22471
+ }
22472
+
22367
22473
  // src/extension/proxy-client.ts
22368
22474
  var MCP_PROTOCOL_VERSION = "2024-11-05";
22369
22475
  var ProxyConfigFetchError = class extends Error {
@@ -22404,8 +22510,12 @@ function isProxyConfigRow(v) {
22404
22510
  const o = v;
22405
22511
  return typeof o["proxy_url"] === "string" && o["proxy_url"].length > 0 && typeof o["server_name"] === "string" && typeof o["target_url"] === "string";
22406
22512
  }
22407
- async function fetchProxyConfigs(baseUrl, apiKey, timeoutMs) {
22513
+ async function fetchProxyConfigs(baseUrl, apiKey, timeoutMs, options) {
22408
22514
  const url2 = `${normalizeBaseUrl(baseUrl)}/api/v1/proxy/config`;
22515
+ assertSafeProxyUrl(
22516
+ url2,
22517
+ options?.allowPrivateNetworks === true ? { allowPrivateNetworks: true } : void 0
22518
+ );
22409
22519
  let response;
22410
22520
  try {
22411
22521
  response = await fetch(url2, {
@@ -22563,6 +22673,10 @@ var ProxySession = class {
22563
22673
  this.nextId = 1;
22564
22674
  this.sessionId = null;
22565
22675
  this.closed = false;
22676
+ assertSafeProxyUrl(
22677
+ proxyUrl,
22678
+ options?.allowPrivateNetworks === true ? { allowPrivateNetworks: true } : void 0
22679
+ );
22566
22680
  this.proxyUrl = proxyUrl.replace(/\/+$/, "") + "/mcp";
22567
22681
  this.apiKey = apiKey;
22568
22682
  this.requestTimeoutMs = options?.requestTimeoutMs ?? 6e4;
@@ -23554,6 +23668,8 @@ async function autoCreateProxyConfig(baseUrl, apiKey, serverName, entry, agentNa
23554
23668
  const targetUrl = `stdio://${entry.command}/${entry.args.join("/")}`;
23555
23669
  const url2 = `${baseUrl.replace(/\/+$/, "")}/api/v1/proxy/config`;
23556
23670
  debugLog2(`[SHIELD] Auto-creating proxy config for "${serverName}".`);
23671
+ const allowPrivateNetworks = process.env["MULTICORN_ALLOW_PRIVATE_PROXY_HOSTS"] === "1";
23672
+ assertSafeProxyUrl(url2, { allowPrivateNetworks });
23557
23673
  let response;
23558
23674
  try {
23559
23675
  response = await fetch(url2, {
@@ -23686,6 +23802,7 @@ async function runShieldExtension() {
23686
23802
  `[SHIELD] Config read; ${String(serverCount)} MCP server(s) discovered (excluding Shield).`
23687
23803
  );
23688
23804
  debugLog2("[SHIELD] Resolving proxy configs (local config or API).");
23805
+ const allowPrivateNetworks = process.env["MULTICORN_ALLOW_PRIVATE_PROXY_HOSTS"] === "1";
23689
23806
  let configs;
23690
23807
  const localConfigs = await readProxyConfigsFromLocalMulticornConfig();
23691
23808
  if (localConfigs.length > 0) {
@@ -23694,7 +23811,9 @@ async function runShieldExtension() {
23694
23811
  } else {
23695
23812
  debugLog2("[SHIELD] No local proxy configs; fetching from API.");
23696
23813
  try {
23697
- configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS);
23814
+ configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS, {
23815
+ allowPrivateNetworks
23816
+ });
23698
23817
  } catch (e) {
23699
23818
  clearTimeout(setupTimeout);
23700
23819
  if (e instanceof ProxyConfigFetchError) {
@@ -23728,7 +23847,9 @@ async function runShieldExtension() {
23728
23847
  `[SHIELD] Auto-created ${String(createdCount)} proxy config(s); re-fetching from API.`
23729
23848
  );
23730
23849
  try {
23731
- configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS);
23850
+ configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS, {
23851
+ allowPrivateNetworks
23852
+ });
23732
23853
  } catch (e) {
23733
23854
  const message = e instanceof Error ? e.message : String(e);
23734
23855
  debugLog2(`[SHIELD] Re-fetch after auto-creation failed: ${message}`);
@@ -23766,7 +23887,7 @@ async function runShieldExtension() {
23766
23887
  const toolsByProxy = /* @__PURE__ */ new Map();
23767
23888
  const sessionByProxyUrl = /* @__PURE__ */ new Map();
23768
23889
  for (const cfg of configs) {
23769
- const session = new ProxySession(cfg.proxy_url, apiKey);
23890
+ const session = new ProxySession(cfg.proxy_url, apiKey, { allowPrivateNetworks });
23770
23891
  try {
23771
23892
  debugLog2(`[SHIELD] Initializing proxy session for ${cfg.server_name}.`);
23772
23893
  await session.initialize();
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
+ "author": "Multicorn AI Pty Ltd",
6
7
  "type": "module",
7
8
  "main": "./dist/index.cjs",
8
9
  "module": "./dist/index.js",
@@ -37,15 +38,25 @@
37
38
  "dist",
38
39
  "plugins/windsurf",
39
40
  "LICENSE",
40
- "README.md"
41
+ "README.md",
42
+ "CHANGELOG.md"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ },
48
+ "sideEffects": [
49
+ "dist/index.js",
50
+ "dist/index.cjs",
51
+ "dist/badge.js",
52
+ "src/badge/multicorn-badge.ts"
41
53
  ],
42
- "sideEffects": false,
43
54
  "engines": {
44
55
  "node": ">=20"
45
56
  },
46
57
  "lint-staged": {
47
58
  "*.ts": [
48
- "eslint --fix",
59
+ "eslint --fix --no-warn-ignored",
49
60
  "prettier --write"
50
61
  ],
51
62
  "*.{json,md}": [
@@ -91,6 +102,11 @@
91
102
  "path": "dist/index.cjs",
92
103
  "limit": "50 kB",
93
104
  "gzip": true
105
+ },
106
+ {
107
+ "path": "dist/badge.js",
108
+ "limit": "5 kB",
109
+ "gzip": true
94
110
  }
95
111
  ],
96
112
  "keywords": [
@@ -117,8 +133,8 @@
117
133
  "scripts": {
118
134
  "build": "tsup",
119
135
  "dev": "tsup --watch",
120
- "lint": "eslint . && prettier --check .",
121
- "lint:fix": "eslint --fix . && prettier --write .",
136
+ "lint": "eslint . --no-warn-ignored && prettier --check .",
137
+ "lint:fix": "eslint --fix . --no-warn-ignored && prettier --write .",
122
138
  "test": "vitest run",
123
139
  "test:watch": "vitest",
124
140
  "test:coverage": "vitest run --coverage",