multicorn-shield 0.11.0 → 0.12.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 +30 -0
- package/dist/badge.js +4 -4
- package/dist/index.cjs +25 -19
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +24 -19
- package/dist/multicorn-proxy.js +181 -9
- package/dist/openclaw-hook/handler.js +0 -1
- package/dist/openclaw-plugin/multicorn-shield.js +11 -17
- package/dist/openclaw-plugin/openclaw.plugin.json +3 -1
- package/dist/proxy.cjs +174 -0
- package/dist/proxy.d.cts +228 -1
- package/dist/proxy.d.ts +228 -1
- package/dist/proxy.js +174 -1
- package/dist/shield-extension.js +1 -4
- package/package.json +3 -2
- package/plugins/cline/README.md +61 -0
- package/plugins/cline/hooks/scripts/post-tool-use.cjs +116 -0
- package/plugins/cline/hooks/scripts/pre-tool-use.cjs +271 -0
- package/plugins/cline/hooks/scripts/shared.cjs +303 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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
|
+
## [X.Y.Z] - YYYY-MM-DD
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Cline native plugin support via PreToolUse/PostToolUse hooks
|
|
13
|
+
- Hook scripts for Cline: pre-tool-use.cjs, post-tool-use.cjs, shared.cjs
|
|
14
|
+
- Cline plugin README with setup instructions and troubleshooting
|
|
15
|
+
- Browser auto-open for consent screen when Shield blocks an action
|
|
16
|
+
- Licence headers on all plugin scripts
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- CLI wizard installs Cline hooks to ~/Documents/Cline/Hooks/ (previously ~/Documents/Cline/Rules/Hooks/)
|
|
21
|
+
- Cline hook reads toolName field from hook input (Cline v3.81+ sends toolName, not tool)
|
|
22
|
+
- Consent flow no longer polls for approval (blocks immediately with consent URL to avoid Cline's 30-second hook timeout)
|
|
23
|
+
- Extracted shared utilities (config loading, HTTP, tool mapping) into shared.cjs to eliminate duplication between hooks
|
|
24
|
+
- Parameter metadata scrubbed before sending to Shield API (file contents redacted, commands truncated)
|
|
25
|
+
- HTTPS enforced for non-local Shield API connections
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
- Polling-based consent approval flow (replaced with immediate block + consent URL)
|
|
30
|
+
- Consent marker filesystem state (no longer needed without polling)
|
|
31
|
+
|
|
32
|
+
### Security
|
|
33
|
+
|
|
34
|
+
- Fixed Windows shell injection in openBrowser (replaced execSync with execFileSync)
|
|
35
|
+
- Added HTTPS enforcement for non-localhost baseUrl in hook config
|
|
36
|
+
- Added parameter and result scrubbing to prevent sensitive data leakage in audit metadata
|
|
37
|
+
|
|
8
38
|
## [0.11.0] - 2026-04-25
|
|
9
39
|
|
|
10
40
|
### Added
|
package/dist/badge.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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",
|
|
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",L="#f1f5f9",A="#e2e8f0";function h(){return `
|
|
2
2
|
:host { display: inline-block; line-height: 0; }
|
|
3
3
|
.badge {
|
|
4
4
|
display: inline-flex;
|
|
@@ -19,7 +19,7 @@ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",
|
|
|
19
19
|
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
|
20
20
|
}
|
|
21
21
|
:host([theme="light"]) .badge {
|
|
22
|
-
border-color: ${
|
|
22
|
+
border-color: ${A};
|
|
23
23
|
background: ${y};
|
|
24
24
|
color: ${E};
|
|
25
25
|
}
|
|
@@ -29,7 +29,7 @@ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",
|
|
|
29
29
|
box-shadow: 0 0 0 1px ${n.accentDim};
|
|
30
30
|
}
|
|
31
31
|
:host([theme="light"]) .badge:hover {
|
|
32
|
-
background: ${
|
|
32
|
+
background: ${L};
|
|
33
33
|
border-color: ${n.accent};
|
|
34
34
|
}
|
|
35
35
|
.badge:focus-visible {
|
|
@@ -41,4 +41,4 @@ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",
|
|
|
41
41
|
:host([size="compact"]) .text { display: none; }
|
|
42
42
|
:host([size="compact"]) .badge { padding: 4px 6px; }
|
|
43
43
|
@media (prefers-reduced-motion: reduce) { .badge { transition: none; } }
|
|
44
|
-
`.trim()}var
|
|
44
|
+
`.trim()}var T="https://multicorn.ai/verify/",x="multicorn-badge",v="M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z",w=typeof HTMLElement<"u"?HTMLElement:class{connectedCallback(){}};function C(e){if(e==null||e==="")return;let t=Number(e);return Number.isNaN(t)?void 0:t}var m=class extends w{#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=`${T}${encodeURIComponent(s)}`,o=document.createElement("a");o.className="badge",o.href=S,o.target="_blank",o.rel="noopener noreferrer",o.setAttribute("aria-label",l);let p="http://www.w3.org/2000/svg",r=document.createElementNS(p,"svg");r.setAttribute("class","icon"),r.setAttribute("width","16"),r.setAttribute("height","16"),r.setAttribute("viewBox","0 0 24 24"),r.setAttribute("aria-hidden","true");let u=document.createElementNS(p,"path");u.setAttribute("d",v),u.setAttribute("fill",n.accent),r.appendChild(u),o.appendChild(r);let b=document.createElement("span");b.className="text",b.textContent=d,o.appendChild(b),t.appendChild(o);}};typeof customElements<"u"&&customElements.get(x)===void 0&&customElements.define(x,m);function H(e){return e==="compact"||e==="standard"}function M(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;H(s)&&t.setAttribute("size",s);let c=i.dataset.theme;M(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
|
@@ -4,13 +4,12 @@ var lit = require('lit');
|
|
|
4
4
|
var decorators_js = require('lit/decorators.js');
|
|
5
5
|
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
7
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
7
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
9
|
-
var result =
|
|
8
|
+
var result = void 0 ;
|
|
10
9
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
11
10
|
if (decorator = decorators[i])
|
|
12
|
-
result = (
|
|
13
|
-
if (
|
|
11
|
+
result = (decorator(target, key, result) ) || result;
|
|
12
|
+
if (result) __defProp(target, key, result);
|
|
14
13
|
return result;
|
|
15
14
|
};
|
|
16
15
|
|
|
@@ -1128,7 +1127,7 @@ function groupScopesByService(scopes) {
|
|
|
1128
1127
|
function scopeKey(scope) {
|
|
1129
1128
|
return `${scope.service}:${scope.permissionLevel}`;
|
|
1130
1129
|
}
|
|
1131
|
-
|
|
1130
|
+
var MulticornConsent = class extends lit.LitElement {
|
|
1132
1131
|
constructor() {
|
|
1133
1132
|
super(...arguments);
|
|
1134
1133
|
this.agentName = "";
|
|
@@ -1201,6 +1200,9 @@ exports.MulticornConsent = class MulticornConsent extends lit.LitElement {
|
|
|
1201
1200
|
}
|
|
1202
1201
|
};
|
|
1203
1202
|
}
|
|
1203
|
+
static {
|
|
1204
|
+
this.styles = [consentStyles];
|
|
1205
|
+
}
|
|
1204
1206
|
connectedCallback() {
|
|
1205
1207
|
super.connectedCallback();
|
|
1206
1208
|
if (Array.isArray(this.scopes) && this.scopes.length > 0) {
|
|
@@ -1574,34 +1576,33 @@ exports.MulticornConsent = class MulticornConsent extends lit.LitElement {
|
|
|
1574
1576
|
this.requestUpdate();
|
|
1575
1577
|
}
|
|
1576
1578
|
};
|
|
1577
|
-
exports.MulticornConsent.styles = [consentStyles];
|
|
1578
1579
|
__decorateClass([
|
|
1579
1580
|
decorators_js.property({ type: String, attribute: "agent-name" })
|
|
1580
|
-
],
|
|
1581
|
+
], MulticornConsent.prototype, "agentName");
|
|
1581
1582
|
__decorateClass([
|
|
1582
1583
|
decorators_js.property({ type: String, attribute: "agent-color" })
|
|
1583
|
-
],
|
|
1584
|
+
], MulticornConsent.prototype, "agentColor");
|
|
1584
1585
|
__decorateClass([
|
|
1585
1586
|
decorators_js.property({ type: Array, attribute: "scopes" })
|
|
1586
|
-
],
|
|
1587
|
+
], MulticornConsent.prototype, "scopes");
|
|
1587
1588
|
__decorateClass([
|
|
1588
1589
|
decorators_js.property({ type: Number, attribute: "spend-limit" })
|
|
1589
|
-
],
|
|
1590
|
+
], MulticornConsent.prototype, "spendLimit");
|
|
1590
1591
|
__decorateClass([
|
|
1591
1592
|
decorators_js.property({ type: String })
|
|
1592
|
-
],
|
|
1593
|
+
], MulticornConsent.prototype, "mode");
|
|
1593
1594
|
__decorateClass([
|
|
1594
1595
|
decorators_js.state()
|
|
1595
|
-
],
|
|
1596
|
+
], MulticornConsent.prototype, "_grantedScopes");
|
|
1596
1597
|
__decorateClass([
|
|
1597
1598
|
decorators_js.state()
|
|
1598
|
-
],
|
|
1599
|
+
], MulticornConsent.prototype, "_adjustedSpendLimit");
|
|
1599
1600
|
__decorateClass([
|
|
1600
1601
|
decorators_js.state()
|
|
1601
|
-
],
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1602
|
+
], MulticornConsent.prototype, "_isOpen");
|
|
1603
|
+
if (typeof customElements !== "undefined" && customElements.get(CONSENT_ELEMENT_TAG) === void 0) {
|
|
1604
|
+
customElements.define(CONSENT_ELEMENT_TAG, MulticornConsent);
|
|
1605
|
+
}
|
|
1605
1606
|
|
|
1606
1607
|
// src/badge/badge-styles.ts
|
|
1607
1608
|
var LIGHT_TEXT = "#0f172a";
|
|
@@ -1659,6 +1660,10 @@ function getBadgeStyleText() {
|
|
|
1659
1660
|
var VERIFY_BASE = "https://multicorn.ai/verify/";
|
|
1660
1661
|
var BADGE_ELEMENT_TAG = "multicorn-badge";
|
|
1661
1662
|
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";
|
|
1663
|
+
var SafeHTMLElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
|
|
1664
|
+
connectedCallback() {
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1662
1667
|
function parseOptionalCount(raw) {
|
|
1663
1668
|
if (raw == null || raw === "") {
|
|
1664
1669
|
return void 0;
|
|
@@ -1666,7 +1671,7 @@ function parseOptionalCount(raw) {
|
|
|
1666
1671
|
const n = Number(raw);
|
|
1667
1672
|
return Number.isNaN(n) ? void 0 : n;
|
|
1668
1673
|
}
|
|
1669
|
-
var MulticornBadge = class extends
|
|
1674
|
+
var MulticornBadge = class extends SafeHTMLElement {
|
|
1670
1675
|
#didInjectStyle = false;
|
|
1671
1676
|
ensureShadow() {
|
|
1672
1677
|
if (this.shadowRoot != null) {
|
|
@@ -1737,7 +1742,7 @@ var MulticornBadge = class extends HTMLElement {
|
|
|
1737
1742
|
root.appendChild(a);
|
|
1738
1743
|
}
|
|
1739
1744
|
};
|
|
1740
|
-
if (customElements.get(BADGE_ELEMENT_TAG) === void 0) {
|
|
1745
|
+
if (typeof customElements !== "undefined" && customElements.get(BADGE_ELEMENT_TAG) === void 0) {
|
|
1741
1746
|
customElements.define(BADGE_ELEMENT_TAG, MulticornBadge);
|
|
1742
1747
|
}
|
|
1743
1748
|
|
|
@@ -2892,6 +2897,7 @@ exports.AGENT_STATUSES = AGENT_STATUSES;
|
|
|
2892
2897
|
exports.BUILT_IN_SERVICES = BUILT_IN_SERVICES;
|
|
2893
2898
|
exports.CONSENT_ELEMENT_TAG = CONSENT_ELEMENT_TAG;
|
|
2894
2899
|
exports.MulticornBadge = MulticornBadge;
|
|
2900
|
+
exports.MulticornConsent = MulticornConsent;
|
|
2895
2901
|
exports.MulticornShield = MulticornShield;
|
|
2896
2902
|
exports.PERMISSION_LEVELS = PERMISSION_LEVELS;
|
|
2897
2903
|
exports.SERVICE_NAME_PATTERN = SERVICE_NAME_PATTERN;
|
package/dist/index.d.cts
CHANGED
|
@@ -1078,7 +1078,11 @@ declare function createFocusTrap(container: HTMLElement, initialFocus?: HTMLElem
|
|
|
1078
1078
|
*/
|
|
1079
1079
|
/** Custom element tag for the trust badge. */
|
|
1080
1080
|
declare const BADGE_ELEMENT_TAG: "multicorn-badge";
|
|
1081
|
-
declare
|
|
1081
|
+
declare const SafeHTMLElement: {
|
|
1082
|
+
new (): HTMLElement;
|
|
1083
|
+
prototype: HTMLElement;
|
|
1084
|
+
};
|
|
1085
|
+
declare class MulticornBadge extends SafeHTMLElement {
|
|
1082
1086
|
#private;
|
|
1083
1087
|
private ensureShadow;
|
|
1084
1088
|
static get observedAttributes(): string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1078,7 +1078,11 @@ declare function createFocusTrap(container: HTMLElement, initialFocus?: HTMLElem
|
|
|
1078
1078
|
*/
|
|
1079
1079
|
/** Custom element tag for the trust badge. */
|
|
1080
1080
|
declare const BADGE_ELEMENT_TAG: "multicorn-badge";
|
|
1081
|
-
declare
|
|
1081
|
+
declare const SafeHTMLElement: {
|
|
1082
|
+
new (): HTMLElement;
|
|
1083
|
+
prototype: HTMLElement;
|
|
1084
|
+
};
|
|
1085
|
+
declare class MulticornBadge extends SafeHTMLElement {
|
|
1082
1086
|
#private;
|
|
1083
1087
|
private ensureShadow;
|
|
1084
1088
|
static get observedAttributes(): string[];
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { unsafeCSS, css, LitElement, html } from 'lit';
|
|
2
|
-
import { property, state
|
|
2
|
+
import { property, state } from 'lit/decorators.js';
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
5
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
7
|
-
var result =
|
|
6
|
+
var result = void 0 ;
|
|
8
7
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
9
8
|
if (decorator = decorators[i])
|
|
10
|
-
result = (
|
|
11
|
-
if (
|
|
9
|
+
result = (decorator(target, key, result) ) || result;
|
|
10
|
+
if (result) __defProp(target, key, result);
|
|
12
11
|
return result;
|
|
13
12
|
};
|
|
14
13
|
|
|
@@ -1199,6 +1198,9 @@ var MulticornConsent = class extends LitElement {
|
|
|
1199
1198
|
}
|
|
1200
1199
|
};
|
|
1201
1200
|
}
|
|
1201
|
+
static {
|
|
1202
|
+
this.styles = [consentStyles];
|
|
1203
|
+
}
|
|
1202
1204
|
connectedCallback() {
|
|
1203
1205
|
super.connectedCallback();
|
|
1204
1206
|
if (Array.isArray(this.scopes) && this.scopes.length > 0) {
|
|
@@ -1572,34 +1574,33 @@ var MulticornConsent = class extends LitElement {
|
|
|
1572
1574
|
this.requestUpdate();
|
|
1573
1575
|
}
|
|
1574
1576
|
};
|
|
1575
|
-
MulticornConsent.styles = [consentStyles];
|
|
1576
1577
|
__decorateClass([
|
|
1577
1578
|
property({ type: String, attribute: "agent-name" })
|
|
1578
|
-
], MulticornConsent.prototype, "agentName"
|
|
1579
|
+
], MulticornConsent.prototype, "agentName");
|
|
1579
1580
|
__decorateClass([
|
|
1580
1581
|
property({ type: String, attribute: "agent-color" })
|
|
1581
|
-
], MulticornConsent.prototype, "agentColor"
|
|
1582
|
+
], MulticornConsent.prototype, "agentColor");
|
|
1582
1583
|
__decorateClass([
|
|
1583
1584
|
property({ type: Array, attribute: "scopes" })
|
|
1584
|
-
], MulticornConsent.prototype, "scopes"
|
|
1585
|
+
], MulticornConsent.prototype, "scopes");
|
|
1585
1586
|
__decorateClass([
|
|
1586
1587
|
property({ type: Number, attribute: "spend-limit" })
|
|
1587
|
-
], MulticornConsent.prototype, "spendLimit"
|
|
1588
|
+
], MulticornConsent.prototype, "spendLimit");
|
|
1588
1589
|
__decorateClass([
|
|
1589
1590
|
property({ type: String })
|
|
1590
|
-
], MulticornConsent.prototype, "mode"
|
|
1591
|
+
], MulticornConsent.prototype, "mode");
|
|
1591
1592
|
__decorateClass([
|
|
1592
1593
|
state()
|
|
1593
|
-
], MulticornConsent.prototype, "_grantedScopes"
|
|
1594
|
+
], MulticornConsent.prototype, "_grantedScopes");
|
|
1594
1595
|
__decorateClass([
|
|
1595
1596
|
state()
|
|
1596
|
-
], MulticornConsent.prototype, "_adjustedSpendLimit"
|
|
1597
|
+
], MulticornConsent.prototype, "_adjustedSpendLimit");
|
|
1597
1598
|
__decorateClass([
|
|
1598
1599
|
state()
|
|
1599
|
-
], MulticornConsent.prototype, "_isOpen"
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1600
|
+
], MulticornConsent.prototype, "_isOpen");
|
|
1601
|
+
if (typeof customElements !== "undefined" && customElements.get(CONSENT_ELEMENT_TAG) === void 0) {
|
|
1602
|
+
customElements.define(CONSENT_ELEMENT_TAG, MulticornConsent);
|
|
1603
|
+
}
|
|
1603
1604
|
|
|
1604
1605
|
// src/badge/badge-styles.ts
|
|
1605
1606
|
var LIGHT_TEXT = "#0f172a";
|
|
@@ -1657,6 +1658,10 @@ function getBadgeStyleText() {
|
|
|
1657
1658
|
var VERIFY_BASE = "https://multicorn.ai/verify/";
|
|
1658
1659
|
var BADGE_ELEMENT_TAG = "multicorn-badge";
|
|
1659
1660
|
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";
|
|
1661
|
+
var SafeHTMLElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
|
|
1662
|
+
connectedCallback() {
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1660
1665
|
function parseOptionalCount(raw) {
|
|
1661
1666
|
if (raw == null || raw === "") {
|
|
1662
1667
|
return void 0;
|
|
@@ -1664,7 +1669,7 @@ function parseOptionalCount(raw) {
|
|
|
1664
1669
|
const n = Number(raw);
|
|
1665
1670
|
return Number.isNaN(n) ? void 0 : n;
|
|
1666
1671
|
}
|
|
1667
|
-
var MulticornBadge = class extends
|
|
1672
|
+
var MulticornBadge = class extends SafeHTMLElement {
|
|
1668
1673
|
#didInjectStyle = false;
|
|
1669
1674
|
ensureShadow() {
|
|
1670
1675
|
if (this.shadowRoot != null) {
|
|
@@ -1735,7 +1740,7 @@ var MulticornBadge = class extends HTMLElement {
|
|
|
1735
1740
|
root.appendChild(a);
|
|
1736
1741
|
}
|
|
1737
1742
|
};
|
|
1738
|
-
if (customElements.get(BADGE_ELEMENT_TAG) === void 0) {
|
|
1743
|
+
if (typeof customElements !== "undefined" && customElements.get(BADGE_ELEMENT_TAG) === void 0) {
|
|
1739
1744
|
customElements.define(BADGE_ELEMENT_TAG, MulticornBadge);
|
|
1740
1745
|
}
|
|
1741
1746
|
|
package/dist/multicorn-proxy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { mkdir, writeFile, readFile, copyFile, unlink } from 'fs/promises';
|
|
3
|
+
import { mkdir, writeFile, readFile, copyFile, chmod, unlink } from 'fs/promises';
|
|
4
4
|
import { join, dirname } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
@@ -473,19 +473,86 @@ async function installWindsurfNativeHooks() {
|
|
|
473
473
|
await mkdir(hooksDir, { recursive: true });
|
|
474
474
|
await writeFile(hooksPath, JSON.stringify(base, null, 2) + "\n", { encoding: "utf8" });
|
|
475
475
|
}
|
|
476
|
-
|
|
476
|
+
function getClineHooksInstallDir() {
|
|
477
|
+
return join(homedir(), ".multicorn", "cline-hooks");
|
|
478
|
+
}
|
|
479
|
+
function getClineGlobalHooksDir() {
|
|
480
|
+
return join(homedir(), "Documents", "Cline", "Hooks");
|
|
481
|
+
}
|
|
482
|
+
async function installClineNativeHooks() {
|
|
483
|
+
const root = multicornShieldPackageRoot();
|
|
484
|
+
const srcPre = join(root, "plugins", "cline", "hooks", "scripts", "pre-tool-use.cjs");
|
|
485
|
+
const srcPost = join(root, "plugins", "cline", "hooks", "scripts", "post-tool-use.cjs");
|
|
486
|
+
const srcShared = join(root, "plugins", "cline", "hooks", "scripts", "shared.cjs");
|
|
487
|
+
if (!existsSync(srcPre) || !existsSync(srcPost) || !existsSync(srcShared)) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Could not find Shield Cline hook scripts at ${srcPre}. If you use npm, install the latest multicorn-shield package.`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
const installDir = getClineHooksInstallDir();
|
|
493
|
+
await mkdir(installDir, { recursive: true });
|
|
494
|
+
const destPre = join(installDir, "pre-tool-use.cjs");
|
|
495
|
+
const destPost = join(installDir, "post-tool-use.cjs");
|
|
496
|
+
const destShared = join(installDir, "shared.cjs");
|
|
497
|
+
await copyFile(srcPre, destPre);
|
|
498
|
+
await copyFile(srcPost, destPost);
|
|
499
|
+
await copyFile(srcShared, destShared);
|
|
500
|
+
const hookScriptMode = 493;
|
|
501
|
+
await chmod(destPre, hookScriptMode);
|
|
502
|
+
await chmod(destPost, hookScriptMode);
|
|
503
|
+
await chmod(destShared, hookScriptMode);
|
|
504
|
+
const hooksDir = getClineGlobalHooksDir();
|
|
505
|
+
await mkdir(hooksDir, { recursive: true });
|
|
506
|
+
const preWrapper = join(hooksDir, "PreToolUse");
|
|
507
|
+
const postWrapper = join(hooksDir, "PostToolUse");
|
|
508
|
+
const preContent = `#!/usr/bin/env node
|
|
509
|
+
require(${JSON.stringify(destPre)});
|
|
510
|
+
`;
|
|
511
|
+
const postContent = `#!/usr/bin/env node
|
|
512
|
+
require(${JSON.stringify(destPost)});
|
|
513
|
+
`;
|
|
514
|
+
await writeFile(preWrapper, preContent, { encoding: "utf8", mode: 493 });
|
|
515
|
+
await writeFile(postWrapper, postContent, { encoding: "utf8", mode: 493 });
|
|
516
|
+
}
|
|
517
|
+
async function promptClineIntegrationMode(ask) {
|
|
518
|
+
process.stderr.write("\n" + style.bold("Cline integration") + "\n");
|
|
519
|
+
process.stderr.write(
|
|
520
|
+
" " + style.violet("1") + ". Native plugin (recommended) - Cline Hooks see every file, terminal, browser, and MCP action\n"
|
|
521
|
+
);
|
|
522
|
+
process.stderr.write(
|
|
523
|
+
" " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into Cline MCP settings)\n"
|
|
524
|
+
);
|
|
525
|
+
let choice = 0;
|
|
526
|
+
while (choice === 0) {
|
|
527
|
+
const input = await ask("Choose integration (1-2): ");
|
|
528
|
+
const num = parseInt(input.trim(), 10);
|
|
529
|
+
if (num === 1) choice = 1;
|
|
530
|
+
if (num === 2) choice = 2;
|
|
531
|
+
}
|
|
532
|
+
return choice === 1 ? "native" : "hosted";
|
|
533
|
+
}
|
|
534
|
+
var PLATFORM_LABELS = [
|
|
535
|
+
"OpenClaw",
|
|
536
|
+
"Claude Code",
|
|
537
|
+
"Cursor",
|
|
538
|
+
"Windsurf",
|
|
539
|
+
"Cline",
|
|
540
|
+
"Local MCP / Other"
|
|
541
|
+
];
|
|
477
542
|
var PLATFORM_BY_SELECTION = {
|
|
478
543
|
1: "openclaw",
|
|
479
544
|
2: "claude-code",
|
|
480
545
|
3: "cursor",
|
|
481
546
|
4: "windsurf",
|
|
482
|
-
5: "
|
|
547
|
+
5: "cline",
|
|
548
|
+
6: "other-mcp"
|
|
483
549
|
};
|
|
484
550
|
var DEFAULT_AGENT_NAMES = {
|
|
485
551
|
openclaw: "my-openclaw-agent",
|
|
486
552
|
"claude-code": "my-claude-code-agent",
|
|
487
553
|
cursor: "my-cursor-agent",
|
|
488
|
-
windsurf: "my-windsurf-agent"
|
|
554
|
+
windsurf: "my-windsurf-agent",
|
|
555
|
+
cline: "my-cline-agent"
|
|
489
556
|
};
|
|
490
557
|
async function promptPlatformSelection(ask) {
|
|
491
558
|
process.stderr.write(
|
|
@@ -505,13 +572,13 @@ async function promptPlatformSelection(ask) {
|
|
|
505
572
|
);
|
|
506
573
|
}
|
|
507
574
|
process.stderr.write(
|
|
508
|
-
style.dim(" Pick
|
|
575
|
+
style.dim(" Pick 6 if you want to wrap a local MCP server with multicorn-proxy --wrap.") + "\n"
|
|
509
576
|
);
|
|
510
577
|
let selection = 0;
|
|
511
578
|
while (selection === 0) {
|
|
512
|
-
const input = await ask("Select (1-
|
|
579
|
+
const input = await ask("Select (1-6): ");
|
|
513
580
|
const num = parseInt(input.trim(), 10);
|
|
514
|
-
if (num >= 1 && num <=
|
|
581
|
+
if (num >= 1 && num <= 6) {
|
|
515
582
|
selection = num;
|
|
516
583
|
}
|
|
517
584
|
}
|
|
@@ -632,7 +699,7 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
|
|
|
632
699
|
return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
|
|
633
700
|
}
|
|
634
701
|
function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
635
|
-
const usesInlineKey = platform === "cursor" || platform === "windsurf";
|
|
702
|
+
const usesInlineKey = platform === "cursor" || platform === "windsurf" || platform === "cline";
|
|
636
703
|
const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
|
|
637
704
|
const urlKey = platform === "windsurf" ? "serverUrl" : "url";
|
|
638
705
|
const mcpSnippet = JSON.stringify(
|
|
@@ -657,6 +724,23 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
657
724
|
process.stderr.write(
|
|
658
725
|
"\n" + style.dim("Add this to ~/.codeium/windsurf/mcp_config.json:") + "\n\n"
|
|
659
726
|
);
|
|
727
|
+
} else if (platform === "cline") {
|
|
728
|
+
process.stderr.write("\n" + style.dim("Add this to your Cline MCP settings file:") + "\n");
|
|
729
|
+
process.stderr.write(
|
|
730
|
+
style.dim(
|
|
731
|
+
" macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
|
|
732
|
+
) + "\n"
|
|
733
|
+
);
|
|
734
|
+
process.stderr.write(
|
|
735
|
+
style.dim(
|
|
736
|
+
" Windows: %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json"
|
|
737
|
+
) + "\n"
|
|
738
|
+
);
|
|
739
|
+
process.stderr.write(
|
|
740
|
+
style.dim(
|
|
741
|
+
" Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
|
|
742
|
+
) + "\n\n"
|
|
743
|
+
);
|
|
660
744
|
} else {
|
|
661
745
|
process.stderr.write("\n" + style.dim("Add this to ~/.cursor/mcp.json:") + "\n\n");
|
|
662
746
|
}
|
|
@@ -680,6 +764,13 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
680
764
|
) + "\n"
|
|
681
765
|
);
|
|
682
766
|
}
|
|
767
|
+
if (platform === "cline") {
|
|
768
|
+
process.stderr.write(
|
|
769
|
+
style.dim(
|
|
770
|
+
"After pasting, restart Cline or reload the VS Code window. Cline will discover the Shield tools automatically."
|
|
771
|
+
) + "\n"
|
|
772
|
+
);
|
|
773
|
+
}
|
|
683
774
|
if (platform === "windsurf") {
|
|
684
775
|
process.stderr.write(style.dim("Then restart Windsurf (Cmd/Ctrl+Q, then reopen).") + "\n");
|
|
685
776
|
process.stderr.write(
|
|
@@ -777,7 +868,7 @@ async function runInit(explicitBaseUrl) {
|
|
|
777
868
|
const selection = await promptPlatformSelection(ask);
|
|
778
869
|
const selectedPlatform = PLATFORM_BY_SELECTION[selection] ?? "cursor";
|
|
779
870
|
const selectedLabel = PLATFORM_LABELS[selection - 1] ?? "Cursor";
|
|
780
|
-
if (selection ===
|
|
871
|
+
if (selection === 6) {
|
|
781
872
|
const raw = existing !== null ? { ...existing } : {};
|
|
782
873
|
raw["apiKey"] = apiKey;
|
|
783
874
|
raw["baseUrl"] = resolvedBaseUrl;
|
|
@@ -1001,6 +1092,71 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1001
1092
|
setupSucceeded = true;
|
|
1002
1093
|
}
|
|
1003
1094
|
}
|
|
1095
|
+
} else if (selection === 5) {
|
|
1096
|
+
const clineMode = await promptClineIntegrationMode(ask);
|
|
1097
|
+
if (clineMode === "native") {
|
|
1098
|
+
try {
|
|
1099
|
+
await installClineNativeHooks();
|
|
1100
|
+
process.stderr.write("\n" + style.bold("Shield Cline hooks installed") + "\n\n");
|
|
1101
|
+
process.stderr.write(
|
|
1102
|
+
style.dim(
|
|
1103
|
+
"The Shield hook runs with your user permissions. It intercepts Cline tool calls to check permissions and log activity. Review the scripts under "
|
|
1104
|
+
) + style.cyan("~/.multicorn/cline-hooks") + style.dim(" if that is a concern.") + "\n"
|
|
1105
|
+
);
|
|
1106
|
+
configuredAgents.push({
|
|
1107
|
+
selection,
|
|
1108
|
+
platform: selectedPlatform,
|
|
1109
|
+
platformLabel: selectedLabel,
|
|
1110
|
+
agentName,
|
|
1111
|
+
clineIntegration: "native"
|
|
1112
|
+
});
|
|
1113
|
+
setupSucceeded = true;
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1116
|
+
process.stderr.write(style.red("\u2717 ") + detail + "\n");
|
|
1117
|
+
}
|
|
1118
|
+
} else {
|
|
1119
|
+
const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
|
|
1120
|
+
let proxyUrl = "";
|
|
1121
|
+
let created = false;
|
|
1122
|
+
while (!created) {
|
|
1123
|
+
const spinner = withSpinner("Creating proxy config...");
|
|
1124
|
+
try {
|
|
1125
|
+
proxyUrl = await createProxyConfig(
|
|
1126
|
+
resolvedBaseUrl,
|
|
1127
|
+
apiKey,
|
|
1128
|
+
agentName,
|
|
1129
|
+
targetUrl,
|
|
1130
|
+
shortName,
|
|
1131
|
+
selectedPlatform
|
|
1132
|
+
);
|
|
1133
|
+
spinner.stop(true, "Proxy config created!");
|
|
1134
|
+
created = true;
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1137
|
+
spinner.stop(false, detail);
|
|
1138
|
+
const retry = await ask("Try again? (Y/n) ");
|
|
1139
|
+
if (retry.trim().toLowerCase() === "n") {
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (created && proxyUrl.length > 0) {
|
|
1145
|
+
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
1146
|
+
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
1147
|
+
printPlatformSnippet(selectedPlatform, proxyUrl, shortName, apiKey);
|
|
1148
|
+
configuredAgents.push({
|
|
1149
|
+
selection,
|
|
1150
|
+
platform: selectedPlatform,
|
|
1151
|
+
platformLabel: selectedLabel,
|
|
1152
|
+
agentName,
|
|
1153
|
+
shortName,
|
|
1154
|
+
proxyUrl,
|
|
1155
|
+
clineIntegration: "hosted"
|
|
1156
|
+
});
|
|
1157
|
+
setupSucceeded = true;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1004
1160
|
} else {
|
|
1005
1161
|
const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
|
|
1006
1162
|
let proxyUrl = "";
|
|
@@ -1119,6 +1275,22 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1119
1275
|
"\n" + style.bold("To complete your Windsurf hosted-proxy setup:") + "\n 1. If you don't have Windsurf yet, download it from " + style.cyan("https://windsurf.com/download") + "\n 2. Open " + style.cyan("~/.codeium/windsurf/mcp_config.json") + " and paste the config snippet shown above\n 3. Restart Windsurf (or launch it for the first time) to load the new MCP server\n"
|
|
1120
1276
|
);
|
|
1121
1277
|
}
|
|
1278
|
+
const clineNativeConfigured = configuredAgents.some(
|
|
1279
|
+
(a) => a.platform === "cline" && a.clineIntegration === "native"
|
|
1280
|
+
);
|
|
1281
|
+
const clineHostedConfigured = configuredAgents.some(
|
|
1282
|
+
(a) => a.platform === "cline" && a.clineIntegration === "hosted"
|
|
1283
|
+
);
|
|
1284
|
+
if (clineNativeConfigured) {
|
|
1285
|
+
blocks.push(
|
|
1286
|
+
"\n" + style.bold("To complete native Cline (Shield) setup:") + "\n 1. Enable Hooks in Cline: open VS Code, click the Cline sidebar icon, click the gear icon,\n scroll down to the Advanced section, and toggle Hooks on.\n 2. Reload the VS Code window (Cmd+Shift+P > Reload Window)\n 3. Trigger any tool call to verify Shield is intercepting\n"
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
if (clineHostedConfigured) {
|
|
1290
|
+
blocks.push(
|
|
1291
|
+
"\n" + style.bold("To complete your Cline hosted-proxy setup:") + "\n 1. If you don't have Cline yet, install it from the VS Code marketplace\n 2. Open your Cline MCP settings file and paste the config snippet shown above\n 3. Restart Cline or reload the VS Code window\n"
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1122
1294
|
if (blocks.length > 0) {
|
|
1123
1295
|
process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
|
|
1124
1296
|
process.stderr.write(blocks.join("") + "\n");
|
|
@@ -377,7 +377,6 @@ ${url}
|
|
|
377
377
|
}
|
|
378
378
|
async function waitForConsent(agentId, agentName, apiKey, baseUrl, scope, logger) {
|
|
379
379
|
const dashboardUrl = deriveDashboardUrl(baseUrl);
|
|
380
|
-
console.error("[SHIELD] buildConsentUrl baseUrl:", baseUrl);
|
|
381
380
|
const consentUrl = buildConsentUrl(agentName, dashboardUrl, scope);
|
|
382
381
|
process.stderr.write(
|
|
383
382
|
`[multicorn-shield] Opening consent page...
|
|
@@ -315,14 +315,6 @@ async function fetchGrantedScopes(agentId, apiKey, baseUrl, logger) {
|
|
|
315
315
|
}
|
|
316
316
|
async function checkActionPermission(payload, apiKey, baseUrl, logger) {
|
|
317
317
|
try {
|
|
318
|
-
const requestBody = {
|
|
319
|
-
agent: payload.agent,
|
|
320
|
-
service: payload.service,
|
|
321
|
-
actionType: payload.actionType,
|
|
322
|
-
status: payload.status,
|
|
323
|
-
metadata: payload.metadata
|
|
324
|
-
};
|
|
325
|
-
console.error("[SHIELD-CLIENT] POST /api/v1/actions request: " + JSON.stringify(requestBody));
|
|
326
318
|
const response = await fetch(`${baseUrl}/api/v1/actions`, {
|
|
327
319
|
method: "POST",
|
|
328
320
|
headers: {
|
|
@@ -333,22 +325,18 @@ async function checkActionPermission(payload, apiKey, baseUrl, logger) {
|
|
|
333
325
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
334
326
|
});
|
|
335
327
|
if (response.status === 201) {
|
|
336
|
-
console.error(
|
|
337
|
-
"[SHIELD-CLIENT] response status=201, returning approved (body not read - backend may have failed approval creation)"
|
|
338
|
-
);
|
|
328
|
+
console.error("[SHIELD-CLIENT] POST /api/v1/actions: 201 approved");
|
|
339
329
|
return { status: "approved" };
|
|
340
330
|
}
|
|
341
331
|
if (response.status === 202) {
|
|
342
332
|
const body = await response.json();
|
|
343
333
|
const data = isApiSuccess(body) ? body.data : null;
|
|
344
|
-
console.error("[SHIELD-CLIENT]
|
|
334
|
+
console.error("[SHIELD-CLIENT] POST /api/v1/actions: 202 pending");
|
|
345
335
|
if (!isApiSuccess(body) || data === null) {
|
|
346
336
|
return { status: "blocked" };
|
|
347
337
|
}
|
|
348
338
|
const approvalId = typeof data["approval_id"] === "string" ? data["approval_id"] : void 0;
|
|
349
|
-
console.error(
|
|
350
|
-
"[SHIELD-CLIENT] extracted: status=" + String(data["status"]) + " approval_id=" + (approvalId ?? "undefined")
|
|
351
|
-
);
|
|
339
|
+
console.error("[SHIELD-CLIENT] extracted: approval_id=" + (approvalId ?? "undefined"));
|
|
352
340
|
if (approvalId === void 0) {
|
|
353
341
|
return { status: "blocked" };
|
|
354
342
|
}
|
|
@@ -439,7 +427,6 @@ ${url}
|
|
|
439
427
|
}
|
|
440
428
|
async function waitForConsent(agentId, agentName, apiKey, baseUrl, scope, logger) {
|
|
441
429
|
const dashboardUrl = deriveDashboardUrl(baseUrl);
|
|
442
|
-
console.error("[SHIELD] buildConsentUrl baseUrl:", baseUrl);
|
|
443
430
|
const consentUrl = buildConsentUrl(agentName, dashboardUrl, scope);
|
|
444
431
|
process.stderr.write(
|
|
445
432
|
`[multicorn-shield] Opening consent page...
|
|
@@ -509,7 +496,14 @@ function readConfig() {
|
|
|
509
496
|
const resolvedBaseUrl = asString(cachedMulticornConfig?.baseUrl) ?? asString(process.env["MULTICORN_BASE_URL"]) ?? "https://api.multicorn.ai";
|
|
510
497
|
const agentName = asString(pc["agentName"]) ?? process.env["MULTICORN_AGENT_NAME"] ?? agentNameFromOpenclawPlatform(cachedMulticornConfig) ?? asString(cachedMulticornConfig?.agentName) ?? null;
|
|
511
498
|
const failMode = "closed";
|
|
512
|
-
|
|
499
|
+
let apiKey = resolvedApiKey;
|
|
500
|
+
if (apiKey.length > 0 && (!apiKey.startsWith("mcs_") || apiKey.length < 16)) {
|
|
501
|
+
pluginLogger?.error(
|
|
502
|
+
"Invalid API key format. Key must start with mcs_ and be at least 16 characters."
|
|
503
|
+
);
|
|
504
|
+
apiKey = "";
|
|
505
|
+
}
|
|
506
|
+
return { apiKey, baseUrl: resolvedBaseUrl, agentName, failMode };
|
|
513
507
|
}
|
|
514
508
|
function asString(value) {
|
|
515
509
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|