multicorn-shield 1.3.6 → 1.4.1

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/CHANGELOG.md CHANGED
@@ -9,6 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - Bump `version` in `package.json` before publishing to npm.
11
11
 
12
+ ## [1.4.1] - 2026-05-09
13
+
14
+ ### Changed
15
+
16
+ - Upstream auth prompt accepts a raw API token (`Bearer` added automatically) or a full Authorization-style value (`Bearer`, `Basic`, `Token`, `ApiKey` prefixes are passed through unchanged)
17
+
18
+ ### Fixed
19
+
20
+ - CLI replace flow no longer shows duplicate agent entries
21
+
22
+ ## [1.4.0] - 2026-05-08
23
+
24
+ ### Added
25
+
26
+ - Hosted proxy wizard now prompts for upstream MCP server authentication (header name and value) when the target server requires credentials
27
+ - Helpful examples shown during hosted proxy setup: common MCP server URLs (GitHub, Supabase, Atlassian, Stripe) with links to where to find tokens
28
+ - Upstream auth headers are stored encrypted and forwarded by the proxy to the target MCP server on every request
29
+
30
+ ### Changed
31
+
32
+ - Target MCP server URL prompt now shows common examples instead of a generic placeholder
33
+ - "govern" replaced with "control" in all user-facing copy
34
+ - "upstream auth" replaced with "server credentials" in all user-facing copy
35
+
36
+ ### Fixed
37
+
38
+ - Hosted proxy connections to MCP servers requiring authentication (e.g. GitHub, Supabase) now work end-to-end - previously the proxy forwarded requests without credentials
39
+
12
40
  ## [1.3.6] - 2026-05-08
13
41
 
14
42
  ### Fixed
@@ -420,6 +420,17 @@ function stripAnsi(str) {
420
420
  function normalizeAgentName(raw) {
421
421
  return raw.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
422
422
  }
423
+ function formatUpstreamAuthorizationBearerHeader(raw) {
424
+ const t = raw.trim();
425
+ if (t.length === 0) return void 0;
426
+ if (UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD.test(t)) {
427
+ return t;
428
+ }
429
+ if (/^(Bearer|Basic|Token|ApiKey)$/i.test(t)) {
430
+ return void 0;
431
+ }
432
+ return `Bearer ${t}`;
433
+ }
423
434
  function isErrnoException(e) {
424
435
  return typeof e === "object" && e !== null && "code" in e;
425
436
  }
@@ -1500,8 +1511,8 @@ async function promptProxyConfig(ask, agentName) {
1500
1511
  while (targetUrl.length === 0) {
1501
1512
  process.stderr.write(
1502
1513
  "\n" + style.bold("Target MCP server URL:") + "\n" + style.dim(
1503
- "The URL of the MCP server you want Shield to protect. Example: https://your-server.example.com/mcp"
1504
- ) + "\n"
1514
+ "This is the URL of the MCP server you want Shield to control. Common examples:"
1515
+ ) + "\n" + style.dim(" GitHub: https://api.githubcopilot.com/mcp/") + "\n" + style.dim(" Supabase: https://mcp.supabase.com/sse") + "\n" + style.dim(" Atlassian: https://mcp.atlassian.com/v1/sse") + "\n" + style.dim(" Stripe: https://mcp.stripe.com/v1/sse") + "\n" + style.dim("Check your MCP server's documentation for the correct URL.") + "\n"
1505
1516
  );
1506
1517
  const input = await ask("URL: ");
1507
1518
  if (input.trim().length === 0) {
@@ -1521,10 +1532,44 @@ async function promptProxyConfig(ask, agentName) {
1521
1532
  targetUrl = input.trim();
1522
1533
  }
1523
1534
  const shortName = normalizeAgentName(agentName) || "shield-mcp";
1524
- return { targetUrl, shortName };
1535
+ process.stderr.write(
1536
+ "\n" + style.bold("Does this MCP server require authentication?") + "\n" + style.dim(
1537
+ "Most MCP servers need a token or API key. Check the server's docs for how to get one:"
1538
+ ) + "\n" + style.dim(" GitHub: Settings > Developer Settings > Personal Access Tokens") + "\n" + style.dim(
1539
+ " Supabase: Project Settings > API > anon or scoped key (service role bypasses RLS; avoid for most MCP)"
1540
+ ) + "\n" + style.dim(" Atlassian: id.atlassian.com > API Tokens") + "\n" + style.dim(" Stripe: Dashboard > Developers > API Keys") + "\n"
1541
+ );
1542
+ const authReply = await ask("(y/N): ");
1543
+ const authNorm = authReply.trim().toLowerCase();
1544
+ const wantsAuth = authNorm === "y" || authNorm === "yes";
1545
+ let upstreamHeaders;
1546
+ if (wantsAuth) {
1547
+ process.stderr.write(
1548
+ "\n" + style.bold("Enter your API token or full Authorization header value.") + "\n" + style.dim(" Bearer tokens: ghp_xxxxxxxxxxxx (Bearer is added automatically)") + "\n" + style.dim(" Other schemes: Basic dXNlcjpwYXNz (passed as-is)") + "\n"
1549
+ );
1550
+ const headerVal = await ask("Value: ");
1551
+ const authHeader = formatUpstreamAuthorizationBearerHeader(headerVal);
1552
+ if (authHeader !== void 0) {
1553
+ upstreamHeaders = { Authorization: authHeader };
1554
+ }
1555
+ }
1556
+ return {
1557
+ targetUrl,
1558
+ shortName,
1559
+ ...upstreamHeaders !== void 0 ? { upstreamHeaders } : {}
1560
+ };
1525
1561
  }
1526
- async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform) {
1562
+ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform, upstreamHeaders) {
1527
1563
  let response;
1564
+ const body = {
1565
+ server_name: serverName,
1566
+ target_url: targetUrl,
1567
+ platform,
1568
+ agent_name: agentName
1569
+ };
1570
+ if (upstreamHeaders !== void 0 && Object.keys(upstreamHeaders).length > 0) {
1571
+ body["upstream_headers"] = upstreamHeaders;
1572
+ }
1528
1573
  try {
1529
1574
  response = await fetch(`${baseUrl}/api/v1/proxy/config`, {
1530
1575
  method: "POST",
@@ -1532,12 +1577,7 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
1532
1577
  "Content-Type": "application/json",
1533
1578
  "X-Multicorn-Key": apiKey
1534
1579
  },
1535
- body: JSON.stringify({
1536
- server_name: serverName,
1537
- target_url: targetUrl,
1538
- platform,
1539
- agent_name: agentName
1540
- }),
1580
+ body: JSON.stringify(body),
1541
1581
  signal: AbortSignal.timeout(1e4)
1542
1582
  });
1543
1583
  } catch (error) {
@@ -2035,7 +2075,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
2035
2075
  }
2036
2076
  }
2037
2077
  function agentDisplayNameDedupeKey(name) {
2038
- return name.trim().toLowerCase();
2078
+ return name.trim().normalize("NFKC").toLowerCase();
2039
2079
  }
2040
2080
  function normalizeAgentEntryForMerge(a) {
2041
2081
  const name = a.name.trim();
@@ -2262,6 +2302,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2262
2302
  const victim = agentsForPlatform[replaceIdx];
2263
2303
  if (victim !== void 0) {
2264
2304
  removeAgentNameBeforeSave = victim.name;
2305
+ process.stderr.write(
2306
+ "\n" + style.dim("Replacing agent ") + style.cyan(victim.name) + style.dim("...") + "\n"
2307
+ );
2265
2308
  }
2266
2309
  }
2267
2310
  }
@@ -2445,7 +2488,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2445
2488
  }
2446
2489
  }
2447
2490
  } else {
2448
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2491
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2449
2492
  let proxyUrl = "";
2450
2493
  let created = false;
2451
2494
  while (!created) {
@@ -2457,7 +2500,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2457
2500
  agentName,
2458
2501
  targetUrl,
2459
2502
  shortName,
2460
- selectedPlatform
2503
+ selectedPlatform,
2504
+ upstreamHeaders
2461
2505
  );
2462
2506
  spinner.stop(true, "Proxy config created!");
2463
2507
  created = true;
@@ -2538,7 +2582,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2538
2582
  }
2539
2583
  }
2540
2584
  } else {
2541
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2585
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2542
2586
  let proxyUrl = "";
2543
2587
  let created = false;
2544
2588
  while (!created) {
@@ -2550,7 +2594,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2550
2594
  agentName,
2551
2595
  targetUrl,
2552
2596
  shortName,
2553
- selectedPlatform
2597
+ selectedPlatform,
2598
+ upstreamHeaders
2554
2599
  );
2555
2600
  spinner.stop(true, "Proxy config created!");
2556
2601
  created = true;
@@ -2625,7 +2670,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2625
2670
  }
2626
2671
  }
2627
2672
  } else {
2628
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2673
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2629
2674
  let proxyUrl = "";
2630
2675
  let created = false;
2631
2676
  while (!created) {
@@ -2637,7 +2682,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2637
2682
  agentName,
2638
2683
  targetUrl,
2639
2684
  shortName,
2640
- selectedPlatform
2685
+ selectedPlatform,
2686
+ upstreamHeaders
2641
2687
  );
2642
2688
  spinner.stop(true, "Proxy config created!");
2643
2689
  created = true;
@@ -2675,7 +2721,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2675
2721
  }
2676
2722
  }
2677
2723
  } else {
2678
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2724
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2679
2725
  let proxyUrl = "";
2680
2726
  let created = false;
2681
2727
  while (!created) {
@@ -2687,7 +2733,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2687
2733
  agentName,
2688
2734
  targetUrl,
2689
2735
  shortName,
2690
- selectedPlatform
2736
+ selectedPlatform,
2737
+ upstreamHeaders
2691
2738
  );
2692
2739
  spinner.stop(true, "Proxy config created!");
2693
2740
  created = true;
@@ -2777,6 +2824,14 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2777
2824
  }
2778
2825
  process.stderr.write("\n");
2779
2826
  const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
2827
+ const cursorMcpPromptLabel = (() => {
2828
+ const rows = configuredAgents.filter((a) => a.platform === "cursor");
2829
+ const last = rows[rows.length - 1];
2830
+ if (last === void 0) return "shield-mcp";
2831
+ const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
2832
+ if (s.length > 0) return s;
2833
+ return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
2834
+ })();
2780
2835
  const blocks = [];
2781
2836
  if (configuredPlatforms.has("openclaw")) {
2782
2837
  blocks.push(
@@ -2795,7 +2850,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2795
2850
  }
2796
2851
  if (configuredPlatforms.has("cursor")) {
2797
2852
  blocks.push(
2798
- "\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + "\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Try it: make a request in Cursor - Shield will intercept the first tool call and ask for your consent\n"
2853
+ "\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + '\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Try it: make a request in Cursor - Shield will intercept the first tool call and ask for your consent\n \u2192 Example: "Use the ' + cursorMcpPromptLabel + ' MCP server to list my GitHub repositories"\n'
2799
2854
  );
2800
2855
  }
2801
2856
  if (configuredPlatforms.has("kilo-code")) {
@@ -2885,7 +2940,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2885
2940
  }
2886
2941
  return lastConfig;
2887
2942
  }
2888
- var SECRET_JSON_FILE_OPTIONS, style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, OPENCLAW_MIN_VERSION, INIT_WIZARD_PLATFORM_REGISTRY, INIT_WIZARD_MENU_SECTIONS, INIT_WIZARD_SELECTION_MAX, PLATFORM_BY_SELECTION, HOSTED_PROXY_PLATFORMS_WITH_URL_KEY, DEFAULT_SHIELD_API_BASE_URL;
2943
+ var SECRET_JSON_FILE_OPTIONS, style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD, OPENCLAW_MIN_VERSION, INIT_WIZARD_PLATFORM_REGISTRY, INIT_WIZARD_MENU_SECTIONS, INIT_WIZARD_SELECTION_MAX, PLATFORM_BY_SELECTION, HOSTED_PROXY_PLATFORMS_WITH_URL_KEY, DEFAULT_SHIELD_API_BASE_URL;
2889
2944
  var init_config = __esm({
2890
2945
  "src/proxy/config.ts"() {
2891
2946
  init_consent();
@@ -2917,6 +2972,7 @@ var init_config = __esm({
2917
2972
  CONFIG_PATH = join(CONFIG_DIR, "config.json");
2918
2973
  OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
2919
2974
  ANSI_PATTERN = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*[a-zA-Z]", "g");
2975
+ UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD = /^(Bearer|Basic|Token|ApiKey)(\s+)(.+)$/is;
2920
2976
  OPENCLAW_MIN_VERSION = "2026.2.26";
2921
2977
  INIT_WIZARD_PLATFORM_REGISTRY = [
2922
2978
  { slug: "openclaw", displayName: "OpenClaw", section: "native" },
@@ -432,6 +432,18 @@ function stripAnsi(str) {
432
432
  function normalizeAgentName(raw) {
433
433
  return raw.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
434
434
  }
435
+ var UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD = /^(Bearer|Basic|Token|ApiKey)(\s+)(.+)$/is;
436
+ function formatUpstreamAuthorizationBearerHeader(raw) {
437
+ const t = raw.trim();
438
+ if (t.length === 0) return void 0;
439
+ if (UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD.test(t)) {
440
+ return t;
441
+ }
442
+ if (/^(Bearer|Basic|Token|ApiKey)$/i.test(t)) {
443
+ return void 0;
444
+ }
445
+ return `Bearer ${t}`;
446
+ }
435
447
  function isErrnoException(e) {
436
448
  return typeof e === "object" && e !== null && "code" in e;
437
449
  }
@@ -1571,8 +1583,8 @@ async function promptProxyConfig(ask, agentName) {
1571
1583
  while (targetUrl.length === 0) {
1572
1584
  process.stderr.write(
1573
1585
  "\n" + style.bold("Target MCP server URL:") + "\n" + style.dim(
1574
- "The URL of the MCP server you want Shield to protect. Example: https://your-server.example.com/mcp"
1575
- ) + "\n"
1586
+ "This is the URL of the MCP server you want Shield to control. Common examples:"
1587
+ ) + "\n" + style.dim(" GitHub: https://api.githubcopilot.com/mcp/") + "\n" + style.dim(" Supabase: https://mcp.supabase.com/sse") + "\n" + style.dim(" Atlassian: https://mcp.atlassian.com/v1/sse") + "\n" + style.dim(" Stripe: https://mcp.stripe.com/v1/sse") + "\n" + style.dim("Check your MCP server's documentation for the correct URL.") + "\n"
1576
1588
  );
1577
1589
  const input = await ask("URL: ");
1578
1590
  if (input.trim().length === 0) {
@@ -1592,10 +1604,44 @@ async function promptProxyConfig(ask, agentName) {
1592
1604
  targetUrl = input.trim();
1593
1605
  }
1594
1606
  const shortName = normalizeAgentName(agentName) || "shield-mcp";
1595
- return { targetUrl, shortName };
1607
+ process.stderr.write(
1608
+ "\n" + style.bold("Does this MCP server require authentication?") + "\n" + style.dim(
1609
+ "Most MCP servers need a token or API key. Check the server's docs for how to get one:"
1610
+ ) + "\n" + style.dim(" GitHub: Settings > Developer Settings > Personal Access Tokens") + "\n" + style.dim(
1611
+ " Supabase: Project Settings > API > anon or scoped key (service role bypasses RLS; avoid for most MCP)"
1612
+ ) + "\n" + style.dim(" Atlassian: id.atlassian.com > API Tokens") + "\n" + style.dim(" Stripe: Dashboard > Developers > API Keys") + "\n"
1613
+ );
1614
+ const authReply = await ask("(y/N): ");
1615
+ const authNorm = authReply.trim().toLowerCase();
1616
+ const wantsAuth = authNorm === "y" || authNorm === "yes";
1617
+ let upstreamHeaders;
1618
+ if (wantsAuth) {
1619
+ process.stderr.write(
1620
+ "\n" + style.bold("Enter your API token or full Authorization header value.") + "\n" + style.dim(" Bearer tokens: ghp_xxxxxxxxxxxx (Bearer is added automatically)") + "\n" + style.dim(" Other schemes: Basic dXNlcjpwYXNz (passed as-is)") + "\n"
1621
+ );
1622
+ const headerVal = await ask("Value: ");
1623
+ const authHeader = formatUpstreamAuthorizationBearerHeader(headerVal);
1624
+ if (authHeader !== void 0) {
1625
+ upstreamHeaders = { Authorization: authHeader };
1626
+ }
1627
+ }
1628
+ return {
1629
+ targetUrl,
1630
+ shortName,
1631
+ ...upstreamHeaders !== void 0 ? { upstreamHeaders } : {}
1632
+ };
1596
1633
  }
1597
- async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform) {
1634
+ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform, upstreamHeaders) {
1598
1635
  let response;
1636
+ const body = {
1637
+ server_name: serverName,
1638
+ target_url: targetUrl,
1639
+ platform,
1640
+ agent_name: agentName
1641
+ };
1642
+ if (upstreamHeaders !== void 0 && Object.keys(upstreamHeaders).length > 0) {
1643
+ body["upstream_headers"] = upstreamHeaders;
1644
+ }
1599
1645
  try {
1600
1646
  response = await fetch(`${baseUrl}/api/v1/proxy/config`, {
1601
1647
  method: "POST",
@@ -1603,12 +1649,7 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
1603
1649
  "Content-Type": "application/json",
1604
1650
  "X-Multicorn-Key": apiKey
1605
1651
  },
1606
- body: JSON.stringify({
1607
- server_name: serverName,
1608
- target_url: targetUrl,
1609
- platform,
1610
- agent_name: agentName
1611
- }),
1652
+ body: JSON.stringify(body),
1612
1653
  signal: AbortSignal.timeout(1e4)
1613
1654
  });
1614
1655
  } catch (error) {
@@ -2114,7 +2155,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
2114
2155
  }
2115
2156
  }
2116
2157
  function agentDisplayNameDedupeKey(name) {
2117
- return name.trim().toLowerCase();
2158
+ return name.trim().normalize("NFKC").toLowerCase();
2118
2159
  }
2119
2160
  function normalizeAgentEntryForMerge(a) {
2120
2161
  const name = a.name.trim();
@@ -2342,6 +2383,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2342
2383
  const victim = agentsForPlatform[replaceIdx];
2343
2384
  if (victim !== void 0) {
2344
2385
  removeAgentNameBeforeSave = victim.name;
2386
+ process.stderr.write(
2387
+ "\n" + style.dim("Replacing agent ") + style.cyan(victim.name) + style.dim("...") + "\n"
2388
+ );
2345
2389
  }
2346
2390
  }
2347
2391
  }
@@ -2525,7 +2569,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2525
2569
  }
2526
2570
  }
2527
2571
  } else {
2528
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2572
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2529
2573
  let proxyUrl = "";
2530
2574
  let created = false;
2531
2575
  while (!created) {
@@ -2537,7 +2581,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2537
2581
  agentName,
2538
2582
  targetUrl,
2539
2583
  shortName,
2540
- selectedPlatform
2584
+ selectedPlatform,
2585
+ upstreamHeaders
2541
2586
  );
2542
2587
  spinner.stop(true, "Proxy config created!");
2543
2588
  created = true;
@@ -2618,7 +2663,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2618
2663
  }
2619
2664
  }
2620
2665
  } else {
2621
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2666
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2622
2667
  let proxyUrl = "";
2623
2668
  let created = false;
2624
2669
  while (!created) {
@@ -2630,7 +2675,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2630
2675
  agentName,
2631
2676
  targetUrl,
2632
2677
  shortName,
2633
- selectedPlatform
2678
+ selectedPlatform,
2679
+ upstreamHeaders
2634
2680
  );
2635
2681
  spinner.stop(true, "Proxy config created!");
2636
2682
  created = true;
@@ -2705,7 +2751,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2705
2751
  }
2706
2752
  }
2707
2753
  } else {
2708
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2754
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2709
2755
  let proxyUrl = "";
2710
2756
  let created = false;
2711
2757
  while (!created) {
@@ -2717,7 +2763,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2717
2763
  agentName,
2718
2764
  targetUrl,
2719
2765
  shortName,
2720
- selectedPlatform
2766
+ selectedPlatform,
2767
+ upstreamHeaders
2721
2768
  );
2722
2769
  spinner.stop(true, "Proxy config created!");
2723
2770
  created = true;
@@ -2755,7 +2802,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2755
2802
  }
2756
2803
  }
2757
2804
  } else {
2758
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2805
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2759
2806
  let proxyUrl = "";
2760
2807
  let created = false;
2761
2808
  while (!created) {
@@ -2767,7 +2814,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2767
2814
  agentName,
2768
2815
  targetUrl,
2769
2816
  shortName,
2770
- selectedPlatform
2817
+ selectedPlatform,
2818
+ upstreamHeaders
2771
2819
  );
2772
2820
  spinner.stop(true, "Proxy config created!");
2773
2821
  created = true;
@@ -2857,6 +2905,14 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2857
2905
  }
2858
2906
  process.stderr.write("\n");
2859
2907
  const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
2908
+ const cursorMcpPromptLabel = (() => {
2909
+ const rows = configuredAgents.filter((a) => a.platform === "cursor");
2910
+ const last = rows[rows.length - 1];
2911
+ if (last === void 0) return "shield-mcp";
2912
+ const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
2913
+ if (s.length > 0) return s;
2914
+ return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
2915
+ })();
2860
2916
  const blocks = [];
2861
2917
  if (configuredPlatforms.has("openclaw")) {
2862
2918
  blocks.push(
@@ -2875,7 +2931,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2875
2931
  }
2876
2932
  if (configuredPlatforms.has("cursor")) {
2877
2933
  blocks.push(
2878
- "\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + "\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Try it: make a request in Cursor - Shield will intercept the first tool call and ask for your consent\n"
2934
+ "\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + '\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Try it: make a request in Cursor - Shield will intercept the first tool call and ask for your consent\n \u2192 Example: "Use the ' + cursorMcpPromptLabel + ' MCP server to list my GitHub repositories"\n'
2879
2935
  );
2880
2936
  }
2881
2937
  if (configuredPlatforms.has("kilo-code")) {
@@ -3090,11 +3090,14 @@ var require_data = __commonJS({
3090
3090
  }
3091
3091
  });
3092
3092
 
3093
- // node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/utils.js
3093
+ // node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js
3094
3094
  var require_utils = __commonJS({
3095
- "node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/utils.js"(exports$1, module) {
3095
+ "node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js"(exports$1, module) {
3096
3096
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3097
3097
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3098
+ var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3099
+ var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3100
+ var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3098
3101
  function stringArrayToHexStripped(input) {
3099
3102
  let acc = "";
3100
3103
  let code = 0;
@@ -3287,27 +3290,77 @@ var require_utils = __commonJS({
3287
3290
  }
3288
3291
  return output.join("");
3289
3292
  }
3290
- function normalizeComponentEncoding(component, esc2) {
3291
- const func = esc2 !== true ? escape : unescape;
3292
- if (component.scheme !== void 0) {
3293
- component.scheme = func(component.scheme);
3294
- }
3295
- if (component.userinfo !== void 0) {
3296
- component.userinfo = func(component.userinfo);
3297
- }
3298
- if (component.host !== void 0) {
3299
- component.host = func(component.host);
3293
+ var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
3294
+ var HOST_DELIM_RE = /[@/?#:]/g;
3295
+ var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
3296
+ function reescapeHostDelimiters(host, isIP) {
3297
+ const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
3298
+ re.lastIndex = 0;
3299
+ return host.replace(re, (ch) => HOST_DELIMS[ch]);
3300
+ }
3301
+ function normalizePercentEncoding(input, decodeUnreserved = false) {
3302
+ if (input.indexOf("%") === -1) {
3303
+ return input;
3300
3304
  }
3301
- if (component.path !== void 0) {
3302
- component.path = func(component.path);
3305
+ let output = "";
3306
+ for (let i = 0; i < input.length; i++) {
3307
+ if (input[i] === "%" && i + 2 < input.length) {
3308
+ const hex3 = input.slice(i + 1, i + 3);
3309
+ if (isHexPair(hex3)) {
3310
+ const normalizedHex = hex3.toUpperCase();
3311
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3312
+ if (decodeUnreserved && isUnreserved(decoded)) {
3313
+ output += decoded;
3314
+ } else {
3315
+ output += "%" + normalizedHex;
3316
+ }
3317
+ i += 2;
3318
+ continue;
3319
+ }
3320
+ }
3321
+ output += input[i];
3303
3322
  }
3304
- if (component.query !== void 0) {
3305
- component.query = func(component.query);
3323
+ return output;
3324
+ }
3325
+ function normalizePathEncoding(input) {
3326
+ let output = "";
3327
+ for (let i = 0; i < input.length; i++) {
3328
+ if (input[i] === "%" && i + 2 < input.length) {
3329
+ const hex3 = input.slice(i + 1, i + 3);
3330
+ if (isHexPair(hex3)) {
3331
+ const normalizedHex = hex3.toUpperCase();
3332
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3333
+ if (decoded !== "." && isUnreserved(decoded)) {
3334
+ output += decoded;
3335
+ } else {
3336
+ output += "%" + normalizedHex;
3337
+ }
3338
+ i += 2;
3339
+ continue;
3340
+ }
3341
+ }
3342
+ if (isPathCharacter(input[i])) {
3343
+ output += input[i];
3344
+ } else {
3345
+ output += escape(input[i]);
3346
+ }
3306
3347
  }
3307
- if (component.fragment !== void 0) {
3308
- component.fragment = func(component.fragment);
3348
+ return output;
3349
+ }
3350
+ function escapePreservingEscapes(input) {
3351
+ let output = "";
3352
+ for (let i = 0; i < input.length; i++) {
3353
+ if (input[i] === "%" && i + 2 < input.length) {
3354
+ const hex3 = input.slice(i + 1, i + 3);
3355
+ if (isHexPair(hex3)) {
3356
+ output += "%" + hex3.toUpperCase();
3357
+ i += 2;
3358
+ continue;
3359
+ }
3360
+ }
3361
+ output += escape(input[i]);
3309
3362
  }
3310
- return component;
3363
+ return output;
3311
3364
  }
3312
3365
  function recomposeAuthority(component) {
3313
3366
  const uriTokens = [];
@@ -3322,7 +3375,7 @@ var require_utils = __commonJS({
3322
3375
  if (ipV6res.isIPV6 === true) {
3323
3376
  host = `[${ipV6res.escapedHost}]`;
3324
3377
  } else {
3325
- host = component.host;
3378
+ host = reescapeHostDelimiters(host, false);
3326
3379
  }
3327
3380
  }
3328
3381
  uriTokens.push(host);
@@ -3336,7 +3389,10 @@ var require_utils = __commonJS({
3336
3389
  module.exports = {
3337
3390
  nonSimpleDomain,
3338
3391
  recomposeAuthority,
3339
- normalizeComponentEncoding,
3392
+ reescapeHostDelimiters,
3393
+ normalizePercentEncoding,
3394
+ normalizePathEncoding,
3395
+ escapePreservingEscapes,
3340
3396
  removeDotSegments,
3341
3397
  isIPv4,
3342
3398
  isUUID,
@@ -3346,9 +3402,9 @@ var require_utils = __commonJS({
3346
3402
  }
3347
3403
  });
3348
3404
 
3349
- // node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/schemes.js
3405
+ // node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js
3350
3406
  var require_schemes = __commonJS({
3351
- "node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/schemes.js"(exports$1, module) {
3407
+ "node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js"(exports$1, module) {
3352
3408
  var { isUUID } = require_utils();
3353
3409
  var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3354
3410
  var supportedSchemeNames = (
@@ -3555,15 +3611,15 @@ var require_schemes = __commonJS({
3555
3611
  }
3556
3612
  });
3557
3613
 
3558
- // node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/index.js
3614
+ // node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js
3559
3615
  var require_fast_uri = __commonJS({
3560
- "node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/index.js"(exports$1, module) {
3561
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
3616
+ "node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js"(exports$1, module) {
3617
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3562
3618
  var { SCHEMES, getSchemeHandler } = require_schemes();
3563
3619
  function normalize(uri, options) {
3564
3620
  if (typeof uri === "string") {
3565
3621
  uri = /** @type {T} */
3566
- serialize(parse3(uri, options), options);
3622
+ normalizeString(uri, options);
3567
3623
  } else if (typeof uri === "object") {
3568
3624
  uri = /** @type {T} */
3569
3625
  parse3(serialize(uri, options), options);
@@ -3630,19 +3686,9 @@ var require_fast_uri = __commonJS({
3630
3686
  return target;
3631
3687
  }
3632
3688
  function equal(uriA, uriB, options) {
3633
- if (typeof uriA === "string") {
3634
- uriA = unescape(uriA);
3635
- uriA = serialize(normalizeComponentEncoding(parse3(uriA, options), true), { ...options, skipEscape: true });
3636
- } else if (typeof uriA === "object") {
3637
- uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
3638
- }
3639
- if (typeof uriB === "string") {
3640
- uriB = unescape(uriB);
3641
- uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
3642
- } else if (typeof uriB === "object") {
3643
- uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
3644
- }
3645
- return uriA.toLowerCase() === uriB.toLowerCase();
3689
+ const normalizedA = normalizeComparableURI(uriA, options);
3690
+ const normalizedB = normalizeComparableURI(uriB, options);
3691
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3646
3692
  }
3647
3693
  function serialize(cmpts, opts) {
3648
3694
  const component = {
@@ -3667,12 +3713,12 @@ var require_fast_uri = __commonJS({
3667
3713
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
3668
3714
  if (component.path !== void 0) {
3669
3715
  if (!options.skipEscape) {
3670
- component.path = escape(component.path);
3716
+ component.path = escapePreservingEscapes(component.path);
3671
3717
  if (component.scheme !== void 0) {
3672
3718
  component.path = component.path.split("%3A").join(":");
3673
3719
  }
3674
3720
  } else {
3675
- component.path = unescape(component.path);
3721
+ component.path = normalizePercentEncoding(component.path);
3676
3722
  }
3677
3723
  }
3678
3724
  if (options.reference !== "suffix" && component.scheme) {
@@ -3707,7 +3753,16 @@ var require_fast_uri = __commonJS({
3707
3753
  return uriTokens.join("");
3708
3754
  }
3709
3755
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3710
- function parse3(uri, opts) {
3756
+ function getParseError(parsed, matches) {
3757
+ if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3758
+ return 'URI path must start with "/" when authority is present.';
3759
+ }
3760
+ if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
3761
+ return "URI port is malformed.";
3762
+ }
3763
+ return void 0;
3764
+ }
3765
+ function parseWithStatus(uri, opts) {
3711
3766
  const options = Object.assign({}, opts);
3712
3767
  const parsed = {
3713
3768
  scheme: void 0,
@@ -3718,6 +3773,7 @@ var require_fast_uri = __commonJS({
3718
3773
  query: void 0,
3719
3774
  fragment: void 0
3720
3775
  };
3776
+ let malformedAuthorityOrPort = false;
3721
3777
  let isIP = false;
3722
3778
  if (options.reference === "suffix") {
3723
3779
  if (options.scheme) {
@@ -3738,6 +3794,11 @@ var require_fast_uri = __commonJS({
3738
3794
  if (isNaN(parsed.port)) {
3739
3795
  parsed.port = matches[5];
3740
3796
  }
3797
+ const parseError = getParseError(parsed, matches);
3798
+ if (parseError !== void 0) {
3799
+ parsed.error = parsed.error || parseError;
3800
+ malformedAuthorityOrPort = true;
3801
+ }
3741
3802
  if (parsed.host) {
3742
3803
  const ipv4result = isIPv4(parsed.host);
3743
3804
  if (ipv4result === false) {
@@ -3776,14 +3837,18 @@ var require_fast_uri = __commonJS({
3776
3837
  parsed.scheme = unescape(parsed.scheme);
3777
3838
  }
3778
3839
  if (parsed.host !== void 0) {
3779
- parsed.host = unescape(parsed.host);
3840
+ parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
3780
3841
  }
3781
3842
  }
3782
3843
  if (parsed.path) {
3783
- parsed.path = escape(unescape(parsed.path));
3844
+ parsed.path = normalizePathEncoding(parsed.path);
3784
3845
  }
3785
3846
  if (parsed.fragment) {
3786
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3847
+ try {
3848
+ parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3849
+ } catch {
3850
+ parsed.error = parsed.error || "URI malformed";
3851
+ }
3787
3852
  }
3788
3853
  }
3789
3854
  if (schemeHandler && schemeHandler.parse) {
@@ -3792,7 +3857,29 @@ var require_fast_uri = __commonJS({
3792
3857
  } else {
3793
3858
  parsed.error = parsed.error || "URI can not be parsed.";
3794
3859
  }
3795
- return parsed;
3860
+ return { parsed, malformedAuthorityOrPort };
3861
+ }
3862
+ function parse3(uri, opts) {
3863
+ return parseWithStatus(uri, opts).parsed;
3864
+ }
3865
+ function normalizeString(uri, opts) {
3866
+ return normalizeStringWithStatus(uri, opts).normalized;
3867
+ }
3868
+ function normalizeStringWithStatus(uri, opts) {
3869
+ const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
3870
+ return {
3871
+ normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3872
+ malformedAuthorityOrPort
3873
+ };
3874
+ }
3875
+ function normalizeComparableURI(uri, opts) {
3876
+ if (typeof uri === "string") {
3877
+ const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3878
+ return malformedAuthorityOrPort ? void 0 : normalized;
3879
+ }
3880
+ if (typeof uri === "object") {
3881
+ return serialize(uri, opts);
3882
+ }
3796
3883
  }
3797
3884
  var fastUri = {
3798
3885
  SCHEMES,
@@ -22417,7 +22504,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22417
22504
 
22418
22505
  // package.json
22419
22506
  var package_default = {
22420
- version: "1.3.6"};
22507
+ version: "1.4.1"};
22421
22508
 
22422
22509
  // src/package-meta.ts
22423
22510
  var PACKAGE_VERSION = package_default.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "1.3.6",
3
+ "version": "1.4.1",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
6
  "author": "Multicorn AI Pty Ltd",