chainlesschain 0.162.198 → 0.162.200
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/bin/chainlesschain.js +14 -6
- package/package.json +4 -1
- package/scripts/command-lifecycle-report.mjs +94 -0
- package/src/assets/web-panel/assets/{AIOps-CSN-D2xb.js → AIOps-DcQ0bWKy.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CBSMlGzv.js → ActionButton-GecwfFPt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-3yr3Nt2t.js → Analytics-CD6iSt9v.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-1b-o3lMH.js → AppLayout-CDEtuEYf.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dr8rJi17.js → Artifacts-DS3jAjeW.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-2V689HC2.js → Audit-CcSJA048.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DIxkNrD_.js → BackgroundAgents-DRo9Morb.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Pg6BcwrE.js → Backup-Vw5MrWft.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CLUpqyfH.js → BaseInput-DNHIu823.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-qVS8rD4G.js → Chat-DvAsO8-b.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-ti49X12Q.js → ChatBubbleRenderer-9aDuObay.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Bs9AaEay.js → Checkbox-CKCa7tvn.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dvc1mq8q.js → Codegen-CIFvZdaT.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C4_biO7A.js → Col-C79yz7bK.js} +1 -1
- package/src/assets/web-panel/assets/{Community-al25XQUV.js → Community-DVvZskLJ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-D-ZDB8M1.js → Compact-oaew61hV.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-6lIfDz1W.js → Compliance-DqA7wmRx.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DdI48XMJ.js → Cowork-B-qGadxa.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DF5HZd2g.js → Cron-CUlCl4sS.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DuxeSvt3.js → Crosschain-CNOlr9DU.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B9yHkUTd.js → DID-DJqFCT_3.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Z5EuI3IM.js → Dashboard-CznGpphf.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-vuRfO3dY.js → Dropdown-_cJw218E.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-QFLCc1uL.js → EmailListRenderer-Dr-1BZ9C.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BZ7EXkFJ.js → FamilyGuardDashboard-Dxf4xtSk.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DycIEg2s.js → Federation-DOiG3i_1.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Ba0H_7yG.js → FormItemContext-BzbMcFB9.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer--JgSTK-C.js → GenericCardRenderer-VbBrYttP.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DEvTbldE.js → Git-CLG8E6ED.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJIljy18.js → Governance-LGJQfk_q.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CVyW3G3q.js → Inference-DlQKx9lF.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-v8-borge.js → KnowledgeGraph-DU8vEouV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BCdS7gGo.js → Logs-FZIQOdln.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BJNWIrbw.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-AN6tGA2B.js → Marketplace-C_xd-zZI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DGViXASN.js → McpTools-q-YktphS.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-Bpvmx9X4.js → Memory-Cq9OhDin.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CTP-DOTC.js → MobileBridge-B03_R98T.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-C8qfktxn.js → MobileProjects-DZrk-qmF.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-ndYr0M1s.js → Mtc-BidCKW28.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Ci0-YK7W.js → MtcAudit-r-iaMVM7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BehSTFW7.js → Multisig-CgTUFvff.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-B91_YBLv.js → NLProgramming-B7NvzfAV.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BQlJ0-gq.js → Notes-80CWNqJI.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CDgYMjYK.js → NotificationSettings-0H6xdiY3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BJkSlKeH.js → OrderTableRenderer-CKP4AFWy.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B-b3cWCp.js → Organization-B8sxCV_h.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CILUL4nL.js → Overflow-CW9JBBhQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BTCFBkF0.js → P2P-bdSQV7oZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BAR6AUx5.js → PdhVaultBrowser-DQad42nW.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-RRf6HbWJ.js → Permissions-CueexxTR.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BGTMiICa.js → PersonalDataHub-fqxflhR8.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-945qY9YF.js → Pipeline-KFwwnIMC.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DPZwI6lJ.js → Privacy-ClnD9buY.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BPH2NRKc.js → ProjectInit-CZEz6sm3.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BZCRNCvb.js → ProjectSettings-B6NzpmOI.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BTI__tBt.js → Projects-DYyuwPCs.js} +1 -1
- package/src/assets/web-panel/assets/Providers-C3tz_jdh.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-N6rbqt1r.js → QrScannerModal-BzuOBbqB.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DrwjPS7M.js → QuickAsk-CmNO9qSj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cog9Wd99.js → Recommend-VJNj3_NF.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DC9pynjM.js → RemoteSession-CrbdbSBF.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D4th4gKS.js → Reputation-DTyMd45j.js} +1 -1
- package/src/assets/web-panel/assets/{Row-ogiWR6v9.js → Row-DHrQ-Icz.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-uYlk-2Oj.js → RssFeed-DqWxY9c5.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DYzzwvtM.js → Search-ctF1Ks3J.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Da7YOyX6.js → Security-BrQz8uo5.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CLI1VM1X.js → Services-D4scVFar.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DK77dzDC.js → Skeleton-oUScaOjv.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C_z7Lmhl.js → Skills-CL-y0eLt.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-D6413Q8L.js → Sla-DQExhwXj.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C07ydbj2.js → SpeechSettings-iNp_I7kN.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DGOm2W22.js → SyncSettings-CTDlMo0A.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cr_PVyLM.js → Tasks-4kb8l3sc.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BhL2mZ0t.js → Templates-BXcL9oXW.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CPk5oyUr.js → Tenant-DLE8eS0D.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-ClhqUsqV.js → Terminal-DGpUZPPK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BCC7MV5a.js → TimelineRenderer-C1pWXGPC.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DD1Jt4oO.js → Tokens-BYfbfqhZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BCydFvN2.js → Trigger-K9YfLOCw.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DDHT1noa.js → Trust-Ctkq77Hy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CWPZC7oI.js → UkeySign-C6_CSe9J.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9Ea0nJR0.js → VideoEditing-Be3rpOWs.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BjxhLxTf.js → Wallet-D0UIplxk.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C6uCye67.js → WebAuthn-CF9bM9I3.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-d2g4reYo.js → WorkflowEditor-D0H1XVPY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-frTc6ScX.js → chat-Cgzzbe0o.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B5K6lG3v.js → colors-BwNxEVS0.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cdn46r8D.js → compact-item-D60Z4Hhs.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DU1jqVQi.js → createContext-MFk58XB0.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BJiw369i.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DdNH-aW_.js → hasIn-CRj2SvGO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM5FmPZU.js → index--s45_1cb.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4l82Me1.js → index--tquQ2ZG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQorP1Ng.js → index-1T1rhokn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHmkkdfT.js → index-B-M82_rx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGiwfwB9.js → index-BE_el1Zs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwlIxAmX.js → index-BhRkR7IO.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cj6P9Nst.js → index-Bo0y96-K.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoFak7le.js → index-CJgu5JUK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dca_i7H8.js → index-CLWwdebO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVLL-mq_.js → index-CNAK5QG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-LaivuXyF.js → index-CQjJjPRD.js} +1 -1
- package/src/assets/web-panel/assets/{index-t_OBzIP4.js → index-CTDbAfae.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGwGIaR.js → index-CVI_9sRK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23xGYjM.js → index-CY14oMQK.js} +1 -1
- package/src/assets/web-panel/assets/{index-VDM2KKo9.js → index-CbiTJkZf.js} +1 -1
- package/src/assets/web-panel/assets/index-CiMouosn.js +1 -0
- package/src/assets/web-panel/assets/{index-32ajXmTb.js → index-CqWpxMqx.js} +1 -1
- package/src/assets/web-panel/assets/{index-PjAS0sIZ.js → index-CvB8lYUR.js} +1 -1
- package/src/assets/web-panel/assets/{index-7ehytj1K.js → index-D51XwtCY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtBCCkC4.js → index-D6HvK_1O.js} +1 -1
- package/src/assets/web-panel/assets/{index-Be4OtL-N.js → index-D9jVIwLJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUoNAmE8.js → index-DA4tE98u.js} +1 -1
- package/src/assets/web-panel/assets/{index-cYaWV4Mq.js → index-DBwOtXw0.js} +1 -1
- package/src/assets/web-panel/assets/{index-MM4Zz2OE.js → index-DKdNzTIb.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIVyTmZ3.js → index-DRySdmvN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtRPi4YC.js → index-DWzyTev7.js} +1 -1
- package/src/assets/web-panel/assets/index-DXcKlwy7.js +1 -0
- package/src/assets/web-panel/assets/{index-kVO_MDm-.js → index-DYjs7jAP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4miNkWv.js → index-DbROzMBp.js} +1 -1
- package/src/assets/web-panel/assets/{index-yZRuZpwK.js → index-DhFtk5PR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXKhhx9V.js → index-Di2AbMLg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC4eGkar.js → index-DvmocQsM.js} +1 -1
- package/src/assets/web-panel/assets/{index-NmEXVjzt.js → index-Dw-o0zEF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzBBdQ0.js → index-DysPoP38.js} +1 -1
- package/src/assets/web-panel/assets/{index-HjCvXHf5.js → index-LX9pJxm_.js} +1 -1
- package/src/assets/web-panel/assets/{index-gg3QPC3i.js → index-OI_fXWx0.js} +3 -3
- package/src/assets/web-panel/assets/{index-mgU3Q-lN.js → index-eDTOX8Xj.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1qIC4GR.js → index-gWKDsTzF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DftZuyA7.js → index-q_GDUVAA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DwLTRNdo.js → initDefaultProps-C61kZqEd.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BchZDXnh.js → motion-DZnT5U1L.js} +1 -1
- package/src/assets/web-panel/assets/{move-e8M-zZFl.js → move-L8wzwbel.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Ci40fQb3.js → mtc-parser-DLdYSejc.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C7110_f2.js → omit-D53cbS59.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-skteMgnM.js → pickAttrs-Bbc5HPtu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-MGZ-lh2B.js → placementArrow-BkvmO860.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-N4Sd7uy-.js → responsiveObserve-u2y1ryS1.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C7HDGRaa.js → slide-BBqj7svU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BhRexlgy.js → statusUtils-GN0mqu2d.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-ChEoQkN2.js → styleChecker-BvwPKoBT.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CDbG1s2Y.js → useFlexGapSupport-CCt8tnfY.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUIx0tcJ.js → useFs-DXAxl2SV.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-bkO7L7ec.js → usePersonalDataHub-DihlR2dj.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B_ZvABIG.js → vnode-D7BOX2gi.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D5shjpS8.js → zoom-CVbO-nL7.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/mcp.js +119 -0
- package/src/commands/session-show.js +8 -10
- package/src/commands/session.js +60 -52
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/session-protocol.js +14 -4
- package/src/gateways/ws/ws-agent-handler.js +31 -0
- package/src/harness/jsonl-session-store.js +1147 -130
- package/src/harness/mcp-client.js +44 -11
- package/src/harness/session-index.js +80 -80
- package/src/harness/session-list-index.js +147 -2
- package/src/lib/background-interaction-journal.js +2 -2
- package/src/lib/command-lifecycle-report.js +516 -0
- package/src/lib/mcp-stdio-environment.js +48 -0
- package/src/lib/mcp-stdio-executable-identity.js +552 -17
- package/src/lib/mcp-stdio-package-materialization.js +1424 -0
- package/src/lib/packer/native-update-state.js +979 -1
- package/src/lib/packer/pack-update-applier.js +1050 -125
- package/src/lib/packer/pkg-config-generator.js +1 -0
- package/src/lib/paths.js +53 -0
- package/src/lib/process-execution-broker/index.js +180 -37
- package/src/lib/process-execution-broker/platform-sandbox.js +291 -24
- package/src/lib/recent-session.js +17 -17
- package/src/lib/secure-file-identity.js +47 -0
- package/src/lib/secure-fs.js +21 -2
- package/src/lib/session-anti-rollback-anchor.js +766 -0
- package/src/lib/session-host-lease.js +492 -0
- package/src/lib/session-persistence-failure.js +109 -0
- package/src/lib/session-picker.js +26 -3
- package/src/lib/session-search.js +25 -11
- package/src/lib/side-effect-ledger-store.js +2 -2
- package/src/lib/skill-execution-authority.js +13 -9
- package/src/lib/turn-binding-store.js +2 -2
- package/src/repl/agent-repl.js +309 -39
- package/src/runtime/agent-core.js +88 -5
- package/src/runtime/cleanup-deadline.js +143 -0
- package/src/runtime/headless-runner.js +357 -139
- package/src/runtime/headless-stream.js +207 -28
- package/src/runtime/output-backpressure.js +53 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-CU-JB2Ns.js +0 -1
- package/src/assets/web-panel/assets/Providers-DDCf_nIw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +0 -1
- package/src/assets/web-panel/assets/index-CzrCHrFy.js +0 -1
- package/src/assets/web-panel/assets/index-OJsGskZw.js +0 -1
|
@@ -4,16 +4,30 @@
|
|
|
4
4
|
* A trusted config names code; it does not prove which bytes PATH or an
|
|
5
5
|
* interpreter will open. This module resolves those paths, attests regular
|
|
6
6
|
* files through open descriptors, persists the first explicitly trusted
|
|
7
|
-
* identity
|
|
7
|
+
* identity in OS user security state outside CHAINLESSCHAIN_HOME, and issues a
|
|
8
|
+
* one-shot broker token for the final pre-spawn check. Restoring the CLI home
|
|
9
|
+
* therefore cannot silently restore an older executable trust decision.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import crypto from "node:crypto";
|
|
11
13
|
import fs from "node:fs";
|
|
12
14
|
import path from "node:path";
|
|
13
|
-
import {
|
|
15
|
+
import { getMachineSecurityAnchorDir } from "./paths.js";
|
|
14
16
|
import { withFileLock } from "./with-file-lock.js";
|
|
15
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
readSecurityStore,
|
|
19
|
+
writeSecurityStore,
|
|
20
|
+
} from "./durable-security-store.js";
|
|
16
21
|
import { resolveMcpStdioExecutionApproval } from "./mcp-stdio-execution-authority.js";
|
|
22
|
+
import {
|
|
23
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_REQUIRED_CODE,
|
|
24
|
+
reattestMcpStdioPackageMaterialization,
|
|
25
|
+
resolveMcpStdioPackageMaterialization,
|
|
26
|
+
} from "./mcp-stdio-package-materialization.js";
|
|
27
|
+
import {
|
|
28
|
+
isMcpStdioCodeInjectionEnvironmentKey,
|
|
29
|
+
sanitizeMcpStdioHostEnvironment,
|
|
30
|
+
} from "./mcp-stdio-environment.js";
|
|
17
31
|
|
|
18
32
|
export const MCP_STDIO_EXECUTABLE_TRUST_REQUIRED_CODE =
|
|
19
33
|
"CC_MCP_STDIO_EXECUTABLE_TRUST_REQUIRED";
|
|
@@ -25,12 +39,20 @@ export const MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE =
|
|
|
25
39
|
"CC_MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED";
|
|
26
40
|
export const MCP_STDIO_EXECUTABLE_AUTHORITY_REPLAYED_CODE =
|
|
27
41
|
"CC_MCP_STDIO_EXECUTABLE_AUTHORITY_REPLAYED";
|
|
42
|
+
export const MCP_STDIO_EXECUTABLE_IDENTITY_ROLLBACK_CODE =
|
|
43
|
+
"CC_MCP_STDIO_EXECUTABLE_IDENTITY_ROLLBACK";
|
|
44
|
+
export const MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND =
|
|
45
|
+
"strict-mcp-node-capsule";
|
|
46
|
+
export const MCP_STDIO_CAPSULE_CODE_SNAPSHOT_BOUNDARY = "code-snapshot";
|
|
28
47
|
|
|
29
48
|
const STORE_LABEL = "MCP stdio executable identity";
|
|
49
|
+
const WITNESS_LABEL = "MCP stdio executable identity anti-rollback witness";
|
|
50
|
+
const WITNESS_VERSION = 1;
|
|
30
51
|
const MAX_EXECUTABLE_BYTES = 256 * 1024 * 1024;
|
|
31
52
|
const MAX_ENTRYPOINT_BYTES = 64 * 1024 * 1024;
|
|
32
53
|
const HASH_CHUNK_BYTES = 1024 * 1024;
|
|
33
54
|
const issuedLaunchAuthorities = new WeakMap();
|
|
55
|
+
const issuedCapsuleSandboxContracts = new WeakMap();
|
|
34
56
|
|
|
35
57
|
const DYNAMIC_LAUNCHERS = new Set([
|
|
36
58
|
"bunx",
|
|
@@ -39,6 +61,7 @@ const DYNAMIC_LAUNCHERS = new Set([
|
|
|
39
61
|
"npx",
|
|
40
62
|
"pipx",
|
|
41
63
|
"pnpm",
|
|
64
|
+
"pnpx",
|
|
42
65
|
"uvx",
|
|
43
66
|
"yarn",
|
|
44
67
|
"yarnpkg",
|
|
@@ -61,11 +84,298 @@ function sha256(value) {
|
|
|
61
84
|
return crypto.createHash("sha256").update(value).digest("hex");
|
|
62
85
|
}
|
|
63
86
|
|
|
64
|
-
function
|
|
87
|
+
export function getMcpStdioExecutableTrustStorePath(options = {}) {
|
|
65
88
|
return (
|
|
66
89
|
options.storePath ||
|
|
67
90
|
process.env.CC_MCP_EXECUTABLE_TRUST_STORE ||
|
|
68
|
-
path.join(
|
|
91
|
+
path.join(
|
|
92
|
+
getMachineSecurityAnchorDir(),
|
|
93
|
+
"mcp-stdio-executable-identities-v1.json",
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getMcpStdioExecutableTrustWitnessPath(options = {}) {
|
|
99
|
+
return (
|
|
100
|
+
options.witnessPath ||
|
|
101
|
+
process.env.CC_MCP_EXECUTABLE_TRUST_WITNESS ||
|
|
102
|
+
`${getMcpStdioExecutableTrustStorePath(options)}.anti-rollback-v1.json`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function storeDigest(store) {
|
|
107
|
+
return sha256(JSON.stringify(store));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseWitnessGeneration(value, label) {
|
|
111
|
+
if (typeof value !== "string" || !/^(?:0|[1-9]\d*)$/.test(value)) {
|
|
112
|
+
throw new TypeError(`${label} must be a canonical non-negative integer`);
|
|
113
|
+
}
|
|
114
|
+
return BigInt(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function validateStoreDigest(value, label) {
|
|
118
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) {
|
|
119
|
+
throw new TypeError(`${label} must be a SHA-256 digest`);
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateWitnessTransition(value, expectedPrevious, label) {
|
|
125
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
126
|
+
throw new TypeError(`${label} must be an object`);
|
|
127
|
+
}
|
|
128
|
+
const generation = parseWitnessGeneration(
|
|
129
|
+
value.generation,
|
|
130
|
+
`${label}.generation`,
|
|
131
|
+
);
|
|
132
|
+
const previousGeneration = parseWitnessGeneration(
|
|
133
|
+
value.previousGeneration,
|
|
134
|
+
`${label}.previousGeneration`,
|
|
135
|
+
);
|
|
136
|
+
if (
|
|
137
|
+
previousGeneration !== expectedPrevious.generation ||
|
|
138
|
+
generation !== previousGeneration + 1n
|
|
139
|
+
) {
|
|
140
|
+
throw new TypeError(`${label} breaks the generation chain`);
|
|
141
|
+
}
|
|
142
|
+
const previousStoreDigest = validateStoreDigest(
|
|
143
|
+
value.previousStoreDigest,
|
|
144
|
+
`${label}.previousStoreDigest`,
|
|
145
|
+
);
|
|
146
|
+
if (previousStoreDigest !== expectedPrevious.storeDigest) {
|
|
147
|
+
throw new TypeError(`${label} breaks the store digest chain`);
|
|
148
|
+
}
|
|
149
|
+
const nextStoreDigest = validateStoreDigest(
|
|
150
|
+
value.storeDigest,
|
|
151
|
+
`${label}.storeDigest`,
|
|
152
|
+
);
|
|
153
|
+
if (nextStoreDigest === previousStoreDigest) {
|
|
154
|
+
throw new TypeError(`${label} does not advance the store digest`);
|
|
155
|
+
}
|
|
156
|
+
if (
|
|
157
|
+
typeof value.mutationId !== "string" ||
|
|
158
|
+
!/^[a-f0-9-]{16,64}$/i.test(value.mutationId)
|
|
159
|
+
) {
|
|
160
|
+
throw new TypeError(`${label}.mutationId is invalid`);
|
|
161
|
+
}
|
|
162
|
+
if (
|
|
163
|
+
typeof value.preparedAt !== "string" ||
|
|
164
|
+
!Number.isFinite(Date.parse(value.preparedAt))
|
|
165
|
+
) {
|
|
166
|
+
throw new TypeError(`${label}.preparedAt is invalid`);
|
|
167
|
+
}
|
|
168
|
+
if (
|
|
169
|
+
value.committedAt !== undefined &&
|
|
170
|
+
(typeof value.committedAt !== "string" ||
|
|
171
|
+
!Number.isFinite(Date.parse(value.committedAt)))
|
|
172
|
+
) {
|
|
173
|
+
throw new TypeError(`${label}.committedAt is invalid`);
|
|
174
|
+
}
|
|
175
|
+
return Object.freeze({
|
|
176
|
+
generation,
|
|
177
|
+
previousGeneration,
|
|
178
|
+
previousStoreDigest,
|
|
179
|
+
storeDigest: nextStoreDigest,
|
|
180
|
+
mutationId: value.mutationId,
|
|
181
|
+
preparedAt: value.preparedAt,
|
|
182
|
+
...(value.committedAt ? { committedAt: value.committedAt } : {}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function validateWitness(witness) {
|
|
187
|
+
if (witness.version !== WITNESS_VERSION) {
|
|
188
|
+
throw new TypeError(
|
|
189
|
+
`unsupported MCP executable trust witness version: ${String(witness.version)}`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const generation = parseWitnessGeneration(
|
|
193
|
+
witness.generation,
|
|
194
|
+
"witness.generation",
|
|
195
|
+
);
|
|
196
|
+
const initialStoreDigest = validateStoreDigest(
|
|
197
|
+
witness.initialStoreDigest,
|
|
198
|
+
"witness.initialStoreDigest",
|
|
199
|
+
);
|
|
200
|
+
if (!Array.isArray(witness.events)) {
|
|
201
|
+
throw new TypeError("witness.events must be an array");
|
|
202
|
+
}
|
|
203
|
+
let previous = Object.freeze({
|
|
204
|
+
generation: 0n,
|
|
205
|
+
storeDigest: initialStoreDigest,
|
|
206
|
+
});
|
|
207
|
+
for (const [index, event] of witness.events.entries()) {
|
|
208
|
+
const transition = validateWitnessTransition(
|
|
209
|
+
event,
|
|
210
|
+
previous,
|
|
211
|
+
`witness.events[${index}]`,
|
|
212
|
+
);
|
|
213
|
+
if (!transition.committedAt) {
|
|
214
|
+
throw new TypeError(`witness.events[${index}] is not committed`);
|
|
215
|
+
}
|
|
216
|
+
previous = Object.freeze({
|
|
217
|
+
generation: transition.generation,
|
|
218
|
+
storeDigest: transition.storeDigest,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (generation !== previous.generation) {
|
|
222
|
+
throw new TypeError("witness generation does not match its event chain");
|
|
223
|
+
}
|
|
224
|
+
const committedStoreDigest = validateStoreDigest(
|
|
225
|
+
witness.storeDigest,
|
|
226
|
+
"witness.storeDigest",
|
|
227
|
+
);
|
|
228
|
+
if (committedStoreDigest !== previous.storeDigest) {
|
|
229
|
+
throw new TypeError("witness store digest does not match its event chain");
|
|
230
|
+
}
|
|
231
|
+
const pending =
|
|
232
|
+
witness.pending == null
|
|
233
|
+
? null
|
|
234
|
+
: validateWitnessTransition(witness.pending, previous, "witness.pending");
|
|
235
|
+
if (pending?.committedAt) {
|
|
236
|
+
throw new TypeError("witness.pending must not already be committed");
|
|
237
|
+
}
|
|
238
|
+
return Object.freeze({
|
|
239
|
+
generation,
|
|
240
|
+
initialStoreDigest,
|
|
241
|
+
storeDigest: committedStoreDigest,
|
|
242
|
+
events: Object.freeze([...witness.events]),
|
|
243
|
+
pending,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function initialWitness(digest) {
|
|
248
|
+
return {
|
|
249
|
+
version: WITNESS_VERSION,
|
|
250
|
+
generation: "0",
|
|
251
|
+
initialStoreDigest: digest,
|
|
252
|
+
storeDigest: digest,
|
|
253
|
+
events: [],
|
|
254
|
+
pending: null,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function rollbackError(message, details = {}) {
|
|
259
|
+
return identityError(
|
|
260
|
+
MCP_STDIO_EXECUTABLE_IDENTITY_ROLLBACK_CODE,
|
|
261
|
+
`MCP stdio executable trust rollback detected: ${message}`,
|
|
262
|
+
details,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function recoverWitness(witnessPath, witness, currentStoreDigest) {
|
|
267
|
+
const snapshot = validateWitness(witness);
|
|
268
|
+
if (!snapshot.pending) {
|
|
269
|
+
if (currentStoreDigest !== snapshot.storeDigest) {
|
|
270
|
+
throw rollbackError(
|
|
271
|
+
"the trust store does not match its committed witness",
|
|
272
|
+
{
|
|
273
|
+
expectedStoreDigest: snapshot.storeDigest,
|
|
274
|
+
observedStoreDigest: currentStoreDigest,
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return snapshot;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (currentStoreDigest === snapshot.storeDigest) {
|
|
282
|
+
witness.pending = null;
|
|
283
|
+
writeSecurityStore(witnessPath, WITNESS_LABEL, witness);
|
|
284
|
+
return validateWitness(witness);
|
|
285
|
+
}
|
|
286
|
+
if (currentStoreDigest !== snapshot.pending.storeDigest) {
|
|
287
|
+
throw rollbackError(
|
|
288
|
+
"the trust store matches neither side of a pending mutation",
|
|
289
|
+
{
|
|
290
|
+
previousStoreDigest: snapshot.storeDigest,
|
|
291
|
+
pendingStoreDigest: snapshot.pending.storeDigest,
|
|
292
|
+
observedStoreDigest: currentStoreDigest,
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const committed = {
|
|
298
|
+
...witness.pending,
|
|
299
|
+
committedAt: new Date().toISOString(),
|
|
300
|
+
};
|
|
301
|
+
witness.generation = committed.generation;
|
|
302
|
+
witness.storeDigest = committed.storeDigest;
|
|
303
|
+
witness.events = [...witness.events, committed];
|
|
304
|
+
witness.pending = null;
|
|
305
|
+
writeSecurityStore(witnessPath, WITNESS_LABEL, witness);
|
|
306
|
+
return validateWitness(witness);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function mutateExecutableTrustStore(target, mutator, options = {}) {
|
|
310
|
+
const witnessPath = getMcpStdioExecutableTrustWitnessPath({
|
|
311
|
+
...options,
|
|
312
|
+
storePath: target,
|
|
313
|
+
});
|
|
314
|
+
if (path.resolve(witnessPath) === path.resolve(target)) {
|
|
315
|
+
throw new TypeError(
|
|
316
|
+
"MCP stdio executable trust store and witness must use different paths",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
_deps.fs.mkdirSync(path.dirname(witnessPath), {
|
|
321
|
+
recursive: true,
|
|
322
|
+
mode: 0o700,
|
|
323
|
+
});
|
|
324
|
+
} catch (cause) {
|
|
325
|
+
throw identityError(
|
|
326
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
327
|
+
"MCP stdio executable trust witness could not be prepared",
|
|
328
|
+
{ cause, witnessPath },
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return _deps.withFileLock(
|
|
333
|
+
witnessPath,
|
|
334
|
+
() => {
|
|
335
|
+
const store = readSecurityStore(target, STORE_LABEL);
|
|
336
|
+
const currentStoreDigest = storeDigest(store);
|
|
337
|
+
const witnessExists = _deps.fs.existsSync(witnessPath);
|
|
338
|
+
const witness = witnessExists
|
|
339
|
+
? readSecurityStore(witnessPath, WITNESS_LABEL)
|
|
340
|
+
: initialWitness(currentStoreDigest);
|
|
341
|
+
const snapshot = witnessExists
|
|
342
|
+
? recoverWitness(witnessPath, witness, currentStoreDigest)
|
|
343
|
+
: validateWitness(witness);
|
|
344
|
+
const draft = structuredClone(store);
|
|
345
|
+
const result = mutator(draft);
|
|
346
|
+
const nextStoreDigest = storeDigest(draft);
|
|
347
|
+
|
|
348
|
+
if (nextStoreDigest === currentStoreDigest) {
|
|
349
|
+
if (!witnessExists) {
|
|
350
|
+
writeSecurityStore(witnessPath, WITNESS_LABEL, witness);
|
|
351
|
+
}
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const transition = {
|
|
356
|
+
generation: String(snapshot.generation + 1n),
|
|
357
|
+
previousGeneration: String(snapshot.generation),
|
|
358
|
+
previousStoreDigest: currentStoreDigest,
|
|
359
|
+
storeDigest: nextStoreDigest,
|
|
360
|
+
mutationId: crypto.randomUUID(),
|
|
361
|
+
preparedAt: new Date().toISOString(),
|
|
362
|
+
};
|
|
363
|
+
witness.pending = transition;
|
|
364
|
+
writeSecurityStore(witnessPath, WITNESS_LABEL, witness);
|
|
365
|
+
writeSecurityStore(target, STORE_LABEL, draft);
|
|
366
|
+
|
|
367
|
+
const committed = {
|
|
368
|
+
...transition,
|
|
369
|
+
committedAt: new Date().toISOString(),
|
|
370
|
+
};
|
|
371
|
+
witness.generation = committed.generation;
|
|
372
|
+
witness.storeDigest = committed.storeDigest;
|
|
373
|
+
witness.events = [...witness.events, committed];
|
|
374
|
+
witness.pending = null;
|
|
375
|
+
writeSecurityStore(witnessPath, WITNESS_LABEL, witness);
|
|
376
|
+
return result;
|
|
377
|
+
},
|
|
378
|
+
{ timeoutMs: 2000, staleMs: 30000, failIfUnavailable: true },
|
|
69
379
|
);
|
|
70
380
|
}
|
|
71
381
|
|
|
@@ -81,6 +391,38 @@ function executableBasename(value) {
|
|
|
81
391
|
.toLowerCase();
|
|
82
392
|
}
|
|
83
393
|
|
|
394
|
+
function materializeLaunchEnvironment(hostEnv, configEnv) {
|
|
395
|
+
const launch = sanitizeMcpStdioHostEnvironment(hostEnv);
|
|
396
|
+
for (const [key, value] of Object.entries(configEnv || {})) {
|
|
397
|
+
if (isMcpStdioCodeInjectionEnvironmentKey(key)) {
|
|
398
|
+
throw identityError(
|
|
399
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
400
|
+
`MCP stdio config environment may load unattested code through ${key}`,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
if (typeof value !== "string") {
|
|
404
|
+
throw identityError(
|
|
405
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
406
|
+
`MCP stdio config environment value must be a string: ${key}`,
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
launch[key] = value;
|
|
410
|
+
}
|
|
411
|
+
return Object.freeze(launch);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function assertMcpStdioExecutableEnvironmentSafe(env) {
|
|
415
|
+
for (const key of Object.keys(env || {})) {
|
|
416
|
+
if (isMcpStdioCodeInjectionEnvironmentKey(key)) {
|
|
417
|
+
throw identityError(
|
|
418
|
+
MCP_STDIO_EXECUTABLE_UNATTESTED_CODE,
|
|
419
|
+
`MCP stdio launch environment may load unattested code through ${key}`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
|
|
84
426
|
function candidateFiles(command, cwd, env, platform) {
|
|
85
427
|
const value = String(command || "");
|
|
86
428
|
if (!value || value.includes("\0")) return [];
|
|
@@ -377,6 +719,9 @@ function comparableIdentity(identity) {
|
|
|
377
719
|
return {
|
|
378
720
|
command: identity.command,
|
|
379
721
|
entrypoints: identity.entrypoints,
|
|
722
|
+
...(identity.materialization
|
|
723
|
+
? { materialization: identity.materialization }
|
|
724
|
+
: {}),
|
|
380
725
|
};
|
|
381
726
|
}
|
|
382
727
|
|
|
@@ -384,6 +729,115 @@ function identityDigest(identity) {
|
|
|
384
729
|
return sha256(JSON.stringify(comparableIdentity(identity)));
|
|
385
730
|
}
|
|
386
731
|
|
|
732
|
+
function capsuleSandboxFileIdentity(identity, requestedPath) {
|
|
733
|
+
return Object.freeze({
|
|
734
|
+
...(requestedPath ? { requestedPath } : {}),
|
|
735
|
+
realPath: identity.realPath,
|
|
736
|
+
sha256: identity.sha256,
|
|
737
|
+
bytes: identity.bytes,
|
|
738
|
+
dev: identity.dev,
|
|
739
|
+
ino: identity.ino,
|
|
740
|
+
mtimeMs: Number(identity.mtimeNs) / 1_000_000,
|
|
741
|
+
mode: identity.mode,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function attestCapsuleRoot(capsuleRoot) {
|
|
746
|
+
const realpath = _deps.fs.realpathSync.native || _deps.fs.realpathSync;
|
|
747
|
+
const root = realpath(path.resolve(capsuleRoot));
|
|
748
|
+
const before = _deps.fs.lstatSync(root, { bigint: true });
|
|
749
|
+
const after = _deps.fs.statSync(root, { bigint: true });
|
|
750
|
+
if (
|
|
751
|
+
before.isSymbolicLink() ||
|
|
752
|
+
!before.isDirectory() ||
|
|
753
|
+
!after.isDirectory() ||
|
|
754
|
+
before.dev !== after.dev ||
|
|
755
|
+
before.ino !== after.ino
|
|
756
|
+
) {
|
|
757
|
+
throw new Error("MCP capsule root identity changed during attestation");
|
|
758
|
+
}
|
|
759
|
+
return Object.freeze({
|
|
760
|
+
realPath: root,
|
|
761
|
+
dev: String(after.dev),
|
|
762
|
+
ino: String(after.ino),
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function issueCapsuleSandboxExecutionContract({
|
|
767
|
+
serverName,
|
|
768
|
+
config,
|
|
769
|
+
attestation,
|
|
770
|
+
materialization,
|
|
771
|
+
}) {
|
|
772
|
+
if (!materialization) return null;
|
|
773
|
+
const capsuleRoot = attestCapsuleRoot(materialization.capsuleRoot);
|
|
774
|
+
const entryIdentity = attestation.identity.entrypoints.find(
|
|
775
|
+
(entry) => entry.argIndex === 0,
|
|
776
|
+
);
|
|
777
|
+
if (
|
|
778
|
+
attestation.identity.entrypoints.length !== 1 ||
|
|
779
|
+
!entryIdentity ||
|
|
780
|
+
path.dirname(entryIdentity.realPath) !== capsuleRoot.realPath ||
|
|
781
|
+
entryIdentity.realPath !== attestation.launchArgs[0]
|
|
782
|
+
) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
"MCP materialized capsule launch is not one direct Node entrypoint",
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
const contract = Object.freeze({
|
|
788
|
+
contractVersion: 1,
|
|
789
|
+
kind: MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND,
|
|
790
|
+
pluginRoot: capsuleRoot.realPath,
|
|
791
|
+
workingDirectory: capsuleRoot.realPath,
|
|
792
|
+
runtimePath: attestation.identity.command.realPath,
|
|
793
|
+
rootIdentity: capsuleRoot,
|
|
794
|
+
entryIdentity: capsuleSandboxFileIdentity(entryIdentity),
|
|
795
|
+
runtimeIdentity: capsuleSandboxFileIdentity(
|
|
796
|
+
attestation.identity.command,
|
|
797
|
+
path.resolve(process.execPath),
|
|
798
|
+
),
|
|
799
|
+
});
|
|
800
|
+
issuedCapsuleSandboxContracts.set(
|
|
801
|
+
contract,
|
|
802
|
+
Object.freeze({
|
|
803
|
+
origin: config.origin || `mcp:server:${serverName}`,
|
|
804
|
+
command: attestation.launchCommand,
|
|
805
|
+
args: Object.freeze([...attestation.launchArgs]),
|
|
806
|
+
cwd: capsuleRoot.realPath,
|
|
807
|
+
identityDigest: attestation.identityDigest,
|
|
808
|
+
}),
|
|
809
|
+
);
|
|
810
|
+
return contract;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export function consumeMcpStdioCapsuleSandboxExecutionContract(
|
|
814
|
+
contract,
|
|
815
|
+
provenance = {},
|
|
816
|
+
) {
|
|
817
|
+
const issued =
|
|
818
|
+
contract && typeof contract === "object"
|
|
819
|
+
? issuedCapsuleSandboxContracts.get(contract)
|
|
820
|
+
: null;
|
|
821
|
+
if (!issued) return false;
|
|
822
|
+
issuedCapsuleSandboxContracts.delete(contract);
|
|
823
|
+
const args = Array.isArray(provenance.args) ? provenance.args : [];
|
|
824
|
+
const requiredBoundaries = Array.isArray(provenance.requiredBoundaries)
|
|
825
|
+
? provenance.requiredBoundaries
|
|
826
|
+
: [];
|
|
827
|
+
return (
|
|
828
|
+
contract.kind === MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND &&
|
|
829
|
+
provenance.origin === issued.origin &&
|
|
830
|
+
provenance.command === issued.command &&
|
|
831
|
+
provenance.cwd === issued.cwd &&
|
|
832
|
+
provenance.shell === false &&
|
|
833
|
+
provenance.sync === false &&
|
|
834
|
+
provenance.identityDigest === issued.identityDigest &&
|
|
835
|
+
requiredBoundaries.includes(MCP_STDIO_CAPSULE_CODE_SNAPSHOT_BOUNDARY) &&
|
|
836
|
+
args.length === issued.args.length &&
|
|
837
|
+
args.every((value, index) => value === issued.args[index])
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
|
|
387
841
|
export function attestMcpStdioExecutableIdentity({
|
|
388
842
|
command,
|
|
389
843
|
args = [],
|
|
@@ -450,27 +904,46 @@ function trustMessage(serverName, attestation, status) {
|
|
|
450
904
|
const entrypoints = attestation.identity.entrypoints
|
|
451
905
|
.map((entry) => `${entry.realPath} sha256:${entry.sha256}`)
|
|
452
906
|
.join(", ");
|
|
907
|
+
const materialization = attestation.identity.materialization;
|
|
453
908
|
return (
|
|
454
909
|
`MCP stdio executable identity ${status} for "${serverName}": ` +
|
|
455
910
|
`${attestation.identity.command.realPath} sha256:${attestation.identity.command.sha256}` +
|
|
456
911
|
(entrypoints ? `; ${entrypoints}` : "") +
|
|
912
|
+
(materialization
|
|
913
|
+
? `; npm ${materialization.package.name}@${materialization.package.version} closure sha256:${materialization.closureDigest}`
|
|
914
|
+
: "") +
|
|
457
915
|
". Review these bytes, then run 'cc mcp trust-executable <name>' or set CC_MCP_EXECUTABLE_TRUST=1 for this run"
|
|
458
916
|
);
|
|
459
917
|
}
|
|
460
918
|
|
|
919
|
+
function materializedAttestation(attestation, materialization) {
|
|
920
|
+
if (!materialization) return attestation;
|
|
921
|
+
const identity = Object.freeze({
|
|
922
|
+
version: 2,
|
|
923
|
+
command: attestation.identity.command,
|
|
924
|
+
entrypoints: attestation.identity.entrypoints,
|
|
925
|
+
materialization: materialization.identity,
|
|
926
|
+
});
|
|
927
|
+
return Object.freeze({
|
|
928
|
+
launchCommand: attestation.launchCommand,
|
|
929
|
+
launchArgs: attestation.launchArgs,
|
|
930
|
+
identity,
|
|
931
|
+
identityDigest: identityDigest(identity),
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
461
935
|
function checkOrRecordTrust(
|
|
462
936
|
serverName,
|
|
463
937
|
approvalRecord,
|
|
464
938
|
attestation,
|
|
465
939
|
options = {},
|
|
466
940
|
) {
|
|
467
|
-
const target =
|
|
941
|
+
const target = getMcpStdioExecutableTrustStorePath(options);
|
|
468
942
|
const key = trustKey(approvalRecord);
|
|
469
943
|
const requested =
|
|
470
944
|
options.retrust === true || retrustRequested(options.env || process.env);
|
|
471
|
-
return
|
|
945
|
+
return mutateExecutableTrustStore(
|
|
472
946
|
target,
|
|
473
|
-
STORE_LABEL,
|
|
474
947
|
(store) => {
|
|
475
948
|
const existing = store[key] || null;
|
|
476
949
|
if (
|
|
@@ -511,7 +984,7 @@ function checkOrRecordTrust(
|
|
|
511
984
|
key,
|
|
512
985
|
};
|
|
513
986
|
},
|
|
514
|
-
|
|
987
|
+
options,
|
|
515
988
|
);
|
|
516
989
|
}
|
|
517
990
|
|
|
@@ -523,6 +996,9 @@ export function prepareMcpStdioExecutableIdentity({
|
|
|
523
996
|
env = process.env,
|
|
524
997
|
retrust = false,
|
|
525
998
|
storePath: explicitStorePath,
|
|
999
|
+
witnessPath: explicitWitnessPath,
|
|
1000
|
+
materializationRoot,
|
|
1001
|
+
materializationIndexPath,
|
|
526
1002
|
}) {
|
|
527
1003
|
const approvalRecord = resolveMcpStdioExecutionApproval(approval);
|
|
528
1004
|
if (!approvalRecord) {
|
|
@@ -531,16 +1007,52 @@ export function prepareMcpStdioExecutableIdentity({
|
|
|
531
1007
|
`MCP stdio executable identity for "${serverName}" requires a valid execution approval`,
|
|
532
1008
|
);
|
|
533
1009
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
1010
|
+
let materialization = null;
|
|
1011
|
+
let effectiveCommand = config.command;
|
|
1012
|
+
let effectiveArgs = config.args || [];
|
|
1013
|
+
if (DYNAMIC_LAUNCHERS.has(executableBasename(config.command))) {
|
|
1014
|
+
try {
|
|
1015
|
+
materialization = resolveMcpStdioPackageMaterialization({
|
|
1016
|
+
approvalRecord,
|
|
1017
|
+
...(materializationRoot ? { root: materializationRoot } : {}),
|
|
1018
|
+
...(materializationIndexPath
|
|
1019
|
+
? { indexPath: materializationIndexPath }
|
|
1020
|
+
: {}),
|
|
1021
|
+
});
|
|
1022
|
+
effectiveCommand = materialization.command;
|
|
1023
|
+
effectiveArgs = [...materialization.args];
|
|
1024
|
+
} catch (cause) {
|
|
1025
|
+
if (cause?.code === MCP_STDIO_PACKAGE_MATERIALIZATION_REQUIRED_CODE) {
|
|
1026
|
+
throw identityError(
|
|
1027
|
+
MCP_STDIO_DYNAMIC_LAUNCHER_UNPINNED_CODE,
|
|
1028
|
+
cause.message,
|
|
1029
|
+
{ cause },
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
throw cause;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const attestation = materializedAttestation(
|
|
1036
|
+
attestMcpStdioExecutableIdentity({
|
|
1037
|
+
command: effectiveCommand,
|
|
1038
|
+
args: effectiveArgs,
|
|
1039
|
+
env: config.env || {},
|
|
1040
|
+
cwd,
|
|
1041
|
+
}),
|
|
1042
|
+
materialization,
|
|
1043
|
+
);
|
|
1044
|
+
const launchEnv = materializeLaunchEnvironment(env, config.env || {});
|
|
540
1045
|
const trust = checkOrRecordTrust(serverName, approvalRecord, attestation, {
|
|
541
1046
|
env,
|
|
542
1047
|
retrust,
|
|
543
1048
|
...(explicitStorePath ? { storePath: explicitStorePath } : {}),
|
|
1049
|
+
...(explicitWitnessPath ? { witnessPath: explicitWitnessPath } : {}),
|
|
1050
|
+
});
|
|
1051
|
+
const sandboxExecutionContract = issueCapsuleSandboxExecutionContract({
|
|
1052
|
+
serverName,
|
|
1053
|
+
config,
|
|
1054
|
+
attestation,
|
|
1055
|
+
materialization,
|
|
544
1056
|
});
|
|
545
1057
|
const authority = Object.freeze({});
|
|
546
1058
|
issuedLaunchAuthorities.set(
|
|
@@ -550,6 +1062,7 @@ export function prepareMcpStdioExecutableIdentity({
|
|
|
550
1062
|
args: Object.freeze([...attestation.launchArgs]),
|
|
551
1063
|
identity: attestation.identity,
|
|
552
1064
|
identityDigest: attestation.identityDigest,
|
|
1065
|
+
materialization,
|
|
553
1066
|
}),
|
|
554
1067
|
);
|
|
555
1068
|
return Object.freeze({
|
|
@@ -557,6 +1070,12 @@ export function prepareMcpStdioExecutableIdentity({
|
|
|
557
1070
|
args: Object.freeze([...attestation.launchArgs]),
|
|
558
1071
|
identity: attestation.identity,
|
|
559
1072
|
identityDigest: attestation.identityDigest,
|
|
1073
|
+
env: launchEnv,
|
|
1074
|
+
workingDirectory:
|
|
1075
|
+
sandboxExecutionContract?.workingDirectory ||
|
|
1076
|
+
materialization?.capsuleRoot ||
|
|
1077
|
+
cwd,
|
|
1078
|
+
...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
|
|
560
1079
|
authority,
|
|
561
1080
|
trustStatus: trust.status,
|
|
562
1081
|
});
|
|
@@ -564,7 +1083,7 @@ export function prepareMcpStdioExecutableIdentity({
|
|
|
564
1083
|
|
|
565
1084
|
export function consumeMcpStdioExecutableIdentityAuthority(
|
|
566
1085
|
authority,
|
|
567
|
-
{ command, args = [] },
|
|
1086
|
+
{ command, args = [], env = {} },
|
|
568
1087
|
) {
|
|
569
1088
|
const issued = issuedLaunchAuthorities.get(authority);
|
|
570
1089
|
if (!issued) {
|
|
@@ -574,6 +1093,7 @@ export function consumeMcpStdioExecutableIdentityAuthority(
|
|
|
574
1093
|
);
|
|
575
1094
|
}
|
|
576
1095
|
issuedLaunchAuthorities.delete(authority);
|
|
1096
|
+
assertMcpStdioExecutableEnvironmentSafe(env);
|
|
577
1097
|
if (
|
|
578
1098
|
command !== issued.command ||
|
|
579
1099
|
JSON.stringify(args) !== JSON.stringify(issued.args)
|
|
@@ -583,11 +1103,26 @@ export function consumeMcpStdioExecutableIdentityAuthority(
|
|
|
583
1103
|
"MCP stdio launch changed after executable identity approval",
|
|
584
1104
|
);
|
|
585
1105
|
}
|
|
586
|
-
|
|
1106
|
+
let current = attestMcpStdioExecutableIdentity({
|
|
587
1107
|
command,
|
|
588
1108
|
args,
|
|
589
1109
|
cwd: process.cwd(),
|
|
590
1110
|
});
|
|
1111
|
+
if (issued.materialization) {
|
|
1112
|
+
const verified = reattestMcpStdioPackageMaterialization(
|
|
1113
|
+
issued.materialization,
|
|
1114
|
+
);
|
|
1115
|
+
if (
|
|
1116
|
+
JSON.stringify(verified.identity) !==
|
|
1117
|
+
JSON.stringify(issued.materialization.identity)
|
|
1118
|
+
) {
|
|
1119
|
+
throw identityError(
|
|
1120
|
+
MCP_STDIO_EXECUTABLE_CHANGED_CODE,
|
|
1121
|
+
"MCP stdio materialized package identity changed before spawn",
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
current = materializedAttestation(current, issued.materialization);
|
|
1125
|
+
}
|
|
591
1126
|
if (current.identityDigest !== issued.identityDigest) {
|
|
592
1127
|
throw identityError(
|
|
593
1128
|
MCP_STDIO_EXECUTABLE_CHANGED_CODE,
|