chainlesschain 0.162.197 → 0.162.198
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-D3Gu1a5d.js → AIOps-CSN-D2xb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DrwAoKaz.js → ActionButton-CBSMlGzv.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C7Q-gd_3.js → Analytics-3yr3Nt2t.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DgBtae-n.js → AppLayout-1b-o3lMH.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-pQ6LcA-W.js → Artifacts-Dr8rJi17.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DK61tocB.js → Audit-2V689HC2.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOKgDFRh.js → BackgroundAgents-DIxkNrD_.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Bo9G4vTP.js → Backup-Pg6BcwrE.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Do_dE9_v.js → BaseInput-CLUpqyfH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-COVvZzOL.js → Chat-qVS8rD4G.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-ti49X12Q.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Z9MJMolt.js → Checkbox-Bs9AaEay.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BVzWKVbf.js → Codegen-Dvc1mq8q.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C8MnYcbT.js → Col-C4_biO7A.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DQxqw15l.js → Community-al25XQUV.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BAUmrh3y.js → Compact-D-ZDB8M1.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-jPAkQ3zK.js → Compliance-6lIfDz1W.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BBQyxbh8.js → Cowork-DdI48XMJ.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-W5koq8Z5.js → Cron-DF5HZd2g.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BQhnu5hS.js → Crosschain-DuxeSvt3.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DD7PlI18.js → DID-B9yHkUTd.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-w_ILuwKI.js → Dashboard-Z5EuI3IM.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-qiDUAyAo.js → Dropdown-vuRfO3dY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-PDeBcI5H.js → EmailListRenderer-QFLCc1uL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-SuArscRM.js → FamilyGuardDashboard-BZ7EXkFJ.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BCovZaUn.js → Federation-DycIEg2s.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DjlFdVFk.js → FormItemContext-Ba0H_7yG.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DoEZ7fcS.js → GenericCardRenderer--JgSTK-C.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CV0-6GPI.js → Git-DEvTbldE.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B_SFN50w.js → Governance-BJIljy18.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-heI0Eo6k.js → Inference-CVyW3G3q.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Cz5INS5p.js → KnowledgeGraph-v8-borge.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BSFxiHkg.js → Logs-BCdS7gGo.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CU-JB2Ns.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-0TWTpE7k.js → Marketplace-AN6tGA2B.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CV4-wBLm.js → McpTools-DGViXASN.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-x5bH5rw5.js → Memory-Bpvmx9X4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DaBmUu9G.js → MobileBridge-CTP-DOTC.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-C8qfktxn.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-B_zKEBif.js → Mtc-ndYr0M1s.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BCfKD2yg.js → MtcAudit-Ci0-YK7W.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DgYfj6c_.js → Multisig-BehSTFW7.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Dz09lFUm.js → NLProgramming-B91_YBLv.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cuj6ugBE.js → Notes-BQlJ0-gq.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DK2WXjhg.js → NotificationSettings-CDgYMjYK.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-D1IAOQoo.js → OrderTableRenderer-BJkSlKeH.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-9fqnPvTw.js → Organization-B-b3cWCp.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-n5xHkhow.js → Overflow-CILUL4nL.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-B5AUWwH-.js → P2P-BTCFBkF0.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CoUDV6dy.js → PdhVaultBrowser-BAR6AUx5.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-DPRmQf_H.js → Permissions-RRf6HbWJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B37bsbEN.js → PersonalDataHub-BGTMiICa.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CCAuPqu9.js → Pipeline-945qY9YF.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B81msZdv.js → Privacy-DPZwI6lJ.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-B-Py2NbF.js → ProjectInit-BPH2NRKc.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CYsneiRB.js → ProjectSettings-BZCRNCvb.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Y975Mh6R.js → Projects-BTI__tBt.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-uO-10aP1.js → Providers-DDCf_nIw.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Bj_6gJ0l.js → QrScannerModal-N6rbqt1r.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BgpBCMPE.js → QuickAsk-DrwjPS7M.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DhUW3YUA.js → Recommend-Cog9Wd99.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DdeyfbkA.js → RemoteSession-DC9pynjM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-fxW421xd.js → Reputation-D4th4gKS.js} +1 -1
- package/src/assets/web-panel/assets/{Row-De73w1og.js → Row-ogiWR6v9.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-3US_Q_lj.js → RssFeed-uYlk-2Oj.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DbZ_DU74.js → Search-DYzzwvtM.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DoIU1vMF.js → Security-Da7YOyX6.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DB_RGqpc.js → Services-CLI1VM1X.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DnzLaayC.js → Skeleton-DK77dzDC.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-VzVXHENF.js → Skills-C_z7Lmhl.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DMfvjCAI.js → Sla-D6413Q8L.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DQQOYWbF.js → SpeechSettings-C07ydbj2.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Bmx3FG1P.js → SyncSettings-DGOm2W22.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DOQyfb-2.js → Tasks-Cr_PVyLM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D6hTRT7c.js → Templates-BhL2mZ0t.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CTPQZAzs.js → Tenant-CPk5oyUr.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B7vII3oH.js → Terminal-ClhqUsqV.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BkiFJeXV.js → TimelineRenderer-BCC7MV5a.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BPPdQMNo.js → Tokens-DD1Jt4oO.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BxG8JAEI.js → Trigger-BCydFvN2.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DHD7nMpM.js → Trust-DDHT1noa.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B4BJpU47.js → UkeySign-CWPZC7oI.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CRiGpFx2.js → VideoEditing-9Ea0nJR0.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BSDSNiem.js → Wallet-BjxhLxTf.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DLTUh5Zv.js → WebAuthn-C6uCye67.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-tW2MwjhD.js → WorkflowEditor-d2g4reYo.js} +1 -1
- package/src/assets/web-panel/assets/{chat-SgsLQ3FC.js → chat-frTc6ScX.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BHjjLIKO.js → colors-B5K6lG3v.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-VceErdKq.js → compact-item-Cdn46r8D.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BZ332MYl.js → createContext-DU1jqVQi.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CBTjJOjn.js → hasIn-DdNH-aW_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CawpdGTW.js → index-32ajXmTb.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOtvPdHt.js → index-7ehytj1K.js} +1 -1
- package/src/assets/web-panel/assets/{index-vIXOpcjD.js → index-BC4eGkar.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr4rjlW5.js → index-BVLL-mq_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_SePDWj.js → index-BXKhhx9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BETEjuNr.js → index-BcGwGIaR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BY1wexYG.js → index-Be4OtL-N.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRWhLbWF.js → index-BoFak7le.js} +1 -1
- package/src/assets/web-panel/assets/{index-29wVzi-k.js → index-BwlIxAmX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeiqkxJG.js → index-CGiwfwB9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQSmZGgs.js → index-CHmkkdfT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhXZEUAc.js → index-CQorP1Ng.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_B9rFQE.js → index-CUoNAmE8.js} +1 -1
- package/src/assets/web-panel/assets/{index-iYQNU9za.js → index-Cj6P9Nst.js} +1 -1
- package/src/assets/web-panel/assets/{index-DePOeMDM.js → index-CtRPi4YC.js} +1 -1
- package/src/assets/web-panel/assets/index-CzrCHrFy.js +1 -0
- package/src/assets/web-panel/assets/{index-DdefYBSw.js → index-D1qIC4GR.js} +1 -1
- package/src/assets/web-panel/assets/{index-By5jBtkV.js → index-D23xGYjM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMVvxIS8.js → index-D4l82Me1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLc19alH.js → index-D4miNkWv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2u26AS3.js → index-DIVyTmZ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaCCy6lq.js → index-DM5FmPZU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUkVN794.js → index-DRzBBdQ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPXFLYdW.js → index-Dca_i7H8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpumS98o.js → index-DftZuyA7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrUgDQkf.js → index-DtBCCkC4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtjLcCNU.js → index-HjCvXHf5.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaIS_TNX.js → index-LaivuXyF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC1xuQgX.js → index-MM4Zz2OE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcUenmCX.js → index-NmEXVjzt.js} +1 -1
- package/src/assets/web-panel/assets/index-OJsGskZw.js +1 -0
- package/src/assets/web-panel/assets/{index-_PHrIO4O.js → index-PjAS0sIZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgPvuIn2.js → index-VDM2KKo9.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5KAGxNo.js → index-cYaWV4Mq.js} +1 -1
- package/src/assets/web-panel/assets/{index-B40dQn0G.js → index-gg3QPC3i.js} +3 -3
- package/src/assets/web-panel/assets/{index-BTlc-Bve.js → index-kVO_MDm-.js} +1 -1
- package/src/assets/web-panel/assets/{index-FJ3zC9LK.js → index-mgU3Q-lN.js} +1 -1
- package/src/assets/web-panel/assets/{index-1n7xjKDU.js → index-t_OBzIP4.js} +1 -1
- package/src/assets/web-panel/assets/{index-fU7wNHDB.js → index-yZRuZpwK.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D1oZNz0D.js → initDefaultProps-DwLTRNdo.js} +1 -1
- package/src/assets/web-panel/assets/{motion-C8k6doMO.js → motion-BchZDXnh.js} +1 -1
- package/src/assets/web-panel/assets/{move-DJ4qSbYt.js → move-e8M-zZFl.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CdpJD85j.js → mtc-parser-Ci40fQb3.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B0V4slKA.js → omit-C7110_f2.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Di5NiW1F.js → pickAttrs-skteMgnM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B5OMHgEM.js → placementArrow-MGZ-lh2B.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C6U9IWql.js → responsiveObserve-N4Sd7uy-.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BMyTH3rN.js → slide-C7HDGRaa.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C7d6BG7Z.js → statusUtils-BhRexlgy.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CR50QnQy.js → styleChecker-ChEoQkN2.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DBEeoMb8.js → useFlexGapSupport-CDbG1s2Y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-3r099cjv.js → useFs-CUIx0tcJ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C5IYNdTK.js → usePersonalDataHub-bkO7L7ec.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DmEOLRHf.js → vnode-B_ZvABIG.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DnpUiVCB.js → zoom-D5shjpS8.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +4 -4
- package/src/commands/agent.js +3 -3
- package/src/commands/background-session.js +145 -0
- package/src/commands/mcp.js +125 -7
- package/src/commands/serve.js +1 -1
- package/src/commands/session-show.js +131 -0
- package/src/commands/session.js +50 -106
- package/src/commands/skill.js +87 -50
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/ws-agent-handler.js +8 -1
- package/src/harness/jsonl-session-store.js +428 -25
- package/src/harness/mcp-client.js +2652 -404
- package/src/harness/session-list-index.js +65 -7
- package/src/lazy-dispatch.js +76 -8
- package/src/lib/abort-utils.js +68 -0
- package/src/lib/agent-core.js +1 -59
- package/src/lib/chat-core.js +24 -6
- package/src/lib/cowork-mcp-tools.js +47 -2
- package/src/lib/cowork-task-runner.js +11 -3
- package/src/lib/doctor-checkup.js +9 -7
- package/src/lib/learning/skill-improver.js +21 -2
- package/src/lib/mcp-call-ledger-store.js +116 -3
- package/src/lib/mcp-call-ledger.js +59 -14
- package/src/lib/mcp-client.js +1 -0
- package/src/lib/mcp-stdio-executable-identity.js +598 -0
- package/src/lib/mcp-stdio-execution-authority.js +363 -0
- package/src/lib/mcp-tool-metadata.js +436 -0
- package/src/lib/mcp-tool-result.js +367 -0
- package/src/lib/observability/index.js +71 -0
- package/src/lib/pr-link-ledger.js +8 -27
- package/src/lib/pr-link-store.js +26 -0
- package/src/lib/process-execution-broker/index.js +14 -0
- package/src/lib/process-tree-termination.js +253 -0
- package/src/lib/session-host-snapshot.js +9 -5
- package/src/lib/session-projection.js +5 -1
- package/src/lib/session-tail.js +31 -1
- package/src/lib/skill-budget.js +222 -0
- package/src/lib/skill-execution-authority.js +381 -0
- package/src/lib/skill-execution-identity.js +39 -2
- package/src/lib/skill-loader.js +322 -86
- package/src/lib/skill-mcp.js +43 -1
- package/src/repl/agent-repl.js +174 -38
- package/src/repl/bg-dashboard.js +81 -28
- package/src/repl/chat-repl.js +77 -12
- package/src/runtime/agent-core.js +340 -108
- package/src/runtime/agent-runtime.js +26 -3
- package/src/runtime/headless-runner.js +150 -23
- package/src/runtime/headless-stream.js +309 -59
- package/src/runtime/mcp-config.js +157 -10
- package/src/runtime/mcp-tool-search.js +0 -0
- package/src/runtime/output-backpressure.js +366 -0
- package/src/runtime/pipe-safety.js +56 -21
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BUUyJ5QO.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-1mNozZ8v.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-DqnoFPgR.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DosMaL-k.js +0 -1
- package/src/assets/web-panel/assets/index-0O5ePnUq.js +0 -1
- package/src/assets/web-panel/assets/index-ChAzPigF.js +0 -1
- package/src/lib/skill-process-broker.js +0 -85
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable byte identity for MCP stdio launchers and interpreter entrypoints.
|
|
3
|
+
*
|
|
4
|
+
* A trusted config names code; it does not prove which bytes PATH or an
|
|
5
|
+
* interpreter will open. This module resolves those paths, attests regular
|
|
6
|
+
* files through open descriptors, persists the first explicitly trusted
|
|
7
|
+
* identity, and issues a one-shot broker token for the final pre-spawn check.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import crypto from "node:crypto";
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { getStatePath } from "./paths.js";
|
|
14
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
15
|
+
import { mutateSecurityStore } from "./durable-security-store.js";
|
|
16
|
+
import { resolveMcpStdioExecutionApproval } from "./mcp-stdio-execution-authority.js";
|
|
17
|
+
|
|
18
|
+
export const MCP_STDIO_EXECUTABLE_TRUST_REQUIRED_CODE =
|
|
19
|
+
"CC_MCP_STDIO_EXECUTABLE_TRUST_REQUIRED";
|
|
20
|
+
export const MCP_STDIO_EXECUTABLE_CHANGED_CODE =
|
|
21
|
+
"CC_MCP_STDIO_EXECUTABLE_CHANGED";
|
|
22
|
+
export const MCP_STDIO_EXECUTABLE_UNATTESTED_CODE =
|
|
23
|
+
"CC_MCP_STDIO_EXECUTABLE_UNATTESTED";
|
|
24
|
+
export const MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE =
|
|
25
|
+
"CC_MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED";
|
|
26
|
+
export const MCP_STDIO_EXECUTABLE_AUTHORITY_REPLAYED_CODE =
|
|
27
|
+
"CC_MCP_STDIO_EXECUTABLE_AUTHORITY_REPLAYED";
|
|
28
|
+
|
|
29
|
+
const STORE_LABEL = "MCP stdio executable identity";
|
|
30
|
+
const MAX_EXECUTABLE_BYTES = 256 * 1024 * 1024;
|
|
31
|
+
const MAX_ENTRYPOINT_BYTES = 64 * 1024 * 1024;
|
|
32
|
+
const HASH_CHUNK_BYTES = 1024 * 1024;
|
|
33
|
+
const issuedLaunchAuthorities = new WeakMap();
|
|
34
|
+
|
|
35
|
+
const DYNAMIC_LAUNCHERS = new Set([
|
|
36
|
+
"bunx",
|
|
37
|
+
"corepack",
|
|
38
|
+
"npm",
|
|
39
|
+
"npx",
|
|
40
|
+
"pipx",
|
|
41
|
+
"pnpm",
|
|
42
|
+
"uvx",
|
|
43
|
+
"yarn",
|
|
44
|
+
"yarnpkg",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export const _deps = {
|
|
48
|
+
fs,
|
|
49
|
+
withFileLock,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function identityError(code, message, details = {}) {
|
|
53
|
+
const error = new Error(message);
|
|
54
|
+
error.name = "McpStdioExecutableIdentityError";
|
|
55
|
+
error.code = code;
|
|
56
|
+
Object.assign(error, details);
|
|
57
|
+
return error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sha256(value) {
|
|
61
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function storePath(options = {}) {
|
|
65
|
+
return (
|
|
66
|
+
options.storePath ||
|
|
67
|
+
process.env.CC_MCP_EXECUTABLE_TRUST_STORE ||
|
|
68
|
+
path.join(getStatePath(), "mcp-stdio-executable-identities.json")
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function retrustRequested(env = process.env) {
|
|
73
|
+
const value = String(env.CC_MCP_EXECUTABLE_TRUST || "").toLowerCase();
|
|
74
|
+
return value === "1" || value === "true";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function executableBasename(value) {
|
|
78
|
+
return path
|
|
79
|
+
.basename(String(value || ""))
|
|
80
|
+
.replace(/\.(?:exe|com|cmd|bat)$/i, "")
|
|
81
|
+
.toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function candidateFiles(command, cwd, env, platform) {
|
|
85
|
+
const value = String(command || "");
|
|
86
|
+
if (!value || value.includes("\0")) return [];
|
|
87
|
+
if (path.isAbsolute(value)) return [path.resolve(value)];
|
|
88
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
89
|
+
return [path.resolve(cwd, value)];
|
|
90
|
+
}
|
|
91
|
+
const searchPath = String(env.PATH || env.Path || env.path || "");
|
|
92
|
+
const directories = searchPath.split(path.delimiter).filter(Boolean);
|
|
93
|
+
if (platform !== "win32") {
|
|
94
|
+
return directories.map((directory) => path.resolve(directory, value));
|
|
95
|
+
}
|
|
96
|
+
const extensions = String(env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
|
|
97
|
+
.split(";")
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
const hasExtension = path.extname(value) !== "";
|
|
100
|
+
return directories.flatMap((directory) =>
|
|
101
|
+
(hasExtension ? [""] : extensions).map((extension) =>
|
|
102
|
+
path.resolve(directory, `${value}${extension}`),
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveExecutable(command, cwd, env, platform) {
|
|
108
|
+
for (const candidate of candidateFiles(command, cwd, env, platform)) {
|
|
109
|
+
try {
|
|
110
|
+
const candidateStat = _deps.fs.statSync(candidate);
|
|
111
|
+
if (!candidateStat.isFile()) continue;
|
|
112
|
+
if (platform !== "win32") {
|
|
113
|
+
_deps.fs.accessSync(candidate, _deps.fs.constants.X_OK);
|
|
114
|
+
}
|
|
115
|
+
const realPath =
|
|
116
|
+
_deps.fs.realpathSync.native?.(candidate) ||
|
|
117
|
+
_deps.fs.realpathSync(candidate);
|
|
118
|
+
if (
|
|
119
|
+
platform === "win32" &&
|
|
120
|
+
![".exe", ".com"].includes(path.extname(realPath).toLowerCase())
|
|
121
|
+
) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
return realPath;
|
|
125
|
+
} catch {
|
|
126
|
+
// Keep searching PATH/PATHEXT.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
throw identityError(
|
|
130
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
131
|
+
`MCP stdio command could not be resolved to an executable file: ${String(command)}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function statIdentity(stat) {
|
|
136
|
+
const time = (ns, ms) =>
|
|
137
|
+
stat[ns] !== undefined
|
|
138
|
+
? String(stat[ns])
|
|
139
|
+
: String(Math.trunc(Number(stat[ms] || 0) * 1_000_000));
|
|
140
|
+
return {
|
|
141
|
+
dev: String(stat.dev),
|
|
142
|
+
ino: String(stat.ino),
|
|
143
|
+
bytes: Number(stat.size),
|
|
144
|
+
mode: Number(stat.mode),
|
|
145
|
+
nlink: Number(stat.nlink),
|
|
146
|
+
ctimeNs: time("ctimeNs", "ctimeMs"),
|
|
147
|
+
mtimeNs: time("mtimeNs", "mtimeMs"),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function sameOpenIdentity(left, right) {
|
|
152
|
+
return (
|
|
153
|
+
left.dev === right.dev &&
|
|
154
|
+
left.ino === right.ino &&
|
|
155
|
+
left.bytes === right.bytes &&
|
|
156
|
+
left.mode === right.mode &&
|
|
157
|
+
left.nlink === right.nlink &&
|
|
158
|
+
left.ctimeNs === right.ctimeNs &&
|
|
159
|
+
left.mtimeNs === right.mtimeNs
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function hashOpenFile(fd, bytes) {
|
|
164
|
+
const digest = crypto.createHash("sha256");
|
|
165
|
+
const chunk = Buffer.allocUnsafe(
|
|
166
|
+
Math.max(1, Math.min(bytes, HASH_CHUNK_BYTES)),
|
|
167
|
+
);
|
|
168
|
+
let offset = 0;
|
|
169
|
+
while (offset < bytes) {
|
|
170
|
+
const read = _deps.fs.readSync(
|
|
171
|
+
fd,
|
|
172
|
+
chunk,
|
|
173
|
+
0,
|
|
174
|
+
Math.min(chunk.length, bytes - offset),
|
|
175
|
+
offset,
|
|
176
|
+
);
|
|
177
|
+
if (read <= 0) throw new Error("file ended before its attested size");
|
|
178
|
+
digest.update(chunk.subarray(0, read));
|
|
179
|
+
offset += read;
|
|
180
|
+
}
|
|
181
|
+
return digest.digest("hex");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function attestFile(file, maxBytes, role, platform, observation = null) {
|
|
185
|
+
let fd;
|
|
186
|
+
try {
|
|
187
|
+
const realPath =
|
|
188
|
+
_deps.fs.realpathSync.native?.(path.resolve(file)) ||
|
|
189
|
+
_deps.fs.realpathSync(path.resolve(file));
|
|
190
|
+
const lst = _deps.fs.lstatSync(realPath);
|
|
191
|
+
if (lst.isSymbolicLink() || !lst.isFile()) {
|
|
192
|
+
throw new Error("path is not a regular non-symlink file");
|
|
193
|
+
}
|
|
194
|
+
fd = _deps.fs.openSync(
|
|
195
|
+
realPath,
|
|
196
|
+
Number(_deps.fs.constants.O_RDONLY) |
|
|
197
|
+
Number(_deps.fs.constants.O_NOFOLLOW || 0) |
|
|
198
|
+
Number(_deps.fs.constants.O_NONBLOCK || 0),
|
|
199
|
+
);
|
|
200
|
+
const before = _deps.fs.fstatSync(fd, { bigint: true });
|
|
201
|
+
if (
|
|
202
|
+
!before.isFile() ||
|
|
203
|
+
before.size < 0n ||
|
|
204
|
+
before.size > BigInt(maxBytes)
|
|
205
|
+
) {
|
|
206
|
+
throw new Error("file exceeds the attestation size limit");
|
|
207
|
+
}
|
|
208
|
+
const beforeIdentity = statIdentity(before);
|
|
209
|
+
if (observation) {
|
|
210
|
+
const header = Buffer.allocUnsafe(
|
|
211
|
+
Math.max(1, Math.min(Number(before.size), 4096)),
|
|
212
|
+
);
|
|
213
|
+
const headerBytes =
|
|
214
|
+
before.size > 0n
|
|
215
|
+
? _deps.fs.readSync(fd, header, 0, header.length, 0)
|
|
216
|
+
: 0;
|
|
217
|
+
observation.hasShebang =
|
|
218
|
+
headerBytes >= 2 && header[0] === 0x23 && header[1] === 0x21;
|
|
219
|
+
}
|
|
220
|
+
const digest = hashOpenFile(fd, Number(before.size));
|
|
221
|
+
const afterIdentity = statIdentity(
|
|
222
|
+
_deps.fs.fstatSync(fd, { bigint: true }),
|
|
223
|
+
);
|
|
224
|
+
if (!sameOpenIdentity(beforeIdentity, afterIdentity)) {
|
|
225
|
+
throw new Error("file changed while it was being hashed");
|
|
226
|
+
}
|
|
227
|
+
const pathIdentity = statIdentity(
|
|
228
|
+
_deps.fs.statSync(realPath, { bigint: true }),
|
|
229
|
+
);
|
|
230
|
+
if (
|
|
231
|
+
platform !== "win32" &&
|
|
232
|
+
(pathIdentity.dev !== afterIdentity.dev ||
|
|
233
|
+
pathIdentity.ino !== afterIdentity.ino)
|
|
234
|
+
) {
|
|
235
|
+
throw new Error("path no longer names the opened file");
|
|
236
|
+
}
|
|
237
|
+
return Object.freeze({
|
|
238
|
+
role,
|
|
239
|
+
realPath,
|
|
240
|
+
sha256: digest,
|
|
241
|
+
...afterIdentity,
|
|
242
|
+
});
|
|
243
|
+
} catch (cause) {
|
|
244
|
+
throw identityError(
|
|
245
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
246
|
+
`MCP stdio ${role} identity could not be attested: ${cause.message}`,
|
|
247
|
+
{ cause },
|
|
248
|
+
);
|
|
249
|
+
} finally {
|
|
250
|
+
if (fd !== undefined) {
|
|
251
|
+
try {
|
|
252
|
+
_deps.fs.closeSync(fd);
|
|
253
|
+
} catch {
|
|
254
|
+
// Preserve the attestation result.
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function localEntrypoint(value, cwd, label) {
|
|
261
|
+
if (typeof value !== "string" || !value || value === "-") {
|
|
262
|
+
throw identityError(
|
|
263
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
264
|
+
`MCP stdio ${label} must name a local file`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
|
|
268
|
+
throw identityError(
|
|
269
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
270
|
+
`MCP stdio ${label} cannot be a remote URL`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return path.isAbsolute(value)
|
|
274
|
+
? path.resolve(value)
|
|
275
|
+
: path.resolve(cwd, value);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function interpreterEntrypoints(commandPath, args, cwd) {
|
|
279
|
+
const name = executableBasename(commandPath);
|
|
280
|
+
const values = [...args];
|
|
281
|
+
const entry = (index, role = "entrypoint") => [
|
|
282
|
+
{
|
|
283
|
+
argIndex: index,
|
|
284
|
+
path: localEntrypoint(values[index], cwd, role),
|
|
285
|
+
role,
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
if (["node", "nodejs"].includes(name)) {
|
|
289
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
290
|
+
const value = values[index];
|
|
291
|
+
if (["-e", "--eval", "-p", "--print"].includes(value)) return [];
|
|
292
|
+
if (
|
|
293
|
+
value === "-r" ||
|
|
294
|
+
value === "--require" ||
|
|
295
|
+
value === "--import" ||
|
|
296
|
+
value === "--loader" ||
|
|
297
|
+
value === "--experimental-loader" ||
|
|
298
|
+
value.startsWith("--require=") ||
|
|
299
|
+
value.startsWith("--import=") ||
|
|
300
|
+
value.startsWith("--loader=") ||
|
|
301
|
+
value.startsWith("--experimental-loader=")
|
|
302
|
+
) {
|
|
303
|
+
throw identityError(
|
|
304
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
305
|
+
"MCP stdio Node preload/import flags require a transitive code lock and are not supported",
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
if (value === "--") return entry(index + 1);
|
|
309
|
+
if (!value.startsWith("-")) return entry(index);
|
|
310
|
+
}
|
|
311
|
+
throw identityError(
|
|
312
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
313
|
+
"MCP stdio Node launch must provide a script or inline program",
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (["python", "python3", "pythonw", "pythonw3"].includes(name)) {
|
|
317
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
318
|
+
const value = values[index];
|
|
319
|
+
if (value === "-c") return [];
|
|
320
|
+
if (value === "-m") {
|
|
321
|
+
throw identityError(
|
|
322
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
323
|
+
"MCP stdio Python -m modules are not bound to one entrypoint file",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (value === "--") return entry(index + 1);
|
|
327
|
+
if (!value.startsWith("-")) return entry(index);
|
|
328
|
+
}
|
|
329
|
+
throw identityError(
|
|
330
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
331
|
+
"MCP stdio Python launch must provide a script or inline program",
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
if (["bash", "dash", "ksh", "sh", "zsh"].includes(name)) {
|
|
335
|
+
const inline = values.findIndex((value) => value === "-c");
|
|
336
|
+
if (inline >= 0) return [];
|
|
337
|
+
const index = values.findIndex((value) => !value.startsWith("-"));
|
|
338
|
+
if (index >= 0) return entry(index, "shell-script");
|
|
339
|
+
throw identityError(
|
|
340
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
341
|
+
"MCP stdio shell launch must provide a script or inline command",
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (["powershell", "pwsh"].includes(name)) {
|
|
345
|
+
const fileIndex = values.findIndex((value) => /^-file$/i.test(value));
|
|
346
|
+
if (fileIndex >= 0) return entry(fileIndex + 1, "powershell-script");
|
|
347
|
+
if (values.some((value) => /^-(?:command|encodedcommand)$/i.test(value))) {
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
throw identityError(
|
|
351
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
352
|
+
"MCP stdio PowerShell launch must use -File or an inline command",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (name === "java") {
|
|
356
|
+
const jarIndex = values.findIndex((value) => value === "-jar");
|
|
357
|
+
if (jarIndex >= 0) return entry(jarIndex + 1, "java-jar");
|
|
358
|
+
throw identityError(
|
|
359
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
360
|
+
"MCP stdio Java classpath launches are not bound to one entrypoint file",
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (name === "dotnet") {
|
|
364
|
+
const index = values.findIndex((value) => !value.startsWith("-"));
|
|
365
|
+
if (index >= 0 && /\.dll$/i.test(values[index])) {
|
|
366
|
+
return entry(index, "dotnet-assembly");
|
|
367
|
+
}
|
|
368
|
+
throw identityError(
|
|
369
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
370
|
+
"MCP stdio dotnet tool/project launches are not bound to one assembly file",
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function comparableIdentity(identity) {
|
|
377
|
+
return {
|
|
378
|
+
command: identity.command,
|
|
379
|
+
entrypoints: identity.entrypoints,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function identityDigest(identity) {
|
|
384
|
+
return sha256(JSON.stringify(comparableIdentity(identity)));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function attestMcpStdioExecutableIdentity({
|
|
388
|
+
command,
|
|
389
|
+
args = [],
|
|
390
|
+
env = {},
|
|
391
|
+
cwd = process.cwd(),
|
|
392
|
+
platform = process.platform,
|
|
393
|
+
}) {
|
|
394
|
+
const name = executableBasename(command);
|
|
395
|
+
if (DYNAMIC_LAUNCHERS.has(name)) {
|
|
396
|
+
throw identityError(
|
|
397
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
398
|
+
`MCP stdio dynamic launcher "${name}" can execute code not identified by its own bytes`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const launchEnv = { ...process.env, ...env };
|
|
402
|
+
const commandPath = resolveExecutable(command, cwd, launchEnv, platform);
|
|
403
|
+
const commandObservation = {};
|
|
404
|
+
const commandIdentity = attestFile(
|
|
405
|
+
commandPath,
|
|
406
|
+
MAX_EXECUTABLE_BYTES,
|
|
407
|
+
"command",
|
|
408
|
+
platform,
|
|
409
|
+
commandObservation,
|
|
410
|
+
);
|
|
411
|
+
if (commandObservation.hasShebang) {
|
|
412
|
+
throw identityError(
|
|
413
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
414
|
+
"MCP stdio direct shebang commands do not bind the interpreter bytes; configure the interpreter and script as command + args",
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const launchArgs = [...args];
|
|
418
|
+
const entrypoints = interpreterEntrypoints(commandPath, launchArgs, cwd).map(
|
|
419
|
+
({ argIndex, path: entryPath, role }) => {
|
|
420
|
+
const identity = attestFile(
|
|
421
|
+
entryPath,
|
|
422
|
+
MAX_ENTRYPOINT_BYTES,
|
|
423
|
+
role,
|
|
424
|
+
platform,
|
|
425
|
+
);
|
|
426
|
+
launchArgs[argIndex] = identity.realPath;
|
|
427
|
+
return Object.freeze({ argIndex, ...identity });
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
const identity = Object.freeze({
|
|
431
|
+
version: 1,
|
|
432
|
+
command: commandIdentity,
|
|
433
|
+
entrypoints: Object.freeze(entrypoints),
|
|
434
|
+
});
|
|
435
|
+
return Object.freeze({
|
|
436
|
+
launchCommand: commandIdentity.realPath,
|
|
437
|
+
launchArgs: Object.freeze(launchArgs),
|
|
438
|
+
identity,
|
|
439
|
+
identityDigest: identityDigest(identity),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function trustKey(approvalRecord) {
|
|
444
|
+
return sha256(
|
|
445
|
+
`${approvalRecord.approvalKind}\0${approvalRecord.approvalSource}\0${approvalRecord.fingerprint}`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function trustMessage(serverName, attestation, status) {
|
|
450
|
+
const entrypoints = attestation.identity.entrypoints
|
|
451
|
+
.map((entry) => `${entry.realPath} sha256:${entry.sha256}`)
|
|
452
|
+
.join(", ");
|
|
453
|
+
return (
|
|
454
|
+
`MCP stdio executable identity ${status} for "${serverName}": ` +
|
|
455
|
+
`${attestation.identity.command.realPath} sha256:${attestation.identity.command.sha256}` +
|
|
456
|
+
(entrypoints ? `; ${entrypoints}` : "") +
|
|
457
|
+
". Review these bytes, then run 'cc mcp trust-executable <name>' or set CC_MCP_EXECUTABLE_TRUST=1 for this run"
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function checkOrRecordTrust(
|
|
462
|
+
serverName,
|
|
463
|
+
approvalRecord,
|
|
464
|
+
attestation,
|
|
465
|
+
options = {},
|
|
466
|
+
) {
|
|
467
|
+
const target = storePath(options);
|
|
468
|
+
const key = trustKey(approvalRecord);
|
|
469
|
+
const requested =
|
|
470
|
+
options.retrust === true || retrustRequested(options.env || process.env);
|
|
471
|
+
return mutateSecurityStore(
|
|
472
|
+
target,
|
|
473
|
+
STORE_LABEL,
|
|
474
|
+
(store) => {
|
|
475
|
+
const existing = store[key] || null;
|
|
476
|
+
if (
|
|
477
|
+
existing?.identityDigest === attestation.identityDigest &&
|
|
478
|
+
!requested
|
|
479
|
+
) {
|
|
480
|
+
return { status: "trusted", key };
|
|
481
|
+
}
|
|
482
|
+
if (!requested) {
|
|
483
|
+
const changed = Boolean(existing);
|
|
484
|
+
throw identityError(
|
|
485
|
+
changed
|
|
486
|
+
? MCP_STDIO_EXECUTABLE_CHANGED_CODE
|
|
487
|
+
: MCP_STDIO_EXECUTABLE_TRUST_REQUIRED_CODE,
|
|
488
|
+
trustMessage(
|
|
489
|
+
serverName,
|
|
490
|
+
attestation,
|
|
491
|
+
changed ? "changed" : "requires trust",
|
|
492
|
+
),
|
|
493
|
+
{
|
|
494
|
+
executableIdentityDigest: attestation.identityDigest,
|
|
495
|
+
previousExecutableIdentityDigest: existing?.identityDigest || null,
|
|
496
|
+
},
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
store[key] = {
|
|
500
|
+
approvalKind: approvalRecord.approvalKind,
|
|
501
|
+
approvalSource: approvalRecord.approvalSource,
|
|
502
|
+
invocationFingerprint: approvalRecord.fingerprint,
|
|
503
|
+
identityDigest: attestation.identityDigest,
|
|
504
|
+
identity: comparableIdentity(attestation.identity),
|
|
505
|
+
trustedAt: new Date(
|
|
506
|
+
typeof options.now === "number" ? options.now : Date.now(),
|
|
507
|
+
).toISOString(),
|
|
508
|
+
};
|
|
509
|
+
return {
|
|
510
|
+
status: existing ? "retrusted" : "trusted-first-use",
|
|
511
|
+
key,
|
|
512
|
+
};
|
|
513
|
+
},
|
|
514
|
+
{ lock: _deps.withFileLock },
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function prepareMcpStdioExecutableIdentity({
|
|
519
|
+
serverName,
|
|
520
|
+
config,
|
|
521
|
+
approval,
|
|
522
|
+
cwd = process.cwd(),
|
|
523
|
+
env = process.env,
|
|
524
|
+
retrust = false,
|
|
525
|
+
storePath: explicitStorePath,
|
|
526
|
+
}) {
|
|
527
|
+
const approvalRecord = resolveMcpStdioExecutionApproval(approval);
|
|
528
|
+
if (!approvalRecord) {
|
|
529
|
+
throw identityError(
|
|
530
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
531
|
+
`MCP stdio executable identity for "${serverName}" requires a valid execution approval`,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const attestation = attestMcpStdioExecutableIdentity({
|
|
535
|
+
command: config.command,
|
|
536
|
+
args: config.args || [],
|
|
537
|
+
env: config.env || {},
|
|
538
|
+
cwd,
|
|
539
|
+
});
|
|
540
|
+
const trust = checkOrRecordTrust(serverName, approvalRecord, attestation, {
|
|
541
|
+
env,
|
|
542
|
+
retrust,
|
|
543
|
+
...(explicitStorePath ? { storePath: explicitStorePath } : {}),
|
|
544
|
+
});
|
|
545
|
+
const authority = Object.freeze({});
|
|
546
|
+
issuedLaunchAuthorities.set(
|
|
547
|
+
authority,
|
|
548
|
+
Object.freeze({
|
|
549
|
+
command: attestation.launchCommand,
|
|
550
|
+
args: Object.freeze([...attestation.launchArgs]),
|
|
551
|
+
identity: attestation.identity,
|
|
552
|
+
identityDigest: attestation.identityDigest,
|
|
553
|
+
}),
|
|
554
|
+
);
|
|
555
|
+
return Object.freeze({
|
|
556
|
+
command: attestation.launchCommand,
|
|
557
|
+
args: Object.freeze([...attestation.launchArgs]),
|
|
558
|
+
identity: attestation.identity,
|
|
559
|
+
identityDigest: attestation.identityDigest,
|
|
560
|
+
authority,
|
|
561
|
+
trustStatus: trust.status,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function consumeMcpStdioExecutableIdentityAuthority(
|
|
566
|
+
authority,
|
|
567
|
+
{ command, args = [] },
|
|
568
|
+
) {
|
|
569
|
+
const issued = issuedLaunchAuthorities.get(authority);
|
|
570
|
+
if (!issued) {
|
|
571
|
+
throw identityError(
|
|
572
|
+
MCP_STDIO_EXECUTABLE_AUTHORITY_REPLAYED_CODE,
|
|
573
|
+
"MCP stdio executable identity authority is invalid or already consumed",
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
issuedLaunchAuthorities.delete(authority);
|
|
577
|
+
if (
|
|
578
|
+
command !== issued.command ||
|
|
579
|
+
JSON.stringify(args) !== JSON.stringify(issued.args)
|
|
580
|
+
) {
|
|
581
|
+
throw identityError(
|
|
582
|
+
MCP_STDIO_EXECUTABLE_CHANGED_CODE,
|
|
583
|
+
"MCP stdio launch changed after executable identity approval",
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
const current = attestMcpStdioExecutableIdentity({
|
|
587
|
+
command,
|
|
588
|
+
args,
|
|
589
|
+
cwd: process.cwd(),
|
|
590
|
+
});
|
|
591
|
+
if (current.identityDigest !== issued.identityDigest) {
|
|
592
|
+
throw identityError(
|
|
593
|
+
MCP_STDIO_EXECUTABLE_CHANGED_CODE,
|
|
594
|
+
"MCP stdio executable or entrypoint bytes changed before spawn",
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
return Object.freeze({ identityDigest: issued.identityDigest });
|
|
598
|
+
}
|