multicorn-shield 0.10.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.11.0] - 2026-04-25
9
+
10
+ ### Added
11
+
12
+ - `<multicorn-badge>` trust badge web component for embedding in third-party products. Shadow DOM encapsulation, dark/light themes, compact/standard sizes, optional action count display.
13
+ - CDN entrypoint (`dist/badge.js`) for single-script-tag embedding: `<script src="https://cdn.multicorn.ai/badge.js" data-agent-id="..."></script>`. Self-contained, no Lit runtime dependency.
14
+ - `MulticornBadge` class exported from the main SDK barrel for programmatic usage.
15
+ - Shared `shield-tokens.ts` module (`src/shared/`) extracting `SHIELD_COLORS` design tokens for reuse across consent and badge components.
16
+ - `size-limit` budget enforcement for `dist/badge.js` at 5 kB gzip (actual ~1.75 kB).
17
+
8
18
  ## [0.10.0] - 2026-04-21
9
19
 
10
20
  ### Added
package/dist/badge.js ADDED
@@ -0,0 +1,44 @@
1
+ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",accent:"#8b5cf6",accentDim:"rgba(139, 92, 246, 0.12)"};var E="#0f172a",y="#f8fafc",A="#f1f5f9",v="#e2e8f0";function h(){return `
2
+ :host { display: inline-block; line-height: 0; }
3
+ .badge {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ box-sizing: border-box;
8
+ gap: 6px;
9
+ min-height: 28px;
10
+ padding: 4px 10px 4px 8px;
11
+ border-radius: 9999px;
12
+ text-decoration: none;
13
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
14
+ font-size: 12px;
15
+ font-weight: 500;
16
+ border: 1px solid ${n.border};
17
+ background: ${n.surface};
18
+ color: ${n.text};
19
+ transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
20
+ }
21
+ :host([theme="light"]) .badge {
22
+ border-color: ${v};
23
+ background: ${y};
24
+ color: ${E};
25
+ }
26
+ .badge:hover {
27
+ background: ${n.surfaceHover};
28
+ border-color: ${n.accent};
29
+ box-shadow: 0 0 0 1px ${n.accentDim};
30
+ }
31
+ :host([theme="light"]) .badge:hover {
32
+ background: ${A};
33
+ border-color: ${n.accent};
34
+ }
35
+ .badge:focus-visible {
36
+ outline: 2px solid ${n.accent};
37
+ outline-offset: 2px;
38
+ }
39
+ .icon { flex-shrink: 0; display: block; }
40
+ .text { white-space: nowrap; }
41
+ :host([size="compact"]) .text { display: none; }
42
+ :host([size="compact"]) .badge { padding: 4px 6px; }
43
+ @media (prefers-reduced-motion: reduce) { .badge { transition: none; } }
44
+ `.trim()}var L="https://multicorn.ai/verify/",x="multicorn-badge",w="M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z";function C(e){if(e==null||e==="")return;let t=Number(e);return Number.isNaN(t)?void 0:t}var m=class extends HTMLElement{#e=false;ensureShadow(){return this.shadowRoot!=null?this.shadowRoot:this.attachShadow({mode:"open"})}static get observedAttributes(){return ["agent-id","size","theme","action-count"]}connectedCallback(){this.render();}attributeChangedCallback(){this.render();}render(){let t=this.ensureShadow();if(!this.#e){let g=document.createElement("style");g.textContent=h(),t.appendChild(g),this.#e=true;}let s=(this.getAttribute("agent-id")??"").trim(),c=C(this.getAttribute("action-count")),a=t.querySelector("a.badge");if(a&&a.remove(),s==="")return;let d,l;if(c==null)d="Secured by Multicorn",l="Secured by Multicorn, verify this agent";else {let f=String(c);d="Secured by Multicorn \xB7 "+f+" actions secured",l="Secured by Multicorn \xB7 "+f+" actions secured, verify this agent";}let S=`${L}${encodeURIComponent(s)}`,r=document.createElement("a");r.className="badge",r.href=S,r.target="_blank",r.rel="noopener noreferrer",r.setAttribute("aria-label",l);let p="http://www.w3.org/2000/svg",o=document.createElementNS(p,"svg");o.setAttribute("class","icon"),o.setAttribute("width","16"),o.setAttribute("height","16"),o.setAttribute("viewBox","0 0 24 24"),o.setAttribute("aria-hidden","true");let u=document.createElementNS(p,"path");u.setAttribute("d",w),u.setAttribute("fill",n.accent),o.appendChild(u),r.appendChild(o);let b=document.createElement("span");b.className="text",b.textContent=d,r.appendChild(b),t.appendChild(r);}};customElements.get(x)===void 0&&customElements.define(x,m);function T(e){return e==="compact"||e==="standard"}function H(e){return e==="dark"||e==="light"}var i=(typeof document<"u"&&document.currentScript!==null?document.currentScript:null)??null;if(i==null)console.warn("[Multicorn] badge.js must be loaded as a classic script (document.currentScript was null).");else {let e=i.dataset.agentId?.trim();if(e==null||e==="")console.warn("[Multicorn] Skipping trust badge: missing data-agent-id on the badge script tag.");else {let t=document.createElement("multicorn-badge");t.setAttribute("agent-id",e);let s=i.dataset.size;T(s)&&t.setAttribute("size",s);let c=i.dataset.theme;H(c)&&t.setAttribute("theme",c);let a=i.dataset.actionCount;a!=null&&a!==""&&t.setAttribute("action-count",a),i.parentNode?.insertBefore(t,i.nextSibling);}}
package/dist/index.cjs CHANGED
@@ -432,6 +432,8 @@ function getScopeWarning(scopeString) {
432
432
  const metadata = getScopeMetadata(scopeString);
433
433
  return metadata?.warningMessage;
434
434
  }
435
+
436
+ // src/shared/shield-tokens.ts
435
437
  var SHIELD_COLORS = {
436
438
  bg: "#0d0d14",
437
439
  surface: "#14141f",
@@ -452,6 +454,8 @@ var SHIELD_COLORS = {
452
454
  red: "#ef4444",
453
455
  redDim: "rgba(239, 68, 68, 0.12)"
454
456
  };
457
+
458
+ // src/consent/consent-styles.ts
455
459
  var consentStyles = lit.css`
456
460
  :host {
457
461
  display: block;
@@ -1599,6 +1603,144 @@ exports.MulticornConsent = __decorateClass([
1599
1603
  decorators_js.customElement(CONSENT_ELEMENT_TAG)
1600
1604
  ], exports.MulticornConsent);
1601
1605
 
1606
+ // src/badge/badge-styles.ts
1607
+ var LIGHT_TEXT = "#0f172a";
1608
+ var LIGHT_SURFACE = "#f8fafc";
1609
+ var LIGHT_SURFACE_HOVER = "#f1f5f9";
1610
+ var LIGHT_BORDER = "#e2e8f0";
1611
+ function getBadgeStyleText() {
1612
+ return `
1613
+ :host { display: inline-block; line-height: 0; }
1614
+ .badge {
1615
+ display: inline-flex;
1616
+ align-items: center;
1617
+ justify-content: center;
1618
+ box-sizing: border-box;
1619
+ gap: 6px;
1620
+ min-height: 28px;
1621
+ padding: 4px 10px 4px 8px;
1622
+ border-radius: 9999px;
1623
+ text-decoration: none;
1624
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1625
+ font-size: 12px;
1626
+ font-weight: 500;
1627
+ border: 1px solid ${SHIELD_COLORS.border};
1628
+ background: ${SHIELD_COLORS.surface};
1629
+ color: ${SHIELD_COLORS.text};
1630
+ transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
1631
+ }
1632
+ :host([theme="light"]) .badge {
1633
+ border-color: ${LIGHT_BORDER};
1634
+ background: ${LIGHT_SURFACE};
1635
+ color: ${LIGHT_TEXT};
1636
+ }
1637
+ .badge:hover {
1638
+ background: ${SHIELD_COLORS.surfaceHover};
1639
+ border-color: ${SHIELD_COLORS.accent};
1640
+ box-shadow: 0 0 0 1px ${SHIELD_COLORS.accentDim};
1641
+ }
1642
+ :host([theme="light"]) .badge:hover {
1643
+ background: ${LIGHT_SURFACE_HOVER};
1644
+ border-color: ${SHIELD_COLORS.accent};
1645
+ }
1646
+ .badge:focus-visible {
1647
+ outline: 2px solid ${SHIELD_COLORS.accent};
1648
+ outline-offset: 2px;
1649
+ }
1650
+ .icon { flex-shrink: 0; display: block; }
1651
+ .text { white-space: nowrap; }
1652
+ :host([size="compact"]) .text { display: none; }
1653
+ :host([size="compact"]) .badge { padding: 4px 6px; }
1654
+ @media (prefers-reduced-motion: reduce) { .badge { transition: none; } }
1655
+ `.trim();
1656
+ }
1657
+
1658
+ // src/badge/multicorn-badge.ts
1659
+ var VERIFY_BASE = "https://multicorn.ai/verify/";
1660
+ var BADGE_ELEMENT_TAG = "multicorn-badge";
1661
+ var SHIELD_PATH = "M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z";
1662
+ function parseOptionalCount(raw) {
1663
+ if (raw == null || raw === "") {
1664
+ return void 0;
1665
+ }
1666
+ const n = Number(raw);
1667
+ return Number.isNaN(n) ? void 0 : n;
1668
+ }
1669
+ var MulticornBadge = class extends HTMLElement {
1670
+ #didInjectStyle = false;
1671
+ ensureShadow() {
1672
+ if (this.shadowRoot != null) {
1673
+ return this.shadowRoot;
1674
+ }
1675
+ return this.attachShadow({ mode: "open" });
1676
+ }
1677
+ static get observedAttributes() {
1678
+ return ["agent-id", "size", "theme", "action-count"];
1679
+ }
1680
+ connectedCallback() {
1681
+ this.render();
1682
+ }
1683
+ attributeChangedCallback() {
1684
+ this.render();
1685
+ }
1686
+ render() {
1687
+ const root = this.ensureShadow();
1688
+ if (this.#didInjectStyle) ; else {
1689
+ const style = document.createElement("style");
1690
+ style.textContent = getBadgeStyleText();
1691
+ root.appendChild(style);
1692
+ this.#didInjectStyle = true;
1693
+ }
1694
+ const agentId = (this.getAttribute("agent-id") ?? "").trim();
1695
+ const actionCount = parseOptionalCount(this.getAttribute("action-count"));
1696
+ const prior = root.querySelector("a.badge");
1697
+ if (prior) {
1698
+ prior.remove();
1699
+ }
1700
+ if (agentId === "") {
1701
+ return;
1702
+ }
1703
+ let labelSuffix;
1704
+ let ariaLabel;
1705
+ if (actionCount == null) {
1706
+ labelSuffix = "Secured by Multicorn";
1707
+ ariaLabel = "Secured by Multicorn, verify this agent";
1708
+ } else {
1709
+ const count = actionCount;
1710
+ const countText = String(count);
1711
+ labelSuffix = "Secured by Multicorn \xB7 " + countText + " actions secured";
1712
+ ariaLabel = "Secured by Multicorn \xB7 " + countText + " actions secured, verify this agent";
1713
+ }
1714
+ const href = `${VERIFY_BASE}${encodeURIComponent(agentId)}`;
1715
+ const a = document.createElement("a");
1716
+ a.className = "badge";
1717
+ a.href = href;
1718
+ a.target = "_blank";
1719
+ a.rel = "noopener noreferrer";
1720
+ a.setAttribute("aria-label", ariaLabel);
1721
+ const svgNs = "http://www.w3.org/2000/svg";
1722
+ const svg = document.createElementNS(svgNs, "svg");
1723
+ svg.setAttribute("class", "icon");
1724
+ svg.setAttribute("width", "16");
1725
+ svg.setAttribute("height", "16");
1726
+ svg.setAttribute("viewBox", "0 0 24 24");
1727
+ svg.setAttribute("aria-hidden", "true");
1728
+ const path = document.createElementNS(svgNs, "path");
1729
+ path.setAttribute("d", SHIELD_PATH);
1730
+ path.setAttribute("fill", SHIELD_COLORS.accent);
1731
+ svg.appendChild(path);
1732
+ a.appendChild(svg);
1733
+ const text = document.createElement("span");
1734
+ text.className = "text";
1735
+ text.textContent = labelSuffix;
1736
+ a.appendChild(text);
1737
+ root.appendChild(a);
1738
+ }
1739
+ };
1740
+ if (customElements.get(BADGE_ELEMENT_TAG) === void 0) {
1741
+ customElements.define(BADGE_ELEMENT_TAG, MulticornBadge);
1742
+ }
1743
+
1602
1744
  // src/logger/action-logger.ts
1603
1745
  function createActionLogger(config) {
1604
1746
  if (!config.apiKey || config.apiKey.trim().length === 0) {
@@ -2749,6 +2891,7 @@ exports.ACTION_STATUSES = ACTION_STATUSES;
2749
2891
  exports.AGENT_STATUSES = AGENT_STATUSES;
2750
2892
  exports.BUILT_IN_SERVICES = BUILT_IN_SERVICES;
2751
2893
  exports.CONSENT_ELEMENT_TAG = CONSENT_ELEMENT_TAG;
2894
+ exports.MulticornBadge = MulticornBadge;
2752
2895
  exports.MulticornShield = MulticornShield;
2753
2896
  exports.PERMISSION_LEVELS = PERMISSION_LEVELS;
2754
2897
  exports.SERVICE_NAME_PATTERN = SERVICE_NAME_PATTERN;
package/dist/index.d.cts CHANGED
@@ -1069,6 +1069,29 @@ interface FocusTrap {
1069
1069
  */
1070
1070
  declare function createFocusTrap(container: HTMLElement, initialFocus?: HTMLElement | null): FocusTrap;
1071
1071
 
1072
+ /**
1073
+ * `<multicorn-badge>`: small embeddable trust badge (Shadow DOM).
1074
+ * Implemented as a native custom element to keep the CDN `badge.js` under the
1075
+ * size budget. Styling and tokens align with the Lit-based consent screen.
1076
+ *
1077
+ * @module badge/multicorn-badge
1078
+ */
1079
+ /** Custom element tag for the trust badge. */
1080
+ declare const BADGE_ELEMENT_TAG: "multicorn-badge";
1081
+ declare class MulticornBadge extends HTMLElement {
1082
+ #private;
1083
+ private ensureShadow;
1084
+ static get observedAttributes(): string[];
1085
+ connectedCallback(): void;
1086
+ attributeChangedCallback(): void;
1087
+ private render;
1088
+ }
1089
+ declare global {
1090
+ interface HTMLElementTagNameMap {
1091
+ [BADGE_ELEMENT_TAG]: MulticornBadge;
1092
+ }
1093
+ }
1094
+
1072
1095
  /**
1073
1096
  * Action logging client for Multicorn Shield.
1074
1097
  *
@@ -2283,4 +2306,4 @@ interface ContentReviewRequestPayload {
2283
2306
  */
2284
2307
  declare function requestContentReview(payload: ContentReviewRequestPayload, apiKey: string, baseUrl: string, logger?: PluginLogger): Promise<ContentReviewResult>;
2285
2308
 
2286
- export { ACTION_STATUSES, AGENT_STATUSES, type Action, type ActionInput, type ActionLogger, type ActionLoggerConfig, type ActionPayload, type ActionStatus, type Agent, type AgentStatus, type ApiError, BUILT_IN_SERVICES, type BatchModeConfig, type BuiltInServiceName, CONSENT_ELEMENT_TAG, type ConsentDecision, type ConsentDeniedEventDetail, type ConsentEventDetail, type ConsentEventMap, type ConsentEventName, type ConsentGrantedEventDetail, type ConsentOptions, type ConsentPartialEventDetail, type ContentReviewRequestPayload, type ContentReviewResult, type ContentReviewStatusResponse, type FocusTrap, type McpAdapter, type McpAdapterConfig, type McpAdapterResult, type McpBlockedResult, type McpToolCall, type McpToolHandler, type McpToolResult, MulticornConsent, MulticornShield, type MulticornShieldConfig, PERMISSION_LEVELS, type Permission, type PermissionLevel, type RemainingBudget, SERVICE_NAME_PATTERN, type Scope, ScopeParseError, type ScopeParseResult, type ScopeRegistry, type ScopeRequest, type ServiceDefinition, type SpendCheckResult, type SpendingCheckResult, type SpendingChecker, type SpendingLimit, type SpendingLimits, type SpendingTrackerConfig, type ValidationResult, centsToDollars, createActionLogger, createFocusTrap, createMcpAdapter, createScopeRegistry, createSpendingChecker, dollarsToCents, formatScope, getPermissionLabel, getScopeLabel, getScopeShortLabel, getServiceDisplayName, getServiceIcon, hasScope, isBlockedResult, isPublicContentAction, isValidScopeString, parseScope, parseScopes, requestContentReview, requiresContentReview, tryParseScope, validateAllScopesAccess, validateScopeAccess };
2309
+ export { ACTION_STATUSES, AGENT_STATUSES, type Action, type ActionInput, type ActionLogger, type ActionLoggerConfig, type ActionPayload, type ActionStatus, type Agent, type AgentStatus, type ApiError, BUILT_IN_SERVICES, type BatchModeConfig, type BuiltInServiceName, CONSENT_ELEMENT_TAG, type ConsentDecision, type ConsentDeniedEventDetail, type ConsentEventDetail, type ConsentEventMap, type ConsentEventName, type ConsentGrantedEventDetail, type ConsentOptions, type ConsentPartialEventDetail, type ContentReviewRequestPayload, type ContentReviewResult, type ContentReviewStatusResponse, type FocusTrap, type McpAdapter, type McpAdapterConfig, type McpAdapterResult, type McpBlockedResult, type McpToolCall, type McpToolHandler, type McpToolResult, MulticornBadge, MulticornConsent, MulticornShield, type MulticornShieldConfig, PERMISSION_LEVELS, type Permission, type PermissionLevel, type RemainingBudget, SERVICE_NAME_PATTERN, type Scope, ScopeParseError, type ScopeParseResult, type ScopeRegistry, type ScopeRequest, type ServiceDefinition, type SpendCheckResult, type SpendingCheckResult, type SpendingChecker, type SpendingLimit, type SpendingLimits, type SpendingTrackerConfig, type ValidationResult, centsToDollars, createActionLogger, createFocusTrap, createMcpAdapter, createScopeRegistry, createSpendingChecker, dollarsToCents, formatScope, getPermissionLabel, getScopeLabel, getScopeShortLabel, getServiceDisplayName, getServiceIcon, hasScope, isBlockedResult, isPublicContentAction, isValidScopeString, parseScope, parseScopes, requestContentReview, requiresContentReview, tryParseScope, validateAllScopesAccess, validateScopeAccess };
package/dist/index.d.ts CHANGED
@@ -1069,6 +1069,29 @@ interface FocusTrap {
1069
1069
  */
1070
1070
  declare function createFocusTrap(container: HTMLElement, initialFocus?: HTMLElement | null): FocusTrap;
1071
1071
 
1072
+ /**
1073
+ * `<multicorn-badge>`: small embeddable trust badge (Shadow DOM).
1074
+ * Implemented as a native custom element to keep the CDN `badge.js` under the
1075
+ * size budget. Styling and tokens align with the Lit-based consent screen.
1076
+ *
1077
+ * @module badge/multicorn-badge
1078
+ */
1079
+ /** Custom element tag for the trust badge. */
1080
+ declare const BADGE_ELEMENT_TAG: "multicorn-badge";
1081
+ declare class MulticornBadge extends HTMLElement {
1082
+ #private;
1083
+ private ensureShadow;
1084
+ static get observedAttributes(): string[];
1085
+ connectedCallback(): void;
1086
+ attributeChangedCallback(): void;
1087
+ private render;
1088
+ }
1089
+ declare global {
1090
+ interface HTMLElementTagNameMap {
1091
+ [BADGE_ELEMENT_TAG]: MulticornBadge;
1092
+ }
1093
+ }
1094
+
1072
1095
  /**
1073
1096
  * Action logging client for Multicorn Shield.
1074
1097
  *
@@ -2283,4 +2306,4 @@ interface ContentReviewRequestPayload {
2283
2306
  */
2284
2307
  declare function requestContentReview(payload: ContentReviewRequestPayload, apiKey: string, baseUrl: string, logger?: PluginLogger): Promise<ContentReviewResult>;
2285
2308
 
2286
- export { ACTION_STATUSES, AGENT_STATUSES, type Action, type ActionInput, type ActionLogger, type ActionLoggerConfig, type ActionPayload, type ActionStatus, type Agent, type AgentStatus, type ApiError, BUILT_IN_SERVICES, type BatchModeConfig, type BuiltInServiceName, CONSENT_ELEMENT_TAG, type ConsentDecision, type ConsentDeniedEventDetail, type ConsentEventDetail, type ConsentEventMap, type ConsentEventName, type ConsentGrantedEventDetail, type ConsentOptions, type ConsentPartialEventDetail, type ContentReviewRequestPayload, type ContentReviewResult, type ContentReviewStatusResponse, type FocusTrap, type McpAdapter, type McpAdapterConfig, type McpAdapterResult, type McpBlockedResult, type McpToolCall, type McpToolHandler, type McpToolResult, MulticornConsent, MulticornShield, type MulticornShieldConfig, PERMISSION_LEVELS, type Permission, type PermissionLevel, type RemainingBudget, SERVICE_NAME_PATTERN, type Scope, ScopeParseError, type ScopeParseResult, type ScopeRegistry, type ScopeRequest, type ServiceDefinition, type SpendCheckResult, type SpendingCheckResult, type SpendingChecker, type SpendingLimit, type SpendingLimits, type SpendingTrackerConfig, type ValidationResult, centsToDollars, createActionLogger, createFocusTrap, createMcpAdapter, createScopeRegistry, createSpendingChecker, dollarsToCents, formatScope, getPermissionLabel, getScopeLabel, getScopeShortLabel, getServiceDisplayName, getServiceIcon, hasScope, isBlockedResult, isPublicContentAction, isValidScopeString, parseScope, parseScopes, requestContentReview, requiresContentReview, tryParseScope, validateAllScopesAccess, validateScopeAccess };
2309
+ export { ACTION_STATUSES, AGENT_STATUSES, type Action, type ActionInput, type ActionLogger, type ActionLoggerConfig, type ActionPayload, type ActionStatus, type Agent, type AgentStatus, type ApiError, BUILT_IN_SERVICES, type BatchModeConfig, type BuiltInServiceName, CONSENT_ELEMENT_TAG, type ConsentDecision, type ConsentDeniedEventDetail, type ConsentEventDetail, type ConsentEventMap, type ConsentEventName, type ConsentGrantedEventDetail, type ConsentOptions, type ConsentPartialEventDetail, type ContentReviewRequestPayload, type ContentReviewResult, type ContentReviewStatusResponse, type FocusTrap, type McpAdapter, type McpAdapterConfig, type McpAdapterResult, type McpBlockedResult, type McpToolCall, type McpToolHandler, type McpToolResult, MulticornBadge, MulticornConsent, MulticornShield, type MulticornShieldConfig, PERMISSION_LEVELS, type Permission, type PermissionLevel, type RemainingBudget, SERVICE_NAME_PATTERN, type Scope, ScopeParseError, type ScopeParseResult, type ScopeRegistry, type ScopeRequest, type ServiceDefinition, type SpendCheckResult, type SpendingCheckResult, type SpendingChecker, type SpendingLimit, type SpendingLimits, type SpendingTrackerConfig, type ValidationResult, centsToDollars, createActionLogger, createFocusTrap, createMcpAdapter, createScopeRegistry, createSpendingChecker, dollarsToCents, formatScope, getPermissionLabel, getScopeLabel, getScopeShortLabel, getServiceDisplayName, getServiceIcon, hasScope, isBlockedResult, isPublicContentAction, isValidScopeString, parseScope, parseScopes, requestContentReview, requiresContentReview, tryParseScope, validateAllScopesAccess, validateScopeAccess };
package/dist/index.js CHANGED
@@ -430,6 +430,8 @@ function getScopeWarning(scopeString) {
430
430
  const metadata = getScopeMetadata(scopeString);
431
431
  return metadata?.warningMessage;
432
432
  }
433
+
434
+ // src/shared/shield-tokens.ts
433
435
  var SHIELD_COLORS = {
434
436
  bg: "#0d0d14",
435
437
  surface: "#14141f",
@@ -450,6 +452,8 @@ var SHIELD_COLORS = {
450
452
  red: "#ef4444",
451
453
  redDim: "rgba(239, 68, 68, 0.12)"
452
454
  };
455
+
456
+ // src/consent/consent-styles.ts
453
457
  var consentStyles = css`
454
458
  :host {
455
459
  display: block;
@@ -1597,6 +1601,144 @@ MulticornConsent = __decorateClass([
1597
1601
  customElement(CONSENT_ELEMENT_TAG)
1598
1602
  ], MulticornConsent);
1599
1603
 
1604
+ // src/badge/badge-styles.ts
1605
+ var LIGHT_TEXT = "#0f172a";
1606
+ var LIGHT_SURFACE = "#f8fafc";
1607
+ var LIGHT_SURFACE_HOVER = "#f1f5f9";
1608
+ var LIGHT_BORDER = "#e2e8f0";
1609
+ function getBadgeStyleText() {
1610
+ return `
1611
+ :host { display: inline-block; line-height: 0; }
1612
+ .badge {
1613
+ display: inline-flex;
1614
+ align-items: center;
1615
+ justify-content: center;
1616
+ box-sizing: border-box;
1617
+ gap: 6px;
1618
+ min-height: 28px;
1619
+ padding: 4px 10px 4px 8px;
1620
+ border-radius: 9999px;
1621
+ text-decoration: none;
1622
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1623
+ font-size: 12px;
1624
+ font-weight: 500;
1625
+ border: 1px solid ${SHIELD_COLORS.border};
1626
+ background: ${SHIELD_COLORS.surface};
1627
+ color: ${SHIELD_COLORS.text};
1628
+ transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
1629
+ }
1630
+ :host([theme="light"]) .badge {
1631
+ border-color: ${LIGHT_BORDER};
1632
+ background: ${LIGHT_SURFACE};
1633
+ color: ${LIGHT_TEXT};
1634
+ }
1635
+ .badge:hover {
1636
+ background: ${SHIELD_COLORS.surfaceHover};
1637
+ border-color: ${SHIELD_COLORS.accent};
1638
+ box-shadow: 0 0 0 1px ${SHIELD_COLORS.accentDim};
1639
+ }
1640
+ :host([theme="light"]) .badge:hover {
1641
+ background: ${LIGHT_SURFACE_HOVER};
1642
+ border-color: ${SHIELD_COLORS.accent};
1643
+ }
1644
+ .badge:focus-visible {
1645
+ outline: 2px solid ${SHIELD_COLORS.accent};
1646
+ outline-offset: 2px;
1647
+ }
1648
+ .icon { flex-shrink: 0; display: block; }
1649
+ .text { white-space: nowrap; }
1650
+ :host([size="compact"]) .text { display: none; }
1651
+ :host([size="compact"]) .badge { padding: 4px 6px; }
1652
+ @media (prefers-reduced-motion: reduce) { .badge { transition: none; } }
1653
+ `.trim();
1654
+ }
1655
+
1656
+ // src/badge/multicorn-badge.ts
1657
+ var VERIFY_BASE = "https://multicorn.ai/verify/";
1658
+ var BADGE_ELEMENT_TAG = "multicorn-badge";
1659
+ var SHIELD_PATH = "M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z";
1660
+ function parseOptionalCount(raw) {
1661
+ if (raw == null || raw === "") {
1662
+ return void 0;
1663
+ }
1664
+ const n = Number(raw);
1665
+ return Number.isNaN(n) ? void 0 : n;
1666
+ }
1667
+ var MulticornBadge = class extends HTMLElement {
1668
+ #didInjectStyle = false;
1669
+ ensureShadow() {
1670
+ if (this.shadowRoot != null) {
1671
+ return this.shadowRoot;
1672
+ }
1673
+ return this.attachShadow({ mode: "open" });
1674
+ }
1675
+ static get observedAttributes() {
1676
+ return ["agent-id", "size", "theme", "action-count"];
1677
+ }
1678
+ connectedCallback() {
1679
+ this.render();
1680
+ }
1681
+ attributeChangedCallback() {
1682
+ this.render();
1683
+ }
1684
+ render() {
1685
+ const root = this.ensureShadow();
1686
+ if (this.#didInjectStyle) ; else {
1687
+ const style = document.createElement("style");
1688
+ style.textContent = getBadgeStyleText();
1689
+ root.appendChild(style);
1690
+ this.#didInjectStyle = true;
1691
+ }
1692
+ const agentId = (this.getAttribute("agent-id") ?? "").trim();
1693
+ const actionCount = parseOptionalCount(this.getAttribute("action-count"));
1694
+ const prior = root.querySelector("a.badge");
1695
+ if (prior) {
1696
+ prior.remove();
1697
+ }
1698
+ if (agentId === "") {
1699
+ return;
1700
+ }
1701
+ let labelSuffix;
1702
+ let ariaLabel;
1703
+ if (actionCount == null) {
1704
+ labelSuffix = "Secured by Multicorn";
1705
+ ariaLabel = "Secured by Multicorn, verify this agent";
1706
+ } else {
1707
+ const count = actionCount;
1708
+ const countText = String(count);
1709
+ labelSuffix = "Secured by Multicorn \xB7 " + countText + " actions secured";
1710
+ ariaLabel = "Secured by Multicorn \xB7 " + countText + " actions secured, verify this agent";
1711
+ }
1712
+ const href = `${VERIFY_BASE}${encodeURIComponent(agentId)}`;
1713
+ const a = document.createElement("a");
1714
+ a.className = "badge";
1715
+ a.href = href;
1716
+ a.target = "_blank";
1717
+ a.rel = "noopener noreferrer";
1718
+ a.setAttribute("aria-label", ariaLabel);
1719
+ const svgNs = "http://www.w3.org/2000/svg";
1720
+ const svg = document.createElementNS(svgNs, "svg");
1721
+ svg.setAttribute("class", "icon");
1722
+ svg.setAttribute("width", "16");
1723
+ svg.setAttribute("height", "16");
1724
+ svg.setAttribute("viewBox", "0 0 24 24");
1725
+ svg.setAttribute("aria-hidden", "true");
1726
+ const path = document.createElementNS(svgNs, "path");
1727
+ path.setAttribute("d", SHIELD_PATH);
1728
+ path.setAttribute("fill", SHIELD_COLORS.accent);
1729
+ svg.appendChild(path);
1730
+ a.appendChild(svg);
1731
+ const text = document.createElement("span");
1732
+ text.className = "text";
1733
+ text.textContent = labelSuffix;
1734
+ a.appendChild(text);
1735
+ root.appendChild(a);
1736
+ }
1737
+ };
1738
+ if (customElements.get(BADGE_ELEMENT_TAG) === void 0) {
1739
+ customElements.define(BADGE_ELEMENT_TAG, MulticornBadge);
1740
+ }
1741
+
1600
1742
  // src/logger/action-logger.ts
1601
1743
  function createActionLogger(config) {
1602
1744
  if (!config.apiKey || config.apiKey.trim().length === 0) {
@@ -2743,4 +2885,4 @@ function validateApiKey(apiKey) {
2743
2885
  }
2744
2886
  }
2745
2887
 
2746
- export { ACTION_STATUSES, AGENT_STATUSES, BUILT_IN_SERVICES, CONSENT_ELEMENT_TAG, MulticornConsent, MulticornShield, PERMISSION_LEVELS, SERVICE_NAME_PATTERN, ScopeParseError, centsToDollars, createActionLogger, createFocusTrap, createMcpAdapter, createScopeRegistry, createSpendingChecker, dollarsToCents, formatScope, getPermissionLabel, getScopeLabel, getScopeShortLabel, getServiceDisplayName, getServiceIcon, hasScope, isBlockedResult, isPublicContentAction, isValidScopeString, parseScope, parseScopes, requestContentReview, requiresContentReview, tryParseScope, validateAllScopesAccess, validateScopeAccess };
2888
+ export { ACTION_STATUSES, AGENT_STATUSES, BUILT_IN_SERVICES, CONSENT_ELEMENT_TAG, MulticornBadge, MulticornConsent, MulticornShield, PERMISSION_LEVELS, SERVICE_NAME_PATTERN, ScopeParseError, centsToDollars, createActionLogger, createFocusTrap, createMcpAdapter, createScopeRegistry, createSpendingChecker, dollarsToCents, formatScope, getPermissionLabel, getScopeLabel, getScopeShortLabel, getServiceDisplayName, getServiceIcon, hasScope, isBlockedResult, isPublicContentAction, isValidScopeString, parseScope, parseScopes, requestContentReview, requiresContentReview, tryParseScope, validateAllScopesAccess, validateScopeAccess };
@@ -884,7 +884,9 @@ var plugin = {
884
884
  if (config.agentName !== null) {
885
885
  pinnedAgentName = config.agentName;
886
886
  }
887
- console.error("[SHIELD-DIAG] cachedMulticornConfig: " + JSON.stringify(cachedMulticornConfig));
887
+ api.logger.info(
888
+ `Multicorn Shield config loaded: hasApiKey=${String((cachedMulticornConfig?.apiKey ?? "").length > 0)} baseUrl=${cachedMulticornConfig?.baseUrl ?? "default"} agentName=${cachedMulticornConfig?.agentName ?? "unset"} defaultAgent=${cachedMulticornConfig?.defaultAgent ?? "unset"} agents=${String(cachedMulticornConfig?.agents?.length ?? 0)}`
889
+ );
888
890
  api.on("before_tool_call", beforeToolCall, { priority: 10 });
889
891
  api.on("after_tool_call", afterToolCall);
890
892
  api.logger.info("Multicorn Shield plugin registered.");
@@ -22359,11 +22359,117 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22359
22359
 
22360
22360
  // package.json
22361
22361
  var package_default = {
22362
- version: "0.10.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,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "0.10.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
6
  "author": "Multicorn AI Pty Ltd",
@@ -45,13 +45,18 @@
45
45
  "access": "public",
46
46
  "provenance": true
47
47
  },
48
- "sideEffects": false,
48
+ "sideEffects": [
49
+ "dist/index.js",
50
+ "dist/index.cjs",
51
+ "dist/badge.js",
52
+ "src/badge/multicorn-badge.ts"
53
+ ],
49
54
  "engines": {
50
55
  "node": ">=20"
51
56
  },
52
57
  "lint-staged": {
53
58
  "*.ts": [
54
- "eslint --fix",
59
+ "eslint --fix --no-warn-ignored",
55
60
  "prettier --write"
56
61
  ],
57
62
  "*.{json,md}": [
@@ -97,6 +102,11 @@
97
102
  "path": "dist/index.cjs",
98
103
  "limit": "50 kB",
99
104
  "gzip": true
105
+ },
106
+ {
107
+ "path": "dist/badge.js",
108
+ "limit": "5 kB",
109
+ "gzip": true
100
110
  }
101
111
  ],
102
112
  "keywords": [
@@ -123,8 +133,8 @@
123
133
  "scripts": {
124
134
  "build": "tsup",
125
135
  "dev": "tsup --watch",
126
- "lint": "eslint . && prettier --check .",
127
- "lint:fix": "eslint --fix . && prettier --write .",
136
+ "lint": "eslint . --no-warn-ignored && prettier --check .",
137
+ "lint:fix": "eslint --fix . --no-warn-ignored && prettier --write .",
128
138
  "test": "vitest run",
129
139
  "test:watch": "vitest",
130
140
  "test:coverage": "vitest run --coverage",