chainlesschain 0.163.7 → 0.164.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/package.json +5 -1
- package/scripts/macos-mcp-launcher-build.mjs +317 -0
- package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
- package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
- package/src/assets/web-panel/assets/{AIOps-axqQfwv8.js → AIOps-D2MEn7fI.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DV-Ro0By.js → ActionButton-lCW_4Gf0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bt72WEVu.js → Analytics-Bs7i-nsS.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BtepG55E.js → AppLayout-CK7B6S_j.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-B7KaJ7S_.js → Artifacts-C8ILjpyo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-FNFaUg7A.js → Audit-Bb9cNaWc.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DFb6J2wQ.js → BackgroundAgents-sC9ooXIf.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CC7YBhyj.js → Backup-D-DrsplT.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-RkgTNVtj.js → BaseInput-uCNZpwSK.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-hHEmGZy-.js → Chat-CsDK1uE9.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CAP9YhHv.js → ChatBubbleRenderer-OqSDt50w.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-z5wXOQP1.js → Checkbox-JhTXM1-K.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DVaDJnxy.js → Codegen-BbzZLnyD.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CJmaRDR5.js → Col-D4VW4m4j.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DF29_2bI.js → Community-BXrYawxV.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DtkQkBzn.js → Compact-DCihGy1u.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-xoHDxp7I.js → Compliance-DlyZ-lk8.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BUm-R7ht.js → Cowork-DvGZrhbO.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Cgl14ZkB.js → Cron-DUncZi8h.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BNcbmoqh.js → Crosschain-C_hLuUcm.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Bwja0Zo9.js → DID-b6YSUqxr.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Bk5XjEdJ.js → Dashboard-uvM15Cfb.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Ci36Oitq.js → Dropdown-D1-Nd4qY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer--U04ZN4q.js → EmailListRenderer-8ApvL6Zc.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-B2gNPR2s.js → FamilyGuardDashboard-LYJt8811.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C4KghYn7.js → Federation-BbI3HGpv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-QL7x_Q_0.js → FormItemContext-DSoEKuFu.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-F3Ryltwm.js → GenericCardRenderer-B0S9OWd5.js} +1 -1
- package/src/assets/web-panel/assets/{Git-2HassK5k.js → Git-DIs9Zhf3.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DoBIp7oe.js → Governance-jzZXrxcl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference--W01QhZ-.js → Inference-JUPjf2ZI.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DmvwqGpT.js → KnowledgeGraph-DxnfJvlP.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BBJ4yAel.js → Logs-iXiz8BEE.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BLCZMzhJ.js → MarkdownRenderer-GmB6DOr6.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D5lNngSI.js → Marketplace-Y3Xch312.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CkEVOMKH.js → McpTools-D7kGGbO7.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-B1Ow1xVE.js → Memory-Do_hVxzh.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-SgYbUfb6.js → MobileBridge-CtrR0Tas.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-DcTXSSma.js → Mtc-qo5QCTGd.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Dt3WG4oj.js → MtcAudit-cq5mnjYj.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-B9B38UZx.js → Multisig-Bq0MAQHw.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-B0S4GGvG.js → NLProgramming-DdWE6Vi2.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DZ5U58Ng.js → Notes-BxMBFcIp.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-PjL5ZW34.js → NotificationSettings-RTrAcbUe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DrAX2ESt.js → OrderTableRenderer-DJbpGqzT.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Cv1GYs-c.js → Organization-SQzIPdqC.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Bbkp_PL3.js → Overflow-02yECh22.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BD8QUgoZ.js → P2P-BNg4pNCM.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BO-2PeaB.js → PdhVaultBrowser-BxPJZsD3.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D_OPVa02.js → Permissions-CLnDnPAz.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-mh-pEmaB.js → PersonalDataHub-C5m0AjGf.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-6JC2EDdb.js → Pipeline-VwOf5Dhv.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CUshzUAG.js → Privacy-CC0W2hZd.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Cs2Aalri.js → ProjectInit-Dz893Bob.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-1ak49tcj.js → ProjectSettings-CxQkvl0k.js} +2 -2
- package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CXXxvafH.js → Providers-DeB9IkxK.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-PTcDsF58.js → QrScannerModal-OCeoaswn.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CDwucrJw.js → QuickAsk-DenotaAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CJlLVRXR.js → Recommend-Cies_jjI.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DlN-ETsO.js → RemoteSession-Dxzl5n2T.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DI23LR6m.js → Reputation-CW0mvxgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Cl7UQMrM.js → Row-SEd32qZU.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CexHNfh8.js → RssFeed-BSwnJ3Lm.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BneBWvRN.js → Search-xe2m8bkC.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DX7Phgrp.js → Security-Mq2F9Nro.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BWXO8Mte.js → Services-BKGw2V3P.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DCyOjKPJ.js → Skeleton-DW95-Rxf.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CXqERsTB.js → Skills-CHgChCv_.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-bhbrbz8E.js → Sla-tKh6k3R7.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-3IPU0HBZ.js → SpeechSettings-D6UL3dA5.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DBNTQLbM.js → SyncSettings-D0Fw8fVL.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-quP0bkN-.js → Tasks-20x1GB3D.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BouWmgho.js → Templates-DJ-lts-a.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-qv0D9s7z.js → Tenant-BFdGpbs7.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BxvbE8BB.js → Terminal-Xpx61vHL.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D-IeLnJ2.js → TimelineRenderer-C6UIvyZQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CFMKKyVG.js → Tokens-Dellq08f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-L8as3UuJ.js → Trigger-VKw8d0J-.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-MxmA4VZG.js → Trust-DNftdKT5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C0VvJYRi.js → UkeySign-DX9VAKJI.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Bf5QZj8N.js → VideoEditing-B2h3jO8q.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BIJ9riRz.js → Wallet-CjGkof7f.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B8p9Mhkw.js → WebAuthn-2Zfcu55P.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BVsuaPen.js → WorkflowEditor-B9eZ8UxZ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BfkjoKpV.js → chat-CQNfD-YO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DeAlsWK8.js → colors-D4usyFT2.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cs97bz5j.js → compact-item-s4TpjvjX.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BqbG60LH.js → createContext-BRCSvYxj.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-i_RY0b1X.js → hasIn-AdkUbkV-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOVkf1K3.js → index--00U6vXZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-3tEpTc.js → index-B2V-eKfK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2B4VPyP.js → index-B4JJCfQ2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkBi5TmC.js → index-BIoG5yYf.js} +1 -1
- package/src/assets/web-panel/assets/{index-79V_zv1s.js → index-BMcimU-2.js} +1 -1
- package/src/assets/web-panel/assets/{index-U4f5izHz.js → index-BPhkFZ1I.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiSkx4UB.js → index-BQ3DXBtD.js} +3 -3
- package/src/assets/web-panel/assets/{index-dIHr4B_u.js → index-Bad4VNvz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIG8fQ2_.js → index-BgiXR_-_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqBgBpgp.js → index-BtobShro.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZxO_-E8a.js → index-Bw2MEnUk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUUrv141.js → index-C1mJtE6q.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bm7lLR4c.js → index-C484EJiS.js} +1 -1
- package/src/assets/web-panel/assets/{index-COdGA-8n.js → index-C5KAQFPQ.js} +1 -1
- package/src/assets/web-panel/assets/index-C61cnl3K.js +1 -0
- package/src/assets/web-panel/assets/{index-CED2PL_y.js → index-C7Y8Aluc.js} +1 -1
- package/src/assets/web-panel/assets/{index-YXgLng0K.js → index-CCsM_Ybx.js} +1 -1
- package/src/assets/web-panel/assets/{index-0-agYlWL.js → index-CJCFe2Qf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5UkyA3c.js → index-CJVEOa4i.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1CbOH1i.js → index-CJdQrZth.js} +1 -1
- package/src/assets/web-panel/assets/{index-s1opAL-M.js → index-CKC1t3Ic.js} +1 -1
- package/src/assets/web-panel/assets/{index-yObj1O4n.js → index-CQhAVU5t.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgsqdnRV.js → index-CYdoQsa1.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dk7-v4hP.js → index-Cduu2Hhr.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-JVTrpr.js → index-CkwiSLhU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeWoNHau.js → index-Cvyt-zhm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE2VtTlp.js → index-D2GjI2oe.js} +1 -1
- package/src/assets/web-panel/assets/index-D4XFHxBr.js +1 -0
- package/src/assets/web-panel/assets/{index-DmbfhJUP.js → index-DARsAyUp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BESLFt4q.js → index-DVKfQ0Ux.js} +1 -1
- package/src/assets/web-panel/assets/{index-PT_5sBMA.js → index-DZT36ZYq.js} +1 -1
- package/src/assets/web-panel/assets/{index-gyFpvL7l.js → index-D_XBHopw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYUVuwKt.js → index-DdCB70SS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcUXAB2s.js → index-DzRjHxEv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dk2qouAz.js → index-MMN9V6JF.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9evtvg2.js → index-RfMr79MW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuLQobzW.js → index-_IRVsV9X.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf3WCLmy.js → index-nXyk8GZ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CG3PRyAL.js → index-s_buFMEG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C9h9yKL3.js → initDefaultProps-Blmpk7HL.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BvmLsPdu.js → motion-DPBJMv1n.js} +1 -1
- package/src/assets/web-panel/assets/{move-CoL4ekU1.js → move-ButdzCM7.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C4pMy6xV.js → mtc-parser-cyRC_QCe.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DPuJfNxx.js → omit-1epSV7hz.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D0KEeaK2.js → pickAttrs-DuPY8xJs.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BpkyINxt.js → placementArrow-6XZqarhe.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DQI_wOq8.js → responsiveObserve-B40pXBWM.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DYfdDiRg.js → slide-jbZ9Wu4n.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B5dtvb0D.js → statusUtils-BKQQcVy4.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CF310mSj.js → styleChecker-NiKfWjUT.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D1Qp4BLp.js → useFlexGapSupport-bQUCvPeb.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C7_iyvJK.js → useFs-CUGji7ph.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DZUt_fIm.js → usePersonalDataHub-4veyN2N2.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D3dsi0lZ.js → vnode-1-nLug-Z.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-WdbOlvdQ.js → zoom-dhCeRDMo.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +6 -6
- package/src/commands/automation.js +103 -1
- package/src/commands/cowork.js +174 -19
- package/src/commands/permissions.js +240 -16
- package/src/commands/plugin.js +1293 -56
- package/src/commands/session-location.js +276 -0
- package/src/commands/session.js +2 -0
- package/src/commands/team-merge-review.js +937 -0
- package/src/commands/team.js +2 -0
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/action-protocol.js +35 -10
- package/src/gateways/ws/remote-session-protocol.js +24 -1
- package/src/gateways/ws/ws-agent-handler.js +12 -0
- package/src/gateways/ws/ws-server.js +48 -12
- package/src/harness/jsonl-session-store.js +341 -24
- package/src/harness/mcp-client.js +8 -1
- package/src/harness/remote-session-audit.js +1 -0
- package/src/harness/remote-session-crypto.js +210 -2
- package/src/harness/remote-session-registry.js +159 -6
- package/src/harness/session-list-index.js +233 -14
- package/src/lib/agent-authority.js +6 -0
- package/src/lib/agent-schedule-store.js +139 -28
- package/src/lib/agent-team/team-merge-review-store.js +765 -0
- package/src/lib/agent-team/team-merge-review-transaction.js +1223 -0
- package/src/lib/agent-team/team-merge-review.js +1847 -0
- package/src/lib/automation-center-incidents.js +314 -0
- package/src/lib/automation-center-runtime.js +448 -0
- package/src/lib/automation-center.js +182 -38
- package/src/lib/automation-engine.js +41 -0
- package/src/lib/automation-execution-authority.js +241 -12
- package/src/lib/automation-execution-incident.js +737 -0
- package/src/lib/background-agent-keeper-client.js +248 -0
- package/src/lib/background-agent-keeper-protocol.js +147 -0
- package/src/lib/background-agent-supervisor.js +520 -40
- package/src/lib/background-interaction-resolver.js +12 -0
- package/src/lib/background-turn-bootstrap-protocol.js +126 -0
- package/src/lib/cowork-cron.js +85 -14
- package/src/lib/cowork-workflow.js +336 -16
- package/src/lib/dynamic-workflow-facade.js +648 -0
- package/src/lib/execution-location-contract.js +551 -0
- package/src/lib/execution-location-runtime.js +130 -0
- package/src/lib/ide-context.js +94 -6
- package/src/lib/mcp-stdio-package-materialization.js +166 -14
- package/src/lib/packer/pkg-config-generator.js +2 -0
- package/src/lib/permission-authority.js +77 -0
- package/src/lib/permission-side-effect-center.js +495 -0
- package/src/lib/plugin-runtime/install.js +514 -13
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
- package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
- package/src/lib/plugin-security.js +60 -3
- package/src/lib/process-execution-broker/index.js +1095 -57
- package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
- package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
- package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
- package/src/lib/public-ide-capability-manifest.js +3 -0
- package/src/lib/remote-approval-bridge.js +123 -17
- package/src/lib/remote-membership-authority.js +632 -0
- package/src/lib/remote-membership-coordinator.js +2409 -0
- package/src/lib/remote-membership-host-store.js +1750 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +17 -7
- package/src/lib/scheduler-kernel/automation-adapter.js +105 -11
- package/src/lib/scheduler-kernel/automation-event-adapter.js +87 -11
- package/src/lib/scheduler-kernel/contract.js +91 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +12 -1
- package/src/lib/scheduler-kernel/loop-adapter.js +7 -1
- package/src/lib/scheduler-kernel/routine-adapter.js +7 -1
- package/src/lib/scheduler-kernel/runtime-control-capabilities.js +31 -0
- package/src/lib/scheduler-kernel/runtime.js +176 -3
- package/src/lib/scheduler-kernel/store.js +1253 -45
- package/src/lib/scoped-permission-store.js +369 -0
- package/src/lib/session-anti-rollback-anchor.js +262 -5
- package/src/lib/sub-agent-context.js +4 -0
- package/src/lib/workflow-definition-contract.js +149 -0
- package/src/repl/agent-repl.js +41 -7
- package/src/repl/chat-repl.js +2 -0
- package/src/repl/clipboard-image.js +735 -16
- package/src/repl/ide-status.js +1 -1
- package/src/repl/prompt-interactions.js +59 -6
- package/src/runtime/agent-core.js +93 -3
- package/src/runtime/headless-runner.js +30 -25
- package/src/runtime/headless-stream.js +32 -25
- package/src/runtime/mcp-config.js +81 -10
- package/src/workers/background-agent-keeper.js +498 -0
- package/src/workers/background-agent-worker.js +358 -13
- package/src/workers/background-turn-bootstrap.js +143 -0
- package/src/assets/web-panel/assets/MobileProjects-BWdOvQAj.js +0 -1
- package/src/assets/web-panel/assets/Projects-BV4oApsm.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +0 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +0 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +0 -1
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
* - macOS: Seatbelt sandbox-exec profiles
|
|
6
6
|
* - Linux: prlimit resource-limit wrapper, plus a narrow bubblewrap backend
|
|
7
7
|
* for an attested direct strict Plugin Node bin or a narrow static/dynamic
|
|
8
|
-
* ELF native bin. Dynamic acceptance
|
|
9
|
-
* DT_NEEDED
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* ELF native bin. Dynamic acceptance recursively parses PT_INTERP and every
|
|
9
|
+
* startup DT_NEEDED edge against the exact descriptor-bound system graph.
|
|
10
|
+
* Dynamic native launches additionally use a read-only final mount namespace
|
|
11
|
+
* with no procfs, devfs, or writable scratch: every pathname-visible regular
|
|
12
|
+
* file is descriptor-pinned, hashed, and policy-bound. This closes the ELF
|
|
13
|
+
* loader/dlopen pathname surface, but deliberately does not claim to prevent
|
|
14
|
+
* anonymous JIT code or a custom in-process ELF loader.
|
|
12
15
|
* - Windows: Win32 Job Object + restricted-token wrapper, with an attested
|
|
13
16
|
* zero-capability AppContainer for explicit filesystem/network boundaries
|
|
14
17
|
*
|
|
@@ -30,6 +33,27 @@ import {
|
|
|
30
33
|
applyLinuxGenericWorkspaceSandbox,
|
|
31
34
|
isLinuxGenericWorkspaceContract,
|
|
32
35
|
} from "./linux-generic-bwrap-runtime.js";
|
|
36
|
+
import {
|
|
37
|
+
buildLinuxBwrapDescriptorScrubbedLaunch,
|
|
38
|
+
LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH,
|
|
39
|
+
linuxBwrapDescriptorScrubberPolicyBinding,
|
|
40
|
+
} from "./linux-bwrap-descriptor-launch.js";
|
|
41
|
+
import {
|
|
42
|
+
MCP_STDIO_FD_ENTRY_BOOTSTRAP,
|
|
43
|
+
MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256,
|
|
44
|
+
MCP_STDIO_MACOS_GATED_ENTRY_BOOTSTRAP_SHA256,
|
|
45
|
+
} from "./mcp-fd-entry-bootstrap.js";
|
|
46
|
+
import {
|
|
47
|
+
MACOS_MCP_LAUNCHER_INPUTS,
|
|
48
|
+
macosMcpLauncherPolicyDigest,
|
|
49
|
+
verifyMacosMcpLauncherInstallContract,
|
|
50
|
+
} from "./macos-mcp-launcher-contract.js";
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
MCP_STDIO_FD_ENTRY_BOOTSTRAP,
|
|
54
|
+
MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256,
|
|
55
|
+
MCP_STDIO_MACOS_GATED_ENTRY_BOOTSTRAP_SHA256,
|
|
56
|
+
} from "./mcp-fd-entry-bootstrap.js";
|
|
33
57
|
|
|
34
58
|
/**
|
|
35
59
|
* Stable boundary identifiers shared by broker policies, platform plans, and
|
|
@@ -161,6 +185,8 @@ const LINUX_BWRAP_BACKEND = "linux-bwrap";
|
|
|
161
185
|
const LINUX_BWRAP_NODE_PROBE_SENTINEL = "chainless-linux-bwrap-plugin-node-v1";
|
|
162
186
|
const LINUX_BWRAP_CHILD_RUNTIME_PROBE_SENTINEL =
|
|
163
187
|
"chainless-linux-bwrap-child-runtime-v1";
|
|
188
|
+
const LINUX_BWRAP_NATIVE_RUNTIME_CLOSURE_PROBE_SENTINEL =
|
|
189
|
+
"chainless-linux-bwrap-native-runtime-pathname-closure-v1";
|
|
164
190
|
const LINUX_BWRAP_CHILD_RUNTIME_PROBE_FAILURE_STATUS = 86;
|
|
165
191
|
const LINUX_BWRAP_MAX_PLUGIN_ENTRIES = 512;
|
|
166
192
|
const LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES = 256;
|
|
@@ -178,15 +204,12 @@ const LINUX_ENTRY_SNAPSHOT_SOURCE_MODE = 0o400;
|
|
|
178
204
|
const LINUX_EXECUTABLE_SNAPSHOT_SOURCE_MODE = 0o500;
|
|
179
205
|
const LINUX_MCP_CAPSULE_BACKEND = "linux-fd-code-snapshot";
|
|
180
206
|
const LINUX_MCP_RUNTIME_SNAPSHOT_SCOPE = "mcp-node-runtime-executable";
|
|
207
|
+
const LINUX_PLUGIN_RUNTIME_SNAPSHOT_SCOPE =
|
|
208
|
+
"plugin-probe-node-runtime-executable";
|
|
181
209
|
const LINUX_MCP_CAPSULE_SNAPSHOT_MECHANISM =
|
|
182
210
|
"verified-o_tmpfile-copy-inherited-fd-module-compile-v1";
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
'const fs=require("node:fs");const Module=require("node:module");const filename="/chainlesschain/mcp-capsule.cjs";const source=fs.readFileSync(4,"utf8");process.argv.splice(1,0,filename);const target=new Module(filename,module);target.filename=filename;target.paths=[];target._compile(source,filename);';
|
|
186
|
-
export const MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256 = crypto
|
|
187
|
-
.createHash("sha256")
|
|
188
|
-
.update(MCP_STDIO_FD_ENTRY_BOOTSTRAP)
|
|
189
|
-
.digest("hex");
|
|
211
|
+
const MACOS_MCP_CAPSULE_CANDIDATE_BACKEND = "macos-fd-code-snapshot";
|
|
212
|
+
const MACOS_MCP_CAPSULE_BACKEND = MACOS_MCP_LAUNCHER_INPUTS.protocol.backend;
|
|
190
213
|
const LINUX_NODE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-source";
|
|
191
214
|
const LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-executable";
|
|
192
215
|
const LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE = "all-pinned-plugin-regular-files";
|
|
@@ -199,6 +222,9 @@ const LINUX_ELF64_DYNAMIC_ENTRY_BYTES = 16;
|
|
|
199
222
|
const LINUX_ELF_MAX_PROGRAM_HEADERS = 128;
|
|
200
223
|
const LINUX_ELF_MAX_DYNAMIC_ENTRIES = 4096;
|
|
201
224
|
const LINUX_ELF_MAX_NEEDED_ENTRIES = 128;
|
|
225
|
+
const LINUX_ELF_MAX_INITIAL_CLOSURE_FILES = 256;
|
|
226
|
+
const LINUX_ELF_MAX_INITIAL_CLOSURE_EDGES = 1024;
|
|
227
|
+
const LINUX_ELF_MAX_INITIAL_CLOSURE_BYTES = 512 * 1024 * 1024;
|
|
202
228
|
const LINUX_ELF_MAX_INTERPRETER_BYTES = 4096;
|
|
203
229
|
const LINUX_ELF_MAX_STRING_TABLE_BYTES = 1024 * 1024;
|
|
204
230
|
const LINUX_AUXV_ENTRY_BYTES = 16;
|
|
@@ -217,6 +243,7 @@ const LINUX_ELF_DYNAMIC_NULL = 0n;
|
|
|
217
243
|
const LINUX_ELF_DYNAMIC_NEEDED = 1n;
|
|
218
244
|
const LINUX_ELF_DYNAMIC_STRTAB = 5n;
|
|
219
245
|
const LINUX_ELF_DYNAMIC_STRSZ = 10n;
|
|
246
|
+
const LINUX_ELF_DYNAMIC_SONAME = 14n;
|
|
220
247
|
const LINUX_ELF_DYNAMIC_RPATH = 15n;
|
|
221
248
|
const LINUX_ELF_DYNAMIC_TEXTREL = 22n;
|
|
222
249
|
const LINUX_ELF_DYNAMIC_FLAGS = 30n;
|
|
@@ -229,10 +256,23 @@ const LINUX_ELF_DYNAMIC_AUXILIARY = 0x7ffffffdn;
|
|
|
229
256
|
const LINUX_ELF_DYNAMIC_FILTER = 0x7fffffffn;
|
|
230
257
|
const LINUX_ELF_DYNAMIC_FLAG_TEXTREL = 0x4n;
|
|
231
258
|
const LINUX_ELF_DYNAMIC_FLAG_PIE = 0x08000000n;
|
|
232
|
-
const
|
|
233
|
-
"initial-pt_interp-and-
|
|
234
|
-
const
|
|
235
|
-
"parsed-elf-
|
|
259
|
+
const LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_SCOPE =
|
|
260
|
+
"initial-pt_interp-and-recursive-dt_needed-attested-system-graph";
|
|
261
|
+
const LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_MECHANISM =
|
|
262
|
+
"recursive-parsed-elf-system-graph-to-attested-runtime-fds-v1";
|
|
263
|
+
const LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_SCOPE =
|
|
264
|
+
"all-pathname-visible-regular-files-in-read-only-bwrap-namespace";
|
|
265
|
+
// In addition to the immutable mount set, the dynamic-only seccomp program
|
|
266
|
+
// blocks every descriptor-acquisition syscall that could import a loader input
|
|
267
|
+
// through SCM_RIGHTS, another process, or a filesystem handle. It also blocks
|
|
268
|
+
// mount/user namespace mutation: otherwise an untrusted target could create a
|
|
269
|
+
// nested user namespace, mount a writable tmpfs over /run, and dlopen a drop-in.
|
|
270
|
+
// This remains a pathname-loader claim: anonymous JIT/custom in-process loaders
|
|
271
|
+
// are excluded.
|
|
272
|
+
const LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MECHANISM =
|
|
273
|
+
"descriptor-pinned-hashed-ro-mount-set-plus-loader-fd-and-namespace-mutation-seccomp-v2";
|
|
274
|
+
const LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MAX_FILES = 512;
|
|
275
|
+
const LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MAX_BYTES = 1024 * 1024 * 1024;
|
|
236
276
|
const LINUX_ELF_MACHINES = Object.freeze({
|
|
237
277
|
x64: 62,
|
|
238
278
|
arm64: 183,
|
|
@@ -242,11 +282,32 @@ const LINUX_ELF_MACHINES = Object.freeze({
|
|
|
242
282
|
// x64, arm64, and riscv64 use this value; unsupported seccomp architectures
|
|
243
283
|
// already fail closed before the anonymous filter is created.
|
|
244
284
|
const LINUX_O_TMPFILE = 0x410000;
|
|
285
|
+
const LINUX_CLONE_NEWNS = 0x00020000;
|
|
286
|
+
const LINUX_CLONE_NEWUSER = 0x10000000;
|
|
287
|
+
const LINUX_NAMESPACE_CLONE_FLAGS = LINUX_CLONE_NEWNS | LINUX_CLONE_NEWUSER;
|
|
245
288
|
const LINUX_SECCOMP_FILTERS = Object.freeze({
|
|
246
289
|
x64: Object.freeze({
|
|
247
290
|
auditArch: 0xc000003e,
|
|
248
291
|
socketSyscall: 41,
|
|
249
292
|
socketpairSyscall: 53,
|
|
293
|
+
recvmsgSyscall: 47,
|
|
294
|
+
recvmmsgSyscall: 299,
|
|
295
|
+
openByHandleAtSyscall: 304,
|
|
296
|
+
pidfdGetfdSyscall: 438,
|
|
297
|
+
cloneSyscall: 56,
|
|
298
|
+
clone3Syscall: 435,
|
|
299
|
+
unshareSyscall: 272,
|
|
300
|
+
setnsSyscall: 308,
|
|
301
|
+
mountSyscall: 165,
|
|
302
|
+
umount2Syscall: 166,
|
|
303
|
+
pivotRootSyscall: 155,
|
|
304
|
+
openTreeSyscall: 428,
|
|
305
|
+
moveMountSyscall: 429,
|
|
306
|
+
fsopenSyscall: 430,
|
|
307
|
+
fsconfigSyscall: 431,
|
|
308
|
+
fsmountSyscall: 432,
|
|
309
|
+
fspickSyscall: 433,
|
|
310
|
+
mountSetattrSyscall: 442,
|
|
250
311
|
ioUringSetupSyscall: 425,
|
|
251
312
|
x32SyscallBit: 0x40000000,
|
|
252
313
|
}),
|
|
@@ -254,12 +315,48 @@ const LINUX_SECCOMP_FILTERS = Object.freeze({
|
|
|
254
315
|
auditArch: 0xc00000b7,
|
|
255
316
|
socketSyscall: 198,
|
|
256
317
|
socketpairSyscall: 199,
|
|
318
|
+
recvmsgSyscall: 212,
|
|
319
|
+
recvmmsgSyscall: 243,
|
|
320
|
+
openByHandleAtSyscall: 265,
|
|
321
|
+
pidfdGetfdSyscall: 438,
|
|
322
|
+
cloneSyscall: 220,
|
|
323
|
+
clone3Syscall: 435,
|
|
324
|
+
unshareSyscall: 97,
|
|
325
|
+
setnsSyscall: 268,
|
|
326
|
+
mountSyscall: 40,
|
|
327
|
+
umount2Syscall: 39,
|
|
328
|
+
pivotRootSyscall: 41,
|
|
329
|
+
openTreeSyscall: 428,
|
|
330
|
+
moveMountSyscall: 429,
|
|
331
|
+
fsopenSyscall: 430,
|
|
332
|
+
fsconfigSyscall: 431,
|
|
333
|
+
fsmountSyscall: 432,
|
|
334
|
+
fspickSyscall: 433,
|
|
335
|
+
mountSetattrSyscall: 442,
|
|
257
336
|
ioUringSetupSyscall: 425,
|
|
258
337
|
}),
|
|
259
338
|
riscv64: Object.freeze({
|
|
260
339
|
auditArch: 0xc00000f3,
|
|
261
340
|
socketSyscall: 198,
|
|
262
341
|
socketpairSyscall: 199,
|
|
342
|
+
recvmsgSyscall: 212,
|
|
343
|
+
recvmmsgSyscall: 243,
|
|
344
|
+
openByHandleAtSyscall: 265,
|
|
345
|
+
pidfdGetfdSyscall: 438,
|
|
346
|
+
cloneSyscall: 220,
|
|
347
|
+
clone3Syscall: 435,
|
|
348
|
+
unshareSyscall: 97,
|
|
349
|
+
setnsSyscall: 268,
|
|
350
|
+
mountSyscall: 40,
|
|
351
|
+
umount2Syscall: 39,
|
|
352
|
+
pivotRootSyscall: 41,
|
|
353
|
+
openTreeSyscall: 428,
|
|
354
|
+
moveMountSyscall: 429,
|
|
355
|
+
fsopenSyscall: 430,
|
|
356
|
+
fsconfigSyscall: 431,
|
|
357
|
+
fsmountSyscall: 432,
|
|
358
|
+
fspickSyscall: 433,
|
|
359
|
+
mountSetattrSyscall: 442,
|
|
263
360
|
ioUringSetupSyscall: 425,
|
|
264
361
|
}),
|
|
265
362
|
});
|
|
@@ -278,6 +375,8 @@ const LINUX_LOCAL_FILESYSTEM_TYPES = new Set([
|
|
|
278
375
|
const DEFAULT_RUNTIME = Object.freeze({
|
|
279
376
|
platform: os.platform(),
|
|
280
377
|
arch: process.arch,
|
|
378
|
+
pkg: process.pkg || null,
|
|
379
|
+
env: process.env,
|
|
281
380
|
fs,
|
|
282
381
|
tmpdir: () => os.tmpdir(),
|
|
283
382
|
homedir: () => os.homedir(),
|
|
@@ -292,6 +391,8 @@ const DEFAULT_RUNTIME = Object.freeze({
|
|
|
292
391
|
sleepSync: (milliseconds) =>
|
|
293
392
|
Atomics.wait(WINDOWS_IDENTITY_WAIT, 0, 0, milliseconds),
|
|
294
393
|
spawnSync: nativeSpawnSync,
|
|
394
|
+
getuid: () => process.getuid?.() ?? null,
|
|
395
|
+
getgid: () => process.getgid?.() ?? null,
|
|
295
396
|
warn: (message) => process.emitWarning(message),
|
|
296
397
|
});
|
|
297
398
|
|
|
@@ -301,6 +402,7 @@ const windowsTemporaryCleanupBacklog = new Set();
|
|
|
301
402
|
let windowsTemporaryCleanupRetryTimer = null;
|
|
302
403
|
const windowsAppContainerCleanupBacklog = new Set();
|
|
303
404
|
const issuedWindowsMcpCodeSnapshotPlans = new WeakMap();
|
|
405
|
+
const issuedMacMcpCodeSnapshotPlans = new WeakMap();
|
|
304
406
|
// Only the unified production adapter can register a Windows MCP launch
|
|
305
407
|
// capability. Direct callers of the exported platform-specific helper (and
|
|
306
408
|
// callers that inject a test/runtime facade into applySandbox) may exercise
|
|
@@ -308,6 +410,7 @@ const issuedWindowsMcpCodeSnapshotPlans = new WeakMap();
|
|
|
308
410
|
const WINDOWS_MCP_CODE_SNAPSHOT_ISSUER = Symbol(
|
|
309
411
|
"windows-mcp-code-snapshot-issuer",
|
|
310
412
|
);
|
|
413
|
+
const MACOS_MCP_CODE_SNAPSHOT_ISSUER = Symbol("macos-mcp-code-snapshot-issuer");
|
|
311
414
|
// Each AppContainer retry starts a synchronous, digest-attested PowerShell
|
|
312
415
|
// helper. Bound automatic retries so a permanently unsupported Windows host
|
|
313
416
|
// cannot repeatedly block a long-lived CLI; explicit cleanup/reset and process
|
|
@@ -693,11 +796,55 @@ function normalizeSandboxRequest(profileOrRequest, explicitRequest) {
|
|
|
693
796
|
* Materialize the requested POSIX shell before adding either wrapper. The
|
|
694
797
|
* sandbox executable itself is always spawned directly.
|
|
695
798
|
*/
|
|
799
|
+
function snapshotWrappedInvocationArgs(args) {
|
|
800
|
+
if (args === null || args === undefined) return Object.freeze([]);
|
|
801
|
+
try {
|
|
802
|
+
if (
|
|
803
|
+
!Array.isArray(args) ||
|
|
804
|
+
Object.getPrototypeOf(args) !== Array.prototype
|
|
805
|
+
) {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
const descriptors = Object.getOwnPropertyDescriptors(args);
|
|
809
|
+
const lengthDescriptor = descriptors.length;
|
|
810
|
+
if (
|
|
811
|
+
!lengthDescriptor ||
|
|
812
|
+
!("value" in lengthDescriptor) ||
|
|
813
|
+
!Number.isSafeInteger(lengthDescriptor.value) ||
|
|
814
|
+
lengthDescriptor.value < 0 ||
|
|
815
|
+
Reflect.ownKeys(descriptors).length !== lengthDescriptor.value + 1
|
|
816
|
+
) {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
const snapshot = [];
|
|
820
|
+
for (let index = 0; index < lengthDescriptor.value; index += 1) {
|
|
821
|
+
const descriptor = descriptors[String(index)];
|
|
822
|
+
if (!descriptor || !("value" in descriptor)) return null;
|
|
823
|
+
snapshot.push(descriptor.value);
|
|
824
|
+
}
|
|
825
|
+
return Object.freeze(snapshot);
|
|
826
|
+
} catch {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
696
831
|
function normalizeWrappedInvocation(command, args, spawnOpts, platform) {
|
|
832
|
+
const argsSnapshot = snapshotWrappedInvocationArgs(args);
|
|
833
|
+
if (!argsSnapshot) {
|
|
834
|
+
return {
|
|
835
|
+
command,
|
|
836
|
+
args: [],
|
|
837
|
+
inputArgs: [],
|
|
838
|
+
argumentsValid: false,
|
|
839
|
+
options: { ...(spawnOpts || {}) },
|
|
840
|
+
};
|
|
841
|
+
}
|
|
697
842
|
if (!spawnOpts?.shell) {
|
|
698
843
|
return {
|
|
699
844
|
command,
|
|
700
|
-
args: [...
|
|
845
|
+
args: [...argsSnapshot],
|
|
846
|
+
inputArgs: argsSnapshot,
|
|
847
|
+
argumentsValid: true,
|
|
701
848
|
options: { ...(spawnOpts || {}) },
|
|
702
849
|
};
|
|
703
850
|
}
|
|
@@ -707,20 +854,24 @@ function normalizeWrappedInvocation(command, args, spawnOpts, platform) {
|
|
|
707
854
|
typeof spawnOpts.shell === "string"
|
|
708
855
|
? spawnOpts.shell
|
|
709
856
|
: process.env.ComSpec || "C:\\Windows\\System32\\cmd.exe";
|
|
710
|
-
const shellCommand = [command, ...
|
|
857
|
+
const shellCommand = [command, ...argsSnapshot].map(String).join(" ");
|
|
711
858
|
return {
|
|
712
859
|
command: shell,
|
|
713
860
|
args: ["/d", "/s", "/c", shellCommand],
|
|
861
|
+
inputArgs: argsSnapshot,
|
|
862
|
+
argumentsValid: true,
|
|
714
863
|
options: { ...spawnOpts, shell: false },
|
|
715
864
|
};
|
|
716
865
|
}
|
|
717
866
|
|
|
718
867
|
const shell =
|
|
719
868
|
typeof spawnOpts.shell === "string" ? spawnOpts.shell : "/bin/sh";
|
|
720
|
-
const shellCommand = [command, ...
|
|
869
|
+
const shellCommand = [command, ...argsSnapshot].map(String).join(" ");
|
|
721
870
|
return {
|
|
722
871
|
command: shell,
|
|
723
872
|
args: ["-c", shellCommand],
|
|
873
|
+
inputArgs: argsSnapshot,
|
|
874
|
+
argumentsValid: true,
|
|
724
875
|
options: { ...spawnOpts, shell: false },
|
|
725
876
|
};
|
|
726
877
|
}
|
|
@@ -857,6 +1008,650 @@ export function generateMacSeatbeltProfile(opts = {}, runtimeOverrides = {}) {
|
|
|
857
1008
|
return lines.join("\n");
|
|
858
1009
|
}
|
|
859
1010
|
|
|
1011
|
+
function macMode(stat) {
|
|
1012
|
+
return Number(stat.mode) & 0o7777;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function macSameOpenIdentity(left, right) {
|
|
1016
|
+
return (
|
|
1017
|
+
linuxOpenStatMatches(left, right) &&
|
|
1018
|
+
String(left.uid) === String(right.uid) &&
|
|
1019
|
+
String(left.gid) === String(right.gid)
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function macFixedPathStat(runtime, candidate, options) {
|
|
1024
|
+
const resolved = linuxRealpath(runtime, candidate);
|
|
1025
|
+
const stat = runtime.fs.lstatSync(candidate);
|
|
1026
|
+
const kindMatches =
|
|
1027
|
+
options.kind === "directory" ? stat.isDirectory() : stat.isFile();
|
|
1028
|
+
if (
|
|
1029
|
+
resolved !== candidate ||
|
|
1030
|
+
stat.isSymbolicLink?.() ||
|
|
1031
|
+
!kindMatches ||
|
|
1032
|
+
Number(stat.uid) !== 0 ||
|
|
1033
|
+
Number(stat.gid) !== 0 ||
|
|
1034
|
+
(options.mode !== undefined && macMode(stat) !== options.mode) ||
|
|
1035
|
+
(options.mode === undefined && (macMode(stat) & 0o022) !== 0) ||
|
|
1036
|
+
(options.nlink !== undefined && Number(stat.nlink) !== options.nlink)
|
|
1037
|
+
) {
|
|
1038
|
+
throw new Error(options.reason || "macos_fixed_path_untrusted");
|
|
1039
|
+
}
|
|
1040
|
+
return stat;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function macOpenPinnedFile(runtime, candidate, options = {}) {
|
|
1044
|
+
let fd;
|
|
1045
|
+
try {
|
|
1046
|
+
const namedBefore = macFixedPathStat(runtime, candidate, {
|
|
1047
|
+
kind: "file",
|
|
1048
|
+
mode: options.mode,
|
|
1049
|
+
nlink: options.nlink,
|
|
1050
|
+
reason: options.reason,
|
|
1051
|
+
});
|
|
1052
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
1053
|
+
fd = runtime.fs.openSync(
|
|
1054
|
+
candidate,
|
|
1055
|
+
Number(constants.O_RDONLY) |
|
|
1056
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
1057
|
+
Number(constants.O_CLOEXEC || 0) |
|
|
1058
|
+
Number(constants.O_NONBLOCK || 0),
|
|
1059
|
+
);
|
|
1060
|
+
const before = runtime.fs.fstatSync(fd);
|
|
1061
|
+
const bytes = Number(before.size);
|
|
1062
|
+
if (
|
|
1063
|
+
!before.isFile() ||
|
|
1064
|
+
!macSameOpenIdentity(namedBefore, before) ||
|
|
1065
|
+
(options.executable === true && (Number(before.mode) & 0o111) === 0) ||
|
|
1066
|
+
!Number.isSafeInteger(bytes) ||
|
|
1067
|
+
bytes < (options.minimumBytes ?? 1) ||
|
|
1068
|
+
bytes > (options.maximumBytes ?? 16 * 1024 * 1024) ||
|
|
1069
|
+
(options.expectedBytes !== undefined && bytes !== options.expectedBytes)
|
|
1070
|
+
) {
|
|
1071
|
+
throw new Error(options.reason || "macos_fixed_file_untrusted");
|
|
1072
|
+
}
|
|
1073
|
+
const digest = hashLinuxOpenFile(runtime, fd, bytes);
|
|
1074
|
+
const after = runtime.fs.fstatSync(fd);
|
|
1075
|
+
const namedAfter = runtime.fs.lstatSync(candidate);
|
|
1076
|
+
if (
|
|
1077
|
+
!macSameOpenIdentity(before, after) ||
|
|
1078
|
+
!macSameOpenIdentity(after, namedAfter) ||
|
|
1079
|
+
linuxRealpath(runtime, candidate) !== candidate ||
|
|
1080
|
+
(options.expectedSha256 !== undefined &&
|
|
1081
|
+
digest !== options.expectedSha256)
|
|
1082
|
+
) {
|
|
1083
|
+
throw new Error(options.reason || "macos_fixed_file_changed");
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
fd,
|
|
1087
|
+
stat: after,
|
|
1088
|
+
sha256: digest,
|
|
1089
|
+
bytes,
|
|
1090
|
+
fileId: { dev: String(after.dev), ino: String(after.ino) },
|
|
1091
|
+
mtimeMs: Number(after.mtimeMs),
|
|
1092
|
+
};
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
if (fd !== undefined) {
|
|
1095
|
+
try {
|
|
1096
|
+
runtime.fs.closeSync(fd);
|
|
1097
|
+
} catch {
|
|
1098
|
+
// best effort
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
throw error;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function macReadPinnedUtf8(runtime, pin, maximumBytes) {
|
|
1106
|
+
if (pin.bytes > maximumBytes) throw new Error("macos_contract_too_large");
|
|
1107
|
+
const buffer = Buffer.alloc(pin.bytes);
|
|
1108
|
+
let offset = 0;
|
|
1109
|
+
while (offset < buffer.length) {
|
|
1110
|
+
const read = runtime.fs.readSync(
|
|
1111
|
+
pin.fd,
|
|
1112
|
+
buffer,
|
|
1113
|
+
offset,
|
|
1114
|
+
buffer.length - offset,
|
|
1115
|
+
offset,
|
|
1116
|
+
);
|
|
1117
|
+
if (read <= 0) throw new Error("macos_contract_ended_early");
|
|
1118
|
+
offset += read;
|
|
1119
|
+
}
|
|
1120
|
+
return buffer.toString("utf8");
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function macSpawnAttestation(runtime, command, args) {
|
|
1124
|
+
const result = runtime.spawnSync(command, args, {
|
|
1125
|
+
cwd: "/",
|
|
1126
|
+
shell: false,
|
|
1127
|
+
encoding: "utf8",
|
|
1128
|
+
timeout: 15_000,
|
|
1129
|
+
env: { PATH: "/usr/bin:/bin", LANG: "C", LC_ALL: "C" },
|
|
1130
|
+
});
|
|
1131
|
+
if (result?.error || result?.signal || result?.status !== 0) {
|
|
1132
|
+
throw new Error("macos_helper_attestation_command_failed");
|
|
1133
|
+
}
|
|
1134
|
+
return {
|
|
1135
|
+
stdout: String(result.stdout || ""),
|
|
1136
|
+
stderr: String(result.stderr || ""),
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function macExactObjectKeys(value, keys) {
|
|
1141
|
+
return (
|
|
1142
|
+
value &&
|
|
1143
|
+
typeof value === "object" &&
|
|
1144
|
+
!Array.isArray(value) &&
|
|
1145
|
+
JSON.stringify(Object.keys(value).sort()) ===
|
|
1146
|
+
JSON.stringify([...keys].sort())
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function verifyMacHelperProbe(probe, nonce, contract, runtime) {
|
|
1151
|
+
const keys = [
|
|
1152
|
+
"schema",
|
|
1153
|
+
"nonce",
|
|
1154
|
+
"protocolVersion",
|
|
1155
|
+
"protocolSha256",
|
|
1156
|
+
"sourceSha256",
|
|
1157
|
+
"gateBootstrapSha256",
|
|
1158
|
+
"selfSha256",
|
|
1159
|
+
"realUid",
|
|
1160
|
+
"effectiveUid",
|
|
1161
|
+
"realGid",
|
|
1162
|
+
"effectiveGid",
|
|
1163
|
+
"installedMode",
|
|
1164
|
+
"snapshotRootMode",
|
|
1165
|
+
"readyGate",
|
|
1166
|
+
"rootWatchdog",
|
|
1167
|
+
"staleCleanupBounded",
|
|
1168
|
+
"maximumStaleSnapshots",
|
|
1169
|
+
"globalLaunchLock",
|
|
1170
|
+
"callerLifelineFd",
|
|
1171
|
+
"callerLifelineWatched",
|
|
1172
|
+
"signalRelayNonblocking",
|
|
1173
|
+
"relayParentCredentialsDropped",
|
|
1174
|
+
"entryRootOwnedAnonymousSnapshot",
|
|
1175
|
+
"entrySourcePrePostStat",
|
|
1176
|
+
"entryWriterClosedBeforeReadonlyReopen",
|
|
1177
|
+
"entryReadonlyIdentityRechecked",
|
|
1178
|
+
"entryUnlinkedAndDirectoryFsyncedBeforeTarget",
|
|
1179
|
+
"targetInheritedEntrySnapshotOnly",
|
|
1180
|
+
"runtimeAndCapsuleSlotsNullBeforeExec",
|
|
1181
|
+
"bootstrapClosesNullAndReadyDescriptors",
|
|
1182
|
+
"processForkExplicitlyDenied",
|
|
1183
|
+
];
|
|
1184
|
+
const callerUid = runtime.getuid();
|
|
1185
|
+
const callerGid = runtime.getgid();
|
|
1186
|
+
if (
|
|
1187
|
+
!macExactObjectKeys(probe, keys) ||
|
|
1188
|
+
!Number.isSafeInteger(callerUid) ||
|
|
1189
|
+
callerUid <= 0 ||
|
|
1190
|
+
!Number.isSafeInteger(callerGid) ||
|
|
1191
|
+
callerGid <= 0 ||
|
|
1192
|
+
probe.schema !== "chainlesschain.macos-mcp-launcher-probe.v1" ||
|
|
1193
|
+
probe.nonce !== nonce ||
|
|
1194
|
+
probe.protocolVersion !== 1 ||
|
|
1195
|
+
probe.protocolSha256 !== MACOS_MCP_LAUNCHER_INPUTS.protocolSha256 ||
|
|
1196
|
+
probe.sourceSha256 !== MACOS_MCP_LAUNCHER_INPUTS.sourceSha256 ||
|
|
1197
|
+
probe.gateBootstrapSha256 !==
|
|
1198
|
+
MCP_STDIO_MACOS_GATED_ENTRY_BOOTSTRAP_SHA256 ||
|
|
1199
|
+
probe.selfSha256 !== contract.helperSha256 ||
|
|
1200
|
+
probe.realUid !== callerUid ||
|
|
1201
|
+
probe.effectiveUid !== 0 ||
|
|
1202
|
+
probe.realGid !== callerGid ||
|
|
1203
|
+
probe.effectiveGid !== callerGid ||
|
|
1204
|
+
probe.installedMode !== "4555" ||
|
|
1205
|
+
probe.snapshotRootMode !== "0711" ||
|
|
1206
|
+
probe.readyGate !== true ||
|
|
1207
|
+
probe.rootWatchdog !== true ||
|
|
1208
|
+
probe.staleCleanupBounded !== true ||
|
|
1209
|
+
probe.maximumStaleSnapshots !==
|
|
1210
|
+
MACOS_MCP_LAUNCHER_INPUTS.protocol.maximumStaleSnapshots ||
|
|
1211
|
+
probe.globalLaunchLock !== true ||
|
|
1212
|
+
probe.callerLifelineFd !==
|
|
1213
|
+
MACOS_MCP_LAUNCHER_INPUTS.protocol.callerLifelineFd ||
|
|
1214
|
+
probe.callerLifelineWatched !== true ||
|
|
1215
|
+
probe.signalRelayNonblocking !== true ||
|
|
1216
|
+
probe.relayParentCredentialsDropped !== true ||
|
|
1217
|
+
probe.entryRootOwnedAnonymousSnapshot !== true ||
|
|
1218
|
+
probe.entrySourcePrePostStat !== true ||
|
|
1219
|
+
probe.entryWriterClosedBeforeReadonlyReopen !== true ||
|
|
1220
|
+
probe.entryReadonlyIdentityRechecked !== true ||
|
|
1221
|
+
probe.entryUnlinkedAndDirectoryFsyncedBeforeTarget !== true ||
|
|
1222
|
+
probe.targetInheritedEntrySnapshotOnly !== true ||
|
|
1223
|
+
probe.runtimeAndCapsuleSlotsNullBeforeExec !== true ||
|
|
1224
|
+
probe.bootstrapClosesNullAndReadyDescriptors !== true ||
|
|
1225
|
+
probe.processForkExplicitlyDenied !== true
|
|
1226
|
+
) {
|
|
1227
|
+
throw new Error("macos_helper_probe_contract_mismatch");
|
|
1228
|
+
}
|
|
1229
|
+
return probe;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function attestInstalledMacMcpLauncher(runtime) {
|
|
1233
|
+
const protocol = MACOS_MCP_LAUNCHER_INPUTS.protocol;
|
|
1234
|
+
let contractPin;
|
|
1235
|
+
let helperPin;
|
|
1236
|
+
try {
|
|
1237
|
+
for (const directory of [
|
|
1238
|
+
"/Library",
|
|
1239
|
+
"/Library/PrivilegedHelperTools",
|
|
1240
|
+
"/Library/Application Support",
|
|
1241
|
+
"/Library/Application Support/ChainlessChain",
|
|
1242
|
+
"/Library/Application Support/ChainlessChain/McpLauncher",
|
|
1243
|
+
]) {
|
|
1244
|
+
macFixedPathStat(runtime, directory, {
|
|
1245
|
+
kind: "directory",
|
|
1246
|
+
mode: 0o755,
|
|
1247
|
+
reason: "macos_helper_install_directory_untrusted",
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
macFixedPathStat(runtime, protocol.snapshotRoot, {
|
|
1251
|
+
kind: "directory",
|
|
1252
|
+
mode: 0o711,
|
|
1253
|
+
reason: "macos_helper_snapshot_root_untrusted",
|
|
1254
|
+
});
|
|
1255
|
+
macFixedPathStat(
|
|
1256
|
+
runtime,
|
|
1257
|
+
path.posix.join(protocol.snapshotRoot, protocol.snapshotLockName),
|
|
1258
|
+
{
|
|
1259
|
+
kind: "file",
|
|
1260
|
+
mode: 0o600,
|
|
1261
|
+
nlink: 1,
|
|
1262
|
+
reason: "macos_helper_snapshot_lock_untrusted",
|
|
1263
|
+
},
|
|
1264
|
+
);
|
|
1265
|
+
macFixedPathStat(runtime, protocol.sandboxExecutable, {
|
|
1266
|
+
kind: "file",
|
|
1267
|
+
reason: "macos_sandbox_exec_untrusted",
|
|
1268
|
+
});
|
|
1269
|
+
for (const tool of [
|
|
1270
|
+
"/usr/bin/codesign",
|
|
1271
|
+
"/usr/sbin/pkgutil",
|
|
1272
|
+
"/usr/sbin/spctl",
|
|
1273
|
+
]) {
|
|
1274
|
+
macFixedPathStat(runtime, tool, {
|
|
1275
|
+
kind: "file",
|
|
1276
|
+
reason: "macos_signature_tool_untrusted",
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
contractPin = macOpenPinnedFile(runtime, protocol.installContractPath, {
|
|
1280
|
+
mode: 0o444,
|
|
1281
|
+
nlink: 1,
|
|
1282
|
+
maximumBytes: 64 * 1024,
|
|
1283
|
+
reason: "macos_helper_install_contract_untrusted",
|
|
1284
|
+
});
|
|
1285
|
+
const contractText = macReadPinnedUtf8(runtime, contractPin, 64 * 1024);
|
|
1286
|
+
const contract = verifyMacosMcpLauncherInstallContract(
|
|
1287
|
+
JSON.parse(contractText),
|
|
1288
|
+
);
|
|
1289
|
+
const receiptResult = macSpawnAttestation(runtime, "/usr/sbin/pkgutil", [
|
|
1290
|
+
"--pkg-info",
|
|
1291
|
+
contract.packageIdentifier,
|
|
1292
|
+
]);
|
|
1293
|
+
const receipt = Object.fromEntries(
|
|
1294
|
+
receiptResult.stdout
|
|
1295
|
+
.split(/\r?\n/u)
|
|
1296
|
+
.map((line) => line.match(/^([^:]+):\s*(.*)$/u))
|
|
1297
|
+
.filter(Boolean)
|
|
1298
|
+
.map((match) => [match[1], match[2]]),
|
|
1299
|
+
);
|
|
1300
|
+
if (
|
|
1301
|
+
receipt["package-id"] !== contract.packageIdentifier ||
|
|
1302
|
+
receipt.version !== contract.packageVersion ||
|
|
1303
|
+
receipt.volume !== "/"
|
|
1304
|
+
) {
|
|
1305
|
+
throw new Error("macos_helper_package_receipt_mismatch");
|
|
1306
|
+
}
|
|
1307
|
+
helperPin = macOpenPinnedFile(runtime, protocol.helperInstallPath, {
|
|
1308
|
+
mode: 0o4555,
|
|
1309
|
+
nlink: 1,
|
|
1310
|
+
executable: true,
|
|
1311
|
+
expectedBytes: contract.helperBytes,
|
|
1312
|
+
expectedSha256: contract.helperSha256,
|
|
1313
|
+
reason: "macos_helper_binary_untrusted",
|
|
1314
|
+
});
|
|
1315
|
+
macSpawnAttestation(runtime, "/usr/bin/codesign", [
|
|
1316
|
+
"--verify",
|
|
1317
|
+
"--strict",
|
|
1318
|
+
"--verbose=4",
|
|
1319
|
+
`-R=${contract.designatedRequirement}`,
|
|
1320
|
+
protocol.helperInstallPath,
|
|
1321
|
+
]);
|
|
1322
|
+
const description = macSpawnAttestation(runtime, "/usr/bin/codesign", [
|
|
1323
|
+
"-dvvv",
|
|
1324
|
+
protocol.helperInstallPath,
|
|
1325
|
+
]);
|
|
1326
|
+
const details = `${description.stdout}\n${description.stderr}`;
|
|
1327
|
+
const teamIdentifier = details.match(/^TeamIdentifier=(.+)$/mu)?.[1];
|
|
1328
|
+
const signingIdentifier = details.match(/^Identifier=(.+)$/mu)?.[1];
|
|
1329
|
+
if (
|
|
1330
|
+
teamIdentifier !== contract.teamIdentifier ||
|
|
1331
|
+
signingIdentifier !== contract.signingIdentifier ||
|
|
1332
|
+
!/^CodeDirectory .+ flags=.+\(runtime\)/mu.test(details)
|
|
1333
|
+
) {
|
|
1334
|
+
throw new Error("macos_helper_signature_identity_mismatch");
|
|
1335
|
+
}
|
|
1336
|
+
const requirementOutput = macSpawnAttestation(
|
|
1337
|
+
runtime,
|
|
1338
|
+
"/usr/bin/codesign",
|
|
1339
|
+
["-dr", "-", protocol.helperInstallPath],
|
|
1340
|
+
);
|
|
1341
|
+
const designatedRequirement =
|
|
1342
|
+
`${requirementOutput.stdout}\n${requirementOutput.stderr}`.match(
|
|
1343
|
+
/^designated => (.+)$/mu,
|
|
1344
|
+
)?.[1];
|
|
1345
|
+
if (designatedRequirement !== contract.designatedRequirement) {
|
|
1346
|
+
throw new Error("macos_helper_designated_requirement_mismatch");
|
|
1347
|
+
}
|
|
1348
|
+
const gatekeeper = macSpawnAttestation(runtime, "/usr/sbin/spctl", [
|
|
1349
|
+
"--assess",
|
|
1350
|
+
"--type",
|
|
1351
|
+
"execute",
|
|
1352
|
+
"--verbose=4",
|
|
1353
|
+
protocol.helperInstallPath,
|
|
1354
|
+
]);
|
|
1355
|
+
const gatekeeperEvidence = `${gatekeeper.stdout}\n${gatekeeper.stderr}`;
|
|
1356
|
+
if (
|
|
1357
|
+
!/\baccepted\b/iu.test(gatekeeperEvidence) ||
|
|
1358
|
+
!/source=Notarized Developer ID/iu.test(gatekeeperEvidence)
|
|
1359
|
+
) {
|
|
1360
|
+
throw new Error("macos_helper_notarization_unattested");
|
|
1361
|
+
}
|
|
1362
|
+
const nonce = runtime.randomBytes(32).toString("hex");
|
|
1363
|
+
const probeResult = macSpawnAttestation(
|
|
1364
|
+
runtime,
|
|
1365
|
+
protocol.helperInstallPath,
|
|
1366
|
+
["--probe-v1", nonce],
|
|
1367
|
+
);
|
|
1368
|
+
if (probeResult.stderr.trim() !== "") {
|
|
1369
|
+
throw new Error("macos_helper_probe_stderr");
|
|
1370
|
+
}
|
|
1371
|
+
const probe = verifyMacHelperProbe(
|
|
1372
|
+
JSON.parse(probeResult.stdout),
|
|
1373
|
+
nonce,
|
|
1374
|
+
contract,
|
|
1375
|
+
runtime,
|
|
1376
|
+
);
|
|
1377
|
+
if (
|
|
1378
|
+
hashLinuxOpenFile(runtime, helperPin.fd, helperPin.bytes) !==
|
|
1379
|
+
helperPin.sha256 ||
|
|
1380
|
+
!macSameOpenIdentity(
|
|
1381
|
+
helperPin.stat,
|
|
1382
|
+
runtime.fs.fstatSync(helperPin.fd),
|
|
1383
|
+
) ||
|
|
1384
|
+
!macSameOpenIdentity(
|
|
1385
|
+
helperPin.stat,
|
|
1386
|
+
macFixedPathStat(runtime, protocol.helperInstallPath, {
|
|
1387
|
+
kind: "file",
|
|
1388
|
+
mode: 0o4555,
|
|
1389
|
+
nlink: 1,
|
|
1390
|
+
reason: "macos_helper_binary_changed",
|
|
1391
|
+
}),
|
|
1392
|
+
) ||
|
|
1393
|
+
!macSameOpenIdentity(
|
|
1394
|
+
contractPin.stat,
|
|
1395
|
+
macFixedPathStat(runtime, protocol.installContractPath, {
|
|
1396
|
+
kind: "file",
|
|
1397
|
+
mode: 0o444,
|
|
1398
|
+
nlink: 1,
|
|
1399
|
+
reason: "macos_helper_install_contract_changed",
|
|
1400
|
+
}),
|
|
1401
|
+
)
|
|
1402
|
+
) {
|
|
1403
|
+
throw new Error("macos_helper_installation_changed_during_attestation");
|
|
1404
|
+
}
|
|
1405
|
+
const installContractSha256 = crypto
|
|
1406
|
+
.createHash("sha256")
|
|
1407
|
+
.update(contractText)
|
|
1408
|
+
.digest("hex");
|
|
1409
|
+
return Object.freeze({
|
|
1410
|
+
contract,
|
|
1411
|
+
probe,
|
|
1412
|
+
helper: Object.freeze({
|
|
1413
|
+
path: protocol.helperInstallPath,
|
|
1414
|
+
sha256: helperPin.sha256,
|
|
1415
|
+
bytes: helperPin.bytes,
|
|
1416
|
+
fileId: Object.freeze({ ...helperPin.fileId }),
|
|
1417
|
+
mode: macMode(helperPin.stat),
|
|
1418
|
+
uid: Number(helperPin.stat.uid),
|
|
1419
|
+
gid: Number(helperPin.stat.gid),
|
|
1420
|
+
signingIdentifier,
|
|
1421
|
+
teamIdentifier,
|
|
1422
|
+
packageIdentifier: contract.packageIdentifier,
|
|
1423
|
+
packageVersion: contract.packageVersion,
|
|
1424
|
+
designatedRequirementSha256: crypto
|
|
1425
|
+
.createHash("sha256")
|
|
1426
|
+
.update(designatedRequirement)
|
|
1427
|
+
.digest("hex"),
|
|
1428
|
+
}),
|
|
1429
|
+
installContractSha256,
|
|
1430
|
+
});
|
|
1431
|
+
} finally {
|
|
1432
|
+
for (const pin of [helperPin, contractPin]) {
|
|
1433
|
+
if (pin?.fd !== undefined) {
|
|
1434
|
+
try {
|
|
1435
|
+
runtime.fs.closeSync(pin.fd);
|
|
1436
|
+
} catch {
|
|
1437
|
+
// best effort; launch admission still fails if later pinning fails
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function macPinContractFile(runtime, identity, options = {}) {
|
|
1445
|
+
let fd;
|
|
1446
|
+
try {
|
|
1447
|
+
if (
|
|
1448
|
+
!identity ||
|
|
1449
|
+
typeof identity.realPath !== "string" ||
|
|
1450
|
+
!path.posix.isAbsolute(identity.realPath) ||
|
|
1451
|
+
!/^[a-f0-9]{64}$/u.test(identity.sha256 || "") ||
|
|
1452
|
+
!identity.fileId ||
|
|
1453
|
+
!Number.isSafeInteger(identity.bytes) ||
|
|
1454
|
+
identity.bytes < (options.minimumBytes ?? 0) ||
|
|
1455
|
+
identity.bytes > options.maximumBytes ||
|
|
1456
|
+
linuxRealpath(runtime, identity.realPath) !== identity.realPath
|
|
1457
|
+
) {
|
|
1458
|
+
throw new Error("macos_contract_file_identity_invalid");
|
|
1459
|
+
}
|
|
1460
|
+
const namedBefore = runtime.fs.lstatSync(identity.realPath);
|
|
1461
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
1462
|
+
if (namedBefore.isSymbolicLink?.() || !namedBefore.isFile()) {
|
|
1463
|
+
throw new Error("macos_contract_file_identity_invalid");
|
|
1464
|
+
}
|
|
1465
|
+
fd = runtime.fs.openSync(
|
|
1466
|
+
identity.realPath,
|
|
1467
|
+
Number(constants.O_RDONLY) |
|
|
1468
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
1469
|
+
Number(constants.O_CLOEXEC || 0) |
|
|
1470
|
+
Number(constants.O_NONBLOCK || 0),
|
|
1471
|
+
);
|
|
1472
|
+
const before = runtime.fs.fstatSync(fd);
|
|
1473
|
+
if (
|
|
1474
|
+
!before.isFile() ||
|
|
1475
|
+
!macSameOpenIdentity(namedBefore, before) ||
|
|
1476
|
+
String(before.dev) !== String(identity.fileId.dev) ||
|
|
1477
|
+
String(before.ino) !== String(identity.fileId.ino) ||
|
|
1478
|
+
Number(before.size) !== identity.bytes ||
|
|
1479
|
+
Number(before.mtimeMs) !== Number(identity.mtimeMs) ||
|
|
1480
|
+
(options.executable === true && (Number(before.mode) & 0o111) === 0)
|
|
1481
|
+
) {
|
|
1482
|
+
throw new Error("macos_contract_file_identity_changed");
|
|
1483
|
+
}
|
|
1484
|
+
const sha256 = hashLinuxOpenFile(runtime, fd, identity.bytes);
|
|
1485
|
+
const after = runtime.fs.fstatSync(fd);
|
|
1486
|
+
const namedAfter = runtime.fs.lstatSync(identity.realPath);
|
|
1487
|
+
if (
|
|
1488
|
+
sha256 !== identity.sha256 ||
|
|
1489
|
+
!macSameOpenIdentity(before, after) ||
|
|
1490
|
+
!macSameOpenIdentity(after, namedAfter) ||
|
|
1491
|
+
linuxRealpath(runtime, identity.realPath) !== identity.realPath
|
|
1492
|
+
) {
|
|
1493
|
+
throw new Error("macos_contract_file_identity_changed");
|
|
1494
|
+
}
|
|
1495
|
+
return {
|
|
1496
|
+
fd,
|
|
1497
|
+
sha256,
|
|
1498
|
+
bytes: identity.bytes,
|
|
1499
|
+
fileId: { dev: String(after.dev), ino: String(after.ino) },
|
|
1500
|
+
mtimeMs: Number(after.mtimeMs),
|
|
1501
|
+
mode: Number(after.mode),
|
|
1502
|
+
};
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
if (fd !== undefined) {
|
|
1505
|
+
try {
|
|
1506
|
+
runtime.fs.closeSync(fd);
|
|
1507
|
+
} catch {
|
|
1508
|
+
// best effort
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
throw error;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
function macPinCapsuleDirectory(runtime, contract, callerUid) {
|
|
1516
|
+
let fd;
|
|
1517
|
+
try {
|
|
1518
|
+
const root = contract.pluginRoot;
|
|
1519
|
+
const identity = contract.rootIdentity;
|
|
1520
|
+
if (
|
|
1521
|
+
typeof root !== "string" ||
|
|
1522
|
+
linuxRealpath(runtime, root) !== root ||
|
|
1523
|
+
!identity?.fileId ||
|
|
1524
|
+
identity.realPath !== root
|
|
1525
|
+
) {
|
|
1526
|
+
throw new Error("macos_capsule_root_identity_invalid");
|
|
1527
|
+
}
|
|
1528
|
+
const namedBefore = runtime.fs.lstatSync(root);
|
|
1529
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
1530
|
+
if (
|
|
1531
|
+
namedBefore.isSymbolicLink?.() ||
|
|
1532
|
+
!namedBefore.isDirectory() ||
|
|
1533
|
+
Number(namedBefore.uid) !== callerUid ||
|
|
1534
|
+
(Number(namedBefore.mode) & 0o022) !== 0
|
|
1535
|
+
) {
|
|
1536
|
+
throw new Error("macos_capsule_root_identity_invalid");
|
|
1537
|
+
}
|
|
1538
|
+
fd = runtime.fs.openSync(
|
|
1539
|
+
root,
|
|
1540
|
+
Number(constants.O_RDONLY) |
|
|
1541
|
+
Number(constants.O_DIRECTORY || 0) |
|
|
1542
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
1543
|
+
Number(constants.O_CLOEXEC || 0) |
|
|
1544
|
+
Number(constants.O_NONBLOCK || 0),
|
|
1545
|
+
);
|
|
1546
|
+
const before = runtime.fs.fstatSync(fd);
|
|
1547
|
+
const namedAfter = runtime.fs.lstatSync(root);
|
|
1548
|
+
if (
|
|
1549
|
+
!before.isDirectory() ||
|
|
1550
|
+
!macSameOpenIdentity(namedBefore, before) ||
|
|
1551
|
+
!macSameOpenIdentity(before, namedAfter) ||
|
|
1552
|
+
String(before.dev) !== String(identity.fileId.dev) ||
|
|
1553
|
+
String(before.ino) !== String(identity.fileId.ino) ||
|
|
1554
|
+
linuxRealpath(runtime, root) !== root
|
|
1555
|
+
) {
|
|
1556
|
+
throw new Error("macos_capsule_root_identity_changed");
|
|
1557
|
+
}
|
|
1558
|
+
return {
|
|
1559
|
+
fd,
|
|
1560
|
+
fileId: { dev: String(before.dev), ino: String(before.ino) },
|
|
1561
|
+
mode: Number(before.mode),
|
|
1562
|
+
uid: Number(before.uid),
|
|
1563
|
+
gid: Number(before.gid),
|
|
1564
|
+
};
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
if (fd !== undefined) {
|
|
1567
|
+
try {
|
|
1568
|
+
runtime.fs.closeSync(fd);
|
|
1569
|
+
} catch {
|
|
1570
|
+
// best effort
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
throw error;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
function macStandardStdio(stdio) {
|
|
1578
|
+
if (Array.isArray(stdio)) {
|
|
1579
|
+
return [stdio[0] ?? "pipe", stdio[1] ?? "pipe", stdio[2] ?? "pipe"];
|
|
1580
|
+
}
|
|
1581
|
+
const value = stdio ?? "pipe";
|
|
1582
|
+
return [value, value, value];
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function macEnvironmentDigest(environment) {
|
|
1586
|
+
const normalized = Object.fromEntries(
|
|
1587
|
+
Object.entries(environment || {})
|
|
1588
|
+
.map(([key, value]) => [key, String(value)])
|
|
1589
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
1590
|
+
);
|
|
1591
|
+
return crypto
|
|
1592
|
+
.createHash("sha256")
|
|
1593
|
+
.update(JSON.stringify(normalized))
|
|
1594
|
+
.digest("hex");
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
export const MACOS_PKG_EXECPATH_MAGIC = "PKG_INVOKE_NODEJS";
|
|
1598
|
+
|
|
1599
|
+
export function macMcpTargetEnvironment(
|
|
1600
|
+
environment,
|
|
1601
|
+
{ packaged = false, inheritedEnvironment = process.env } = {},
|
|
1602
|
+
) {
|
|
1603
|
+
if (!packaged) return environment;
|
|
1604
|
+
const base = environment ?? inheritedEnvironment;
|
|
1605
|
+
if (!base || typeof base !== "object" || Array.isArray(base)) {
|
|
1606
|
+
throw new TypeError("packaged macOS MCP environment must be an object");
|
|
1607
|
+
}
|
|
1608
|
+
return {
|
|
1609
|
+
...base,
|
|
1610
|
+
PKG_EXECPATH: MACOS_PKG_EXECPATH_MAGIC,
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function sameMacStdio(left, right) {
|
|
1615
|
+
return (
|
|
1616
|
+
Array.isArray(left) &&
|
|
1617
|
+
Array.isArray(right) &&
|
|
1618
|
+
left.length === 9 &&
|
|
1619
|
+
right.length === 9 &&
|
|
1620
|
+
left.every((value, index) => value === right[index])
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
export function consumeMacMcpCodeSnapshotPlanBinding(plan, expected = {}) {
|
|
1625
|
+
const issued = issuedMacMcpCodeSnapshotPlans.get(plan);
|
|
1626
|
+
if (!issued) return false;
|
|
1627
|
+
issuedMacMcpCodeSnapshotPlans.delete(plan);
|
|
1628
|
+
return (
|
|
1629
|
+
issued.executionContract === expected.executionContract &&
|
|
1630
|
+
issued.originalCommand === expected.command &&
|
|
1631
|
+
sameStringArray(issued.originalArgs, expected.args) &&
|
|
1632
|
+
issued.originalCwd === (expected.cwd ?? null) &&
|
|
1633
|
+
issued.originalShell === (expected.shell ?? null) &&
|
|
1634
|
+
issued.originalDetached === (expected.detached ?? null) &&
|
|
1635
|
+
issued.profile === expected.profile &&
|
|
1636
|
+
sameStringArray(issued.requiredBoundaries, expected.requiredBoundaries) &&
|
|
1637
|
+
issued.sync === (expected.sync === true) &&
|
|
1638
|
+
plan.backend === MACOS_MCP_CAPSULE_BACKEND &&
|
|
1639
|
+
plan.enforcement === MACOS_MCP_CAPSULE_BACKEND &&
|
|
1640
|
+
plan.policyAttested === true &&
|
|
1641
|
+
plan.policyDigest === issued.policyDigest &&
|
|
1642
|
+
plan.command === issued.helperCommand &&
|
|
1643
|
+
sameStringArray(plan.args, issued.helperArgs) &&
|
|
1644
|
+
plan.options?.cwd === "/" &&
|
|
1645
|
+
plan.options?.shell === false &&
|
|
1646
|
+
plan.options?.detached !== true &&
|
|
1647
|
+
macEnvironmentDigest(plan.options?.env) === issued.environmentDigest &&
|
|
1648
|
+
sameMacStdio(issued.stdio, plan.options?.stdio) &&
|
|
1649
|
+
plan.postSpawn?.required === false &&
|
|
1650
|
+
plan.postSpawn?.mode === "none" &&
|
|
1651
|
+
plan.runtimeProbe?.planBindingDigest === issued.planBindingDigest
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
860
1655
|
function validateMacMcpCapsuleContract(
|
|
861
1656
|
command,
|
|
862
1657
|
args,
|
|
@@ -873,6 +1668,7 @@ function validateMacMcpCapsuleContract(
|
|
|
873
1668
|
!linuxStdioIsNarrow(spawnOpts?.stdio) ||
|
|
874
1669
|
spawnOpts?.serialization !== undefined ||
|
|
875
1670
|
spawnOpts?.argv0 !== undefined ||
|
|
1671
|
+
spawnOpts?.detached === true ||
|
|
876
1672
|
spawnOpts?.uid !== undefined ||
|
|
877
1673
|
spawnOpts?.gid !== undefined ||
|
|
878
1674
|
!Array.isArray(args) ||
|
|
@@ -936,6 +1732,7 @@ function applyMacMcpCapsuleCodeSnapshot(
|
|
|
936
1732
|
sandboxOpts,
|
|
937
1733
|
runtime,
|
|
938
1734
|
base,
|
|
1735
|
+
planBindingAuthority = null,
|
|
939
1736
|
) {
|
|
940
1737
|
const contract = sandboxOpts.executionContract;
|
|
941
1738
|
const validation = validateMacMcpCapsuleContract(
|
|
@@ -950,7 +1747,7 @@ function applyMacMcpCapsuleCodeSnapshot(
|
|
|
950
1747
|
createSandboxPlan({
|
|
951
1748
|
...base,
|
|
952
1749
|
backend: null,
|
|
953
|
-
candidateBackend:
|
|
1750
|
+
candidateBackend: MACOS_MCP_CAPSULE_CANDIDATE_BACKEND,
|
|
954
1751
|
policyAttested: false,
|
|
955
1752
|
runtimeProbe: runtimeProbe
|
|
956
1753
|
? {
|
|
@@ -968,31 +1765,288 @@ function applyMacMcpCapsuleCodeSnapshot(
|
|
|
968
1765
|
return unavailable("macos_mcp_capsule_execution_contract_invalid", {
|
|
969
1766
|
attempted: false,
|
|
970
1767
|
runnable: false,
|
|
971
|
-
reason: validation.reason,
|
|
1768
|
+
reason: validation.reason,
|
|
1769
|
+
contentSnapshot: false,
|
|
1770
|
+
handleAtomic: false,
|
|
1771
|
+
entrySnapshotAtomic: false,
|
|
1772
|
+
runtimeLaunchAtomic: false,
|
|
1773
|
+
sharedLibraryClosure: false,
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
const protocol = MACOS_MCP_LAUNCHER_INPUTS.protocol;
|
|
1778
|
+
const legacyUnavailable = () =>
|
|
1779
|
+
unavailable("macos_atomic_runtime_exec_unavailable", {
|
|
1780
|
+
attempted: true,
|
|
1781
|
+
runnable: false,
|
|
1782
|
+
reason: "public_api_has_no_descriptor_bound_exec",
|
|
1783
|
+
contentSnapshot: false,
|
|
1784
|
+
handleAtomic: false,
|
|
1785
|
+
entrySnapshotAtomic: false,
|
|
1786
|
+
runtimeLaunchAtomic: false,
|
|
1787
|
+
runtimeLaunchMechanism: "darwin-public-api-pathname-exec-only-v1",
|
|
1788
|
+
sharedLibraryClosure: false,
|
|
1789
|
+
});
|
|
1790
|
+
if (
|
|
1791
|
+
typeof runtime.fs.existsSync !== "function" ||
|
|
1792
|
+
!runtime.fs.existsSync(protocol.helperInstallPath) ||
|
|
1793
|
+
!runtime.fs.existsSync(protocol.installContractPath)
|
|
1794
|
+
) {
|
|
1795
|
+
// Darwin public APIs still provide no descriptor-bound exec. Production is
|
|
1796
|
+
// enabled only by the separately signed, root-installed release contract.
|
|
1797
|
+
return legacyUnavailable();
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
let runtimePin;
|
|
1801
|
+
let entryPin;
|
|
1802
|
+
let capsulePin;
|
|
1803
|
+
try {
|
|
1804
|
+
const callerUid = runtime.getuid();
|
|
1805
|
+
const callerGid = runtime.getgid();
|
|
1806
|
+
if (
|
|
1807
|
+
!Number.isSafeInteger(callerUid) ||
|
|
1808
|
+
callerUid <= 0 ||
|
|
1809
|
+
!Number.isSafeInteger(callerGid) ||
|
|
1810
|
+
callerGid <= 0
|
|
1811
|
+
) {
|
|
1812
|
+
throw new Error("macos_helper_caller_credentials_invalid");
|
|
1813
|
+
}
|
|
1814
|
+
const installation = attestInstalledMacMcpLauncher(runtime);
|
|
1815
|
+
runtimePin = macPinContractFile(runtime, contract.runtimeIdentity, {
|
|
1816
|
+
executable: true,
|
|
1817
|
+
minimumBytes: 1,
|
|
1818
|
+
maximumBytes: protocol.maximumRuntimeBytes,
|
|
1819
|
+
});
|
|
1820
|
+
entryPin = macPinContractFile(runtime, contract.entryIdentity, {
|
|
1821
|
+
minimumBytes: 0,
|
|
1822
|
+
maximumBytes: protocol.maximumEntryBytes,
|
|
1823
|
+
});
|
|
1824
|
+
capsulePin = macPinCapsuleDirectory(runtime, contract, callerUid);
|
|
1825
|
+
const nonce = runtime.randomBytes(32).toString("hex");
|
|
1826
|
+
if (!/^[a-f0-9]{64}$/u.test(nonce)) {
|
|
1827
|
+
throw new Error("macos_helper_nonce_generation_failed");
|
|
1828
|
+
}
|
|
1829
|
+
const snapshotPath = path.posix.join(protocol.snapshotRoot, nonce, "node");
|
|
1830
|
+
const policyDigest = macosMcpLauncherPolicyDigest({
|
|
1831
|
+
snapshotPath,
|
|
1832
|
+
capsulePath: contract.pluginRoot,
|
|
1833
|
+
});
|
|
1834
|
+
const helperArgs = [
|
|
1835
|
+
"--launch-v1",
|
|
1836
|
+
nonce,
|
|
1837
|
+
MACOS_MCP_LAUNCHER_INPUTS.protocolSha256,
|
|
1838
|
+
runtimePin.sha256,
|
|
1839
|
+
String(runtimePin.bytes),
|
|
1840
|
+
entryPin.sha256,
|
|
1841
|
+
String(entryPin.bytes),
|
|
1842
|
+
String(callerUid),
|
|
1843
|
+
String(callerGid),
|
|
1844
|
+
policyDigest,
|
|
1845
|
+
...args.slice(1),
|
|
1846
|
+
];
|
|
1847
|
+
const stdio = [
|
|
1848
|
+
...macStandardStdio(spawnOpts?.stdio),
|
|
1849
|
+
runtimePin.fd,
|
|
1850
|
+
entryPin.fd,
|
|
1851
|
+
capsulePin.fd,
|
|
1852
|
+
"ignore",
|
|
1853
|
+
"ignore",
|
|
1854
|
+
"pipe",
|
|
1855
|
+
];
|
|
1856
|
+
const packagedRuntime = Boolean(runtime.pkg);
|
|
1857
|
+
const targetEnvironment = macMcpTargetEnvironment(spawnOpts?.env, {
|
|
1858
|
+
packaged: packagedRuntime,
|
|
1859
|
+
inheritedEnvironment: runtime.env,
|
|
1860
|
+
});
|
|
1861
|
+
const options = {
|
|
1862
|
+
...(spawnOpts || {}),
|
|
1863
|
+
cwd: "/",
|
|
1864
|
+
shell: false,
|
|
1865
|
+
detached: false,
|
|
1866
|
+
stdio,
|
|
1867
|
+
...(targetEnvironment === undefined ? {} : { env: targetEnvironment }),
|
|
1868
|
+
};
|
|
1869
|
+
const installAttestationDigest = crypto
|
|
1870
|
+
.createHash("sha256")
|
|
1871
|
+
.update(
|
|
1872
|
+
JSON.stringify({
|
|
1873
|
+
version: 1,
|
|
1874
|
+
protocolSha256: MACOS_MCP_LAUNCHER_INPUTS.protocolSha256,
|
|
1875
|
+
sourceSha256: MACOS_MCP_LAUNCHER_INPUTS.sourceSha256,
|
|
1876
|
+
gateBootstrapSha256: MCP_STDIO_MACOS_GATED_ENTRY_BOOTSTRAP_SHA256,
|
|
1877
|
+
helper: installation.helper,
|
|
1878
|
+
installContractSha256: installation.installContractSha256,
|
|
1879
|
+
}),
|
|
1880
|
+
)
|
|
1881
|
+
.digest("hex");
|
|
1882
|
+
const environmentDigest = macEnvironmentDigest(options.env);
|
|
1883
|
+
const planBindingDigest = crypto
|
|
1884
|
+
.createHash("sha256")
|
|
1885
|
+
.update(
|
|
1886
|
+
JSON.stringify({
|
|
1887
|
+
version: 1,
|
|
1888
|
+
backend: MACOS_MCP_CAPSULE_BACKEND,
|
|
1889
|
+
helperArgs,
|
|
1890
|
+
policyDigest,
|
|
1891
|
+
installAttestationDigest,
|
|
1892
|
+
environmentDigest,
|
|
1893
|
+
runtimeFd: runtimePin.fd,
|
|
1894
|
+
entryFd: entryPin.fd,
|
|
1895
|
+
capsuleRootFd: capsulePin.fd,
|
|
1896
|
+
callerLifelineFd: protocol.callerLifelineFd,
|
|
1897
|
+
}),
|
|
1898
|
+
)
|
|
1899
|
+
.digest("hex");
|
|
1900
|
+
let cleaned = false;
|
|
1901
|
+
const cleanup = () => {
|
|
1902
|
+
if (cleaned) return;
|
|
1903
|
+
cleaned = true;
|
|
1904
|
+
for (const pin of [runtimePin, entryPin, capsulePin]) {
|
|
1905
|
+
if (pin?.fd !== undefined) {
|
|
1906
|
+
try {
|
|
1907
|
+
runtime.fs.closeSync(pin.fd);
|
|
1908
|
+
} catch {
|
|
1909
|
+
// Closing an already-duplicated parent descriptor is best effort.
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
const runtimeProbe = {
|
|
1915
|
+
kind: "darwin-mcp-capsule-code-snapshot-v2",
|
|
1916
|
+
attempted: true,
|
|
1917
|
+
runnable: true,
|
|
1918
|
+
reason: null,
|
|
1919
|
+
probeRuntime: "node",
|
|
1920
|
+
targetRuntime: "node",
|
|
1921
|
+
targetRuntimeInvocationMode: packagedRuntime
|
|
1922
|
+
? "pkg-copied-executable-eval-v1"
|
|
1923
|
+
: "node-executable-eval-v1",
|
|
1924
|
+
pkgExecPathMagicBound: packagedRuntime,
|
|
1925
|
+
contentSnapshot: true,
|
|
1926
|
+
contentSnapshotScope: "mcp-capsule-entry-and-node-runtime",
|
|
1927
|
+
contentSnapshotMechanism:
|
|
1928
|
+
"signed-root-runtime-path-and-anonymous-entry-fd-snapshots-v1",
|
|
1929
|
+
handleAtomic: true,
|
|
1930
|
+
entrySnapshotAtomic: true,
|
|
1931
|
+
runtimeLaunchAtomic: true,
|
|
1932
|
+
runtimeLaunchMechanism:
|
|
1933
|
+
"signed-root-helper-fd-copy-protected-path-ready-gate-v1",
|
|
1934
|
+
runtimeLaunchPath: snapshotPath,
|
|
1935
|
+
entrySnapshotPath: "anonymous-root-owned-fd4",
|
|
1936
|
+
runtimeSnapshotSha256: runtimePin.sha256,
|
|
1937
|
+
runtimeSnapshotBytes: runtimePin.bytes,
|
|
1938
|
+
entrySnapshotSha256: entryPin.sha256,
|
|
1939
|
+
entrySnapshotBytes: entryPin.bytes,
|
|
1940
|
+
entrySnapshotBootstrapSha256:
|
|
1941
|
+
MCP_STDIO_MACOS_GATED_ENTRY_BOOTSTRAP_SHA256,
|
|
1942
|
+
sharedLibraryClosure: false,
|
|
1943
|
+
rootProtectedRuntimeSnapshot: true,
|
|
1944
|
+
entryRootOwnedAnonymousSnapshot: true,
|
|
1945
|
+
entrySourcePrePostStat: true,
|
|
1946
|
+
entryWriterClosedBeforeReadonlyReopen: true,
|
|
1947
|
+
entryReadonlyIdentityRechecked: true,
|
|
1948
|
+
entryUnlinkedAndDirectoryFsyncedBeforeTarget: true,
|
|
1949
|
+
targetInheritedEntrySnapshotOnly: true,
|
|
1950
|
+
runtimeAndCapsuleSlotsNullBeforeExec: true,
|
|
1951
|
+
bootstrapClosesNullAndReadyDescriptors: true,
|
|
1952
|
+
actualRuntimeReadyHandshake: true,
|
|
1953
|
+
runtimeSnapshotUnlinkedBeforeEntryRelease: true,
|
|
1954
|
+
callerCredentialDropIrreversible: true,
|
|
1955
|
+
relayParentCredentialsDropped: true,
|
|
1956
|
+
callerLifelineWatched: true,
|
|
1957
|
+
signalRelayNonblocking: true,
|
|
1958
|
+
targetDescriptorAllowlist:
|
|
1959
|
+
"stdio-fd3-null-fd4-entry-fd5-null-fd6-gate-fd7-ready",
|
|
1960
|
+
capsuleRootDescriptorBound: true,
|
|
1961
|
+
capsulePathObjectAtomic: false,
|
|
1962
|
+
sandboxProfileFixedAndDigestBound: true,
|
|
1963
|
+
processForkExplicitlyDenied: true,
|
|
1964
|
+
sandboxExecLiveGateContract: true,
|
|
1965
|
+
globalLaunchSerialization: true,
|
|
1966
|
+
maximumStaleSnapshots: protocol.maximumStaleSnapshots,
|
|
1967
|
+
helperSha256: installation.helper.sha256,
|
|
1968
|
+
helperSourceSha256: MACOS_MCP_LAUNCHER_INPUTS.sourceSha256,
|
|
1969
|
+
helperProtocolSha256: MACOS_MCP_LAUNCHER_INPUTS.protocolSha256,
|
|
1970
|
+
helperInstallContractSha256: installation.installContractSha256,
|
|
1971
|
+
helperDesignatedRequirementSha256:
|
|
1972
|
+
installation.helper.designatedRequirementSha256,
|
|
1973
|
+
helperTeamIdentifier: installation.helper.teamIdentifier,
|
|
1974
|
+
helperPackageIdentifier: installation.helper.packageIdentifier,
|
|
1975
|
+
helperPackageVersion: installation.helper.packageVersion,
|
|
1976
|
+
installAttestationDigest,
|
|
1977
|
+
planBindingMechanism: "macos-mcp-code-snapshot-plan-binding-v1",
|
|
1978
|
+
planBindingDigest,
|
|
1979
|
+
};
|
|
1980
|
+
const plan = createSandboxPlan({
|
|
1981
|
+
...base,
|
|
1982
|
+
applied: true,
|
|
1983
|
+
enforcement: MACOS_MCP_CAPSULE_BACKEND,
|
|
1984
|
+
backend: MACOS_MCP_CAPSULE_BACKEND,
|
|
1985
|
+
candidateBackend: null,
|
|
1986
|
+
policyAttested: true,
|
|
1987
|
+
policyDigest,
|
|
1988
|
+
command: protocol.helperInstallPath,
|
|
1989
|
+
args: helperArgs,
|
|
1990
|
+
options,
|
|
1991
|
+
runtimeProbe,
|
|
1992
|
+
guarantees: [
|
|
1993
|
+
SANDBOX_BOUNDARIES.CODE_SNAPSHOT,
|
|
1994
|
+
SANDBOX_BOUNDARIES.FILESYSTEM,
|
|
1995
|
+
SANDBOX_BOUNDARIES.NETWORK,
|
|
1996
|
+
SANDBOX_BOUNDARIES.PROCESS_EXEC,
|
|
1997
|
+
SANDBOX_BOUNDARIES.PROCESS_TREE,
|
|
1998
|
+
SANDBOX_BOUNDARIES.PRIVILEGE_REDUCTION,
|
|
1999
|
+
],
|
|
2000
|
+
cleanup,
|
|
2001
|
+
});
|
|
2002
|
+
if (planBindingAuthority === MACOS_MCP_CODE_SNAPSHOT_ISSUER) {
|
|
2003
|
+
issuedMacMcpCodeSnapshotPlans.set(
|
|
2004
|
+
plan,
|
|
2005
|
+
Object.freeze({
|
|
2006
|
+
executionContract: contract,
|
|
2007
|
+
originalCommand: command,
|
|
2008
|
+
originalArgs: Object.freeze([...args]),
|
|
2009
|
+
originalCwd: spawnOpts?.cwd ?? null,
|
|
2010
|
+
originalShell: spawnOpts?.shell ?? null,
|
|
2011
|
+
originalDetached: spawnOpts?.detached ?? null,
|
|
2012
|
+
profile: base.profile,
|
|
2013
|
+
requiredBoundaries: Object.freeze([
|
|
2014
|
+
...(sandboxOpts.requiredBoundaries || []),
|
|
2015
|
+
]),
|
|
2016
|
+
sync: sandboxOpts.sync === true,
|
|
2017
|
+
policyDigest,
|
|
2018
|
+
helperCommand: protocol.helperInstallPath,
|
|
2019
|
+
helperArgs: Object.freeze([...helperArgs]),
|
|
2020
|
+
environmentDigest,
|
|
2021
|
+
stdio: Object.freeze([...stdio]),
|
|
2022
|
+
planBindingDigest,
|
|
2023
|
+
}),
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
return plan;
|
|
2027
|
+
} catch (error) {
|
|
2028
|
+
for (const pin of [runtimePin, entryPin, capsulePin]) {
|
|
2029
|
+
if (pin?.fd !== undefined) {
|
|
2030
|
+
try {
|
|
2031
|
+
runtime.fs.closeSync(pin.fd);
|
|
2032
|
+
} catch {
|
|
2033
|
+
// best effort
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return unavailable("macos_atomic_runtime_exec_unavailable", {
|
|
2038
|
+
kind: "darwin-mcp-capsule-code-snapshot-v2",
|
|
2039
|
+
attempted: true,
|
|
2040
|
+
runnable: false,
|
|
2041
|
+
reason: error?.message || "signed_root_helper_unattested",
|
|
972
2042
|
contentSnapshot: false,
|
|
973
2043
|
handleAtomic: false,
|
|
974
2044
|
entrySnapshotAtomic: false,
|
|
975
2045
|
runtimeLaunchAtomic: false,
|
|
2046
|
+
runtimeLaunchMechanism: "signed-root-helper-unattested-v1",
|
|
976
2047
|
sharedLibraryClosure: false,
|
|
977
2048
|
});
|
|
978
2049
|
}
|
|
979
|
-
|
|
980
|
-
// Darwin exposes only pathname-based exec/spawn APIs to an unprivileged
|
|
981
|
-
// process. An inherited descriptor can bind the entry source, but public
|
|
982
|
-
// APIs cannot bind the verified Node runtime descriptor to the image that
|
|
983
|
-
// executes. Private temporary paths and Seatbelt still leave a same-UID
|
|
984
|
-
// pathname-replacement window, so they cannot satisfy CODE_SNAPSHOT.
|
|
985
|
-
return unavailable("macos_atomic_runtime_exec_unavailable", {
|
|
986
|
-
attempted: true,
|
|
987
|
-
runnable: false,
|
|
988
|
-
reason: "public_api_has_no_descriptor_bound_exec",
|
|
989
|
-
contentSnapshot: false,
|
|
990
|
-
handleAtomic: false,
|
|
991
|
-
entrySnapshotAtomic: false,
|
|
992
|
-
runtimeLaunchAtomic: false,
|
|
993
|
-
runtimeLaunchMechanism: "darwin-public-api-pathname-exec-only-v1",
|
|
994
|
-
sharedLibraryClosure: false,
|
|
995
|
-
});
|
|
996
2050
|
}
|
|
997
2051
|
|
|
998
2052
|
/**
|
|
@@ -1013,6 +2067,7 @@ export function applyMacSandbox(
|
|
|
1013
2067
|
spawnOpts,
|
|
1014
2068
|
sandboxOpts = {},
|
|
1015
2069
|
runtimeOverrides = {},
|
|
2070
|
+
planBindingAuthority = null,
|
|
1016
2071
|
) {
|
|
1017
2072
|
const runtime = resolveRuntime(runtimeOverrides);
|
|
1018
2073
|
const invocation = normalizeWrappedInvocation(
|
|
@@ -1047,6 +2102,7 @@ export function applyMacSandbox(
|
|
|
1047
2102
|
sandboxOpts,
|
|
1048
2103
|
runtime,
|
|
1049
2104
|
base,
|
|
2105
|
+
planBindingAuthority,
|
|
1050
2106
|
);
|
|
1051
2107
|
}
|
|
1052
2108
|
|
|
@@ -3882,6 +4938,7 @@ function linuxBubblewrapProbe(
|
|
|
3882
4938
|
supervisorBinding = null,
|
|
3883
4939
|
pluginTreeSnapshot = null,
|
|
3884
4940
|
nativeDynamicClosure = null,
|
|
4941
|
+
runtimeLoadSet = null,
|
|
3885
4942
|
runtimeDetachedChildSpawnVerified = false,
|
|
3886
4943
|
) {
|
|
3887
4944
|
const native =
|
|
@@ -3923,18 +4980,42 @@ function linuxBubblewrapProbe(
|
|
|
3923
4980
|
reason === null &&
|
|
3924
4981
|
supervisorDescriptorBound &&
|
|
3925
4982
|
nativeDynamicClosure?.mechanism ===
|
|
3926
|
-
|
|
4983
|
+
LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_MECHANISM &&
|
|
3927
4984
|
nativeDynamicClosure?.scope ===
|
|
3928
|
-
|
|
4985
|
+
LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_SCOPE &&
|
|
3929
4986
|
typeof nativeDynamicClosure?.interpreter === "string" &&
|
|
3930
4987
|
path.posix.isAbsolute(nativeDynamicClosure.interpreter) &&
|
|
3931
4988
|
Number.isSafeInteger(nativeDynamicClosure?.dependencies) &&
|
|
3932
4989
|
nativeDynamicClosure.dependencies >= 0 &&
|
|
3933
|
-
nativeDynamicClosure.dependencies <=
|
|
4990
|
+
nativeDynamicClosure.dependencies <= LINUX_ELF_MAX_INITIAL_CLOSURE_EDGES &&
|
|
3934
4991
|
Number.isSafeInteger(nativeDynamicClosure?.files) &&
|
|
3935
4992
|
nativeDynamicClosure.files >= 1 &&
|
|
4993
|
+
nativeDynamicClosure.files <= LINUX_ELF_MAX_INITIAL_CLOSURE_FILES &&
|
|
4994
|
+
Number.isSafeInteger(nativeDynamicClosure?.bytes) &&
|
|
4995
|
+
nativeDynamicClosure.bytes > 0 &&
|
|
4996
|
+
nativeDynamicClosure.bytes <= LINUX_ELF_MAX_INITIAL_CLOSURE_BYTES &&
|
|
3936
4997
|
typeof nativeDynamicClosure?.digest === "string" &&
|
|
3937
4998
|
/^[a-f0-9]{64}$/.test(nativeDynamicClosure.digest);
|
|
4999
|
+
const runtimeSharedLibraryPathnameClosure =
|
|
5000
|
+
initialDynamicLoadClosureDescriptorBound &&
|
|
5001
|
+
pluginTreeContentSnapshot &&
|
|
5002
|
+
runtimeLoadSet?.scope === LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_SCOPE &&
|
|
5003
|
+
runtimeLoadSet?.mechanism ===
|
|
5004
|
+
LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MECHANISM &&
|
|
5005
|
+
Number.isSafeInteger(runtimeLoadSet?.files) &&
|
|
5006
|
+
runtimeLoadSet.files > 0 &&
|
|
5007
|
+
runtimeLoadSet.files <= LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MAX_FILES &&
|
|
5008
|
+
Number.isSafeInteger(runtimeLoadSet?.bytes) &&
|
|
5009
|
+
runtimeLoadSet.bytes > 0 &&
|
|
5010
|
+
runtimeLoadSet.bytes <= LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MAX_BYTES &&
|
|
5011
|
+
typeof runtimeLoadSet?.digest === "string" &&
|
|
5012
|
+
/^[a-f0-9]{64}$/.test(runtimeLoadSet.digest) &&
|
|
5013
|
+
runtimeLoadSet.policyBound === true &&
|
|
5014
|
+
runtimeLoadSet.writableFilesystems === false &&
|
|
5015
|
+
runtimeLoadSet.procfsMounted === false &&
|
|
5016
|
+
runtimeLoadSet.devfsMounted === false &&
|
|
5017
|
+
runtimeLoadSet.scratchWritable === false &&
|
|
5018
|
+
runtimeLoadSet.descriptorReopenPaths === false;
|
|
3938
5019
|
return {
|
|
3939
5020
|
kind: dynamicNative
|
|
3940
5021
|
? "linux-bwrap-plugin-native-dynamic-elf-policy-v1"
|
|
@@ -3977,7 +5058,31 @@ function linuxBubblewrapProbe(
|
|
|
3977
5058
|
initialDynamicInterpreter: nativeDynamicClosure.interpreter,
|
|
3978
5059
|
initialDynamicDependencyCount: nativeDynamicClosure.dependencies,
|
|
3979
5060
|
initialDynamicRuntimeFileCount: nativeDynamicClosure.files,
|
|
5061
|
+
initialDynamicRuntimeBytes: nativeDynamicClosure.bytes,
|
|
3980
5062
|
initialDynamicLoadClosureDigest: nativeDynamicClosure.digest,
|
|
5063
|
+
// The legacy aggregate field is intentionally kept false: this
|
|
5064
|
+
// production slice closes libc/ELF-loader pathname resolution, not
|
|
5065
|
+
// anonymous JIT mappings or a malicious custom in-process loader.
|
|
5066
|
+
sharedLibraryClosure: false,
|
|
5067
|
+
...(runtimeSharedLibraryPathnameClosure
|
|
5068
|
+
? {
|
|
5069
|
+
runtimeSharedLibraryPathnameClosure: true,
|
|
5070
|
+
runtimeSharedLibraryPathnameClosureExcludes:
|
|
5071
|
+
"anonymous-jit-and-custom-in-process-loader",
|
|
5072
|
+
runtimeSharedLibraryClosureScope: runtimeLoadSet.scope,
|
|
5073
|
+
runtimeSharedLibraryClosureMechanism: runtimeLoadSet.mechanism,
|
|
5074
|
+
runtimeSharedLibraryLoadSetFiles: runtimeLoadSet.files,
|
|
5075
|
+
runtimeSharedLibraryLoadSetBytes: runtimeLoadSet.bytes,
|
|
5076
|
+
runtimeSharedLibraryLoadSetDigest: runtimeLoadSet.digest,
|
|
5077
|
+
runtimeLoadSetPolicyBound: runtimeLoadSet.policyBound,
|
|
5078
|
+
runtimeWritableFilesystems: runtimeLoadSet.writableFilesystems,
|
|
5079
|
+
runtimeProcfsMounted: runtimeLoadSet.procfsMounted,
|
|
5080
|
+
runtimeDevfsMounted: runtimeLoadSet.devfsMounted,
|
|
5081
|
+
runtimeScratchWritable: runtimeLoadSet.scratchWritable,
|
|
5082
|
+
runtimeDescriptorReopenPaths:
|
|
5083
|
+
runtimeLoadSet.descriptorReopenPaths,
|
|
5084
|
+
}
|
|
5085
|
+
: {}),
|
|
3981
5086
|
}
|
|
3982
5087
|
: {}),
|
|
3983
5088
|
supervisorDescriptorBound,
|
|
@@ -3990,8 +5095,9 @@ function linuxBubblewrapProbe(
|
|
|
3990
5095
|
supervisorDescriptorConsumedBeforeTarget: runnable,
|
|
3991
5096
|
supervisorStagingPathHidden: runnable,
|
|
3992
5097
|
supervisorTemporaryCopyObscured: runnable,
|
|
3993
|
-
supervisorPid1ExecutableExposure:
|
|
3994
|
-
|
|
5098
|
+
supervisorPid1ExecutableExposure: runtimeSharedLibraryPathnameClosure
|
|
5099
|
+
? "procfs-not-mounted"
|
|
5100
|
+
: supervisorBinding.pid1ExecutableExposure,
|
|
3995
5101
|
supervisorExecutableIdentity: {
|
|
3996
5102
|
path: supervisorBinding.path,
|
|
3997
5103
|
fileId: supervisorBinding.fileId,
|
|
@@ -4246,7 +5352,7 @@ function assignLinuxMountChildFds(mounts) {
|
|
|
4246
5352
|
function linuxStdioWithPinnedMounts(
|
|
4247
5353
|
stdio,
|
|
4248
5354
|
descriptors,
|
|
4249
|
-
{ probe = false, supervisorFd = null } = {},
|
|
5355
|
+
{ probe = false, supervisorFd = null, scrubberFd = null } = {},
|
|
4250
5356
|
) {
|
|
4251
5357
|
let standard;
|
|
4252
5358
|
if (probe) {
|
|
@@ -4260,10 +5366,12 @@ function linuxStdioWithPinnedMounts(
|
|
|
4260
5366
|
}
|
|
4261
5367
|
while (standard.length < 3) standard.push(undefined);
|
|
4262
5368
|
const supervisor = Number.isInteger(supervisorFd) ? [supervisorFd] : [];
|
|
5369
|
+
const scrubber = Number.isInteger(scrubberFd) ? [scrubberFd] : [];
|
|
4263
5370
|
return [
|
|
4264
5371
|
...standard.slice(0, 3),
|
|
4265
5372
|
...supervisor,
|
|
4266
5373
|
...descriptors.map((descriptor) => descriptor.fd),
|
|
5374
|
+
...scrubber,
|
|
4267
5375
|
];
|
|
4268
5376
|
}
|
|
4269
5377
|
|
|
@@ -4306,16 +5414,51 @@ function validateLinuxPluginContract(
|
|
|
4306
5414
|
) {
|
|
4307
5415
|
return { ok: false, reason: "unsupported_launch_options" };
|
|
4308
5416
|
}
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
5417
|
+
let argsSnapshot = null;
|
|
5418
|
+
try {
|
|
5419
|
+
if (
|
|
5420
|
+
Array.isArray(args) &&
|
|
5421
|
+
Object.getPrototypeOf(args) === Array.prototype
|
|
5422
|
+
) {
|
|
5423
|
+
const descriptors = Object.getOwnPropertyDescriptors(args);
|
|
5424
|
+
const lengthDescriptor = descriptors.length;
|
|
5425
|
+
if (
|
|
5426
|
+
lengthDescriptor &&
|
|
5427
|
+
"value" in lengthDescriptor &&
|
|
5428
|
+
Number.isSafeInteger(lengthDescriptor.value) &&
|
|
5429
|
+
lengthDescriptor.value >= 0 &&
|
|
5430
|
+
Reflect.ownKeys(descriptors).length === lengthDescriptor.value + 1
|
|
5431
|
+
) {
|
|
5432
|
+
const snapshot = [];
|
|
5433
|
+
for (let index = 0; index < lengthDescriptor.value; index += 1) {
|
|
5434
|
+
const descriptor = descriptors[String(index)];
|
|
5435
|
+
if (
|
|
5436
|
+
!descriptor ||
|
|
5437
|
+
!("value" in descriptor) ||
|
|
5438
|
+
typeof descriptor.value !== "string" ||
|
|
5439
|
+
descriptor.value.includes("\0")
|
|
5440
|
+
) {
|
|
5441
|
+
argsSnapshot = null;
|
|
5442
|
+
break;
|
|
5443
|
+
}
|
|
5444
|
+
snapshot.push(descriptor.value);
|
|
5445
|
+
}
|
|
5446
|
+
if (snapshot.length === lengthDescriptor.value) {
|
|
5447
|
+
argsSnapshot = Object.freeze(snapshot);
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
} catch {
|
|
5452
|
+
argsSnapshot = null;
|
|
5453
|
+
}
|
|
5454
|
+
const invalidArgs = argsSnapshot === null;
|
|
4312
5455
|
const invalidNodeLaunch =
|
|
4313
5456
|
nodeContract &&
|
|
4314
|
-
(!
|
|
5457
|
+
(!argsSnapshot ||
|
|
4315
5458
|
command !== contract.runtimePath ||
|
|
4316
5459
|
command !== contract.runtimeIdentity?.realPath ||
|
|
4317
|
-
|
|
4318
|
-
|
|
5460
|
+
argsSnapshot.length < 1 ||
|
|
5461
|
+
argsSnapshot[0] !== contract.entryIdentity?.realPath);
|
|
4319
5462
|
const invalidNativeLaunch =
|
|
4320
5463
|
nativeContract && command !== contract.entryIdentity?.realPath;
|
|
4321
5464
|
if (
|
|
@@ -4433,6 +5576,7 @@ function validateLinuxPluginContract(
|
|
|
4433
5576
|
return {
|
|
4434
5577
|
ok: true,
|
|
4435
5578
|
contract,
|
|
5579
|
+
argsSnapshot,
|
|
4436
5580
|
entryRuntime: nativeContract
|
|
4437
5581
|
? entryFormat?.runtime ||
|
|
4438
5582
|
(legacyStaticNativeContract
|
|
@@ -4555,6 +5699,16 @@ function linuxMcpRuntimeSnapshotContract() {
|
|
|
4555
5699
|
};
|
|
4556
5700
|
}
|
|
4557
5701
|
|
|
5702
|
+
function linuxPluginRuntimeSnapshotContract() {
|
|
5703
|
+
return {
|
|
5704
|
+
scope: LINUX_PLUGIN_RUNTIME_SNAPSHOT_SCOPE,
|
|
5705
|
+
targetMode: "0500",
|
|
5706
|
+
sourceMode: LINUX_EXECUTABLE_SNAPSHOT_SOURCE_MODE,
|
|
5707
|
+
minimumBytes: 1,
|
|
5708
|
+
errorPrefix: "plugin_node_runtime_snapshot",
|
|
5709
|
+
};
|
|
5710
|
+
}
|
|
5711
|
+
|
|
4558
5712
|
function createLinuxRegularFileSnapshot(
|
|
4559
5713
|
runtime,
|
|
4560
5714
|
entryMount,
|
|
@@ -4630,7 +5784,8 @@ function createLinuxRegularFileSnapshot(
|
|
|
4630
5784
|
}
|
|
4631
5785
|
if (
|
|
4632
5786
|
(snapshotContract.scope === LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE ||
|
|
4633
|
-
snapshotContract.scope === LINUX_MCP_RUNTIME_SNAPSHOT_SCOPE
|
|
5787
|
+
snapshotContract.scope === LINUX_MCP_RUNTIME_SNAPSHOT_SCOPE ||
|
|
5788
|
+
snapshotContract.scope === LINUX_PLUGIN_RUNTIME_SNAPSHOT_SCOPE) &&
|
|
4634
5789
|
(sourceFileMode & 0o111) === 0
|
|
4635
5790
|
) {
|
|
4636
5791
|
throw snapshotError("source_mode_changed");
|
|
@@ -5406,9 +6561,14 @@ function linuxSystemDynamicPath(value) {
|
|
|
5406
6561
|
* to a host inspection utility. Besides conventional fully static ET_EXEC and
|
|
5407
6562
|
* static-PIE-shaped ET_DYN images, the parser can describe a narrow dynamic
|
|
5408
6563
|
* executable. The caller must separately prove that its PT_INTERP and every
|
|
5409
|
-
*
|
|
6564
|
+
* recursively reachable startup DT_NEEDED member resolve through a pinned,
|
|
6565
|
+
* attested system-file graph.
|
|
5410
6566
|
*/
|
|
5411
|
-
function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
6567
|
+
function inspectLinuxNativeElf(runtime, fd, identity, { role = "entry" } = {}) {
|
|
6568
|
+
const dependencyObject = role === "dependency";
|
|
6569
|
+
if (!dependencyObject && role !== "entry") {
|
|
6570
|
+
throw new Error("native_elf_inspection_role_invalid");
|
|
6571
|
+
}
|
|
5412
6572
|
const expectedMachine = LINUX_ELF_MACHINES[runtime.arch];
|
|
5413
6573
|
if (!expectedMachine) {
|
|
5414
6574
|
throw new Error(`unsupported_elf_architecture:${String(runtime.arch)}`);
|
|
@@ -5417,8 +6577,11 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5417
6577
|
const before = runtime.fs.fstatSync(fd);
|
|
5418
6578
|
if (
|
|
5419
6579
|
!before.isFile() ||
|
|
5420
|
-
|
|
5421
|
-
|
|
6580
|
+
(dependencyObject
|
|
6581
|
+
? Number(before.nlink) < 1 ||
|
|
6582
|
+
Number(before.uid) !== 0 ||
|
|
6583
|
+
(Number(before.mode) & 0o022) !== 0
|
|
6584
|
+
: Number(before.nlink) !== 1 || (Number(before.mode) & 0o111) === 0) ||
|
|
5422
6585
|
(Number(before.mode) & 0o6000) !== 0 ||
|
|
5423
6586
|
String(before.dev) !== String(identity?.fileId?.dev) ||
|
|
5424
6587
|
String(before.ino) !== String(identity?.fileId?.ino) ||
|
|
@@ -5447,6 +6610,9 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5447
6610
|
if (elfType !== LINUX_ELF_TYPE_EXEC && elfType !== LINUX_ELF_TYPE_DYN) {
|
|
5448
6611
|
throw new Error("native_entry_unsupported_elf_type");
|
|
5449
6612
|
}
|
|
6613
|
+
if (dependencyObject && elfType !== LINUX_ELF_TYPE_DYN) {
|
|
6614
|
+
throw new Error("native_dependency_not_shared_object");
|
|
6615
|
+
}
|
|
5450
6616
|
if (header.readUInt16LE(18) !== expectedMachine) {
|
|
5451
6617
|
throw new Error("native_entry_architecture_mismatch");
|
|
5452
6618
|
}
|
|
@@ -5551,7 +6717,7 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5551
6717
|
}
|
|
5552
6718
|
}
|
|
5553
6719
|
attestLinuxElfLoadMappings(loadSegments, runtimePageSize);
|
|
5554
|
-
if (!executableLoad || !entryInExecutableLoad) {
|
|
6720
|
+
if (!executableLoad || (!dependencyObject && !entryInExecutableLoad)) {
|
|
5555
6721
|
throw new Error("native_entry_has_no_executable_entry_segment");
|
|
5556
6722
|
}
|
|
5557
6723
|
if (!nonExecutableStack) {
|
|
@@ -5561,6 +6727,12 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5561
6727
|
if (interpreterSegments.length > 1) {
|
|
5562
6728
|
throw new Error("native_entry_interpreter_ambiguous");
|
|
5563
6729
|
}
|
|
6730
|
+
// A shared object can itself be directly executable: Ubuntu 24.04's
|
|
6731
|
+
// libc.so.6, for example, carries PT_INTERP. The dynamic loader ignores
|
|
6732
|
+
// that segment when it maps the object as a dependency, so rejecting it
|
|
6733
|
+
// would exclude a legitimate recursive startup graph. Still parse and
|
|
6734
|
+
// validate the segment below; only an entry object's interpreter becomes
|
|
6735
|
+
// part of the kernel-exec contract and returned closure evidence.
|
|
5564
6736
|
let interpreter = null;
|
|
5565
6737
|
if (interpreterSegments.length === 1) {
|
|
5566
6738
|
const segment = interpreterSegments[0];
|
|
@@ -5590,17 +6762,24 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5590
6762
|
}
|
|
5591
6763
|
}
|
|
5592
6764
|
|
|
5593
|
-
const staticPie =
|
|
5594
|
-
|
|
6765
|
+
const staticPie =
|
|
6766
|
+
!dependencyObject && elfType === LINUX_ELF_TYPE_DYN && interpreter === null;
|
|
6767
|
+
const dynamicExecutable = !dependencyObject && interpreter !== null;
|
|
5595
6768
|
let dynamicMetadata = null;
|
|
5596
|
-
if (
|
|
6769
|
+
if (
|
|
6770
|
+
!dependencyObject &&
|
|
6771
|
+
elfType === LINUX_ELF_TYPE_EXEC &&
|
|
6772
|
+
!dynamicExecutable
|
|
6773
|
+
) {
|
|
5597
6774
|
if (dynamicSegments.length > 0) {
|
|
5598
6775
|
throw new Error("native_entry_dynamic_elf_unsupported");
|
|
5599
6776
|
}
|
|
5600
6777
|
} else {
|
|
5601
|
-
const prefix =
|
|
5602
|
-
? "
|
|
5603
|
-
:
|
|
6778
|
+
const prefix = dependencyObject
|
|
6779
|
+
? "native_dependency_dynamic"
|
|
6780
|
+
: staticPie
|
|
6781
|
+
? "native_entry_static_pie"
|
|
6782
|
+
: "native_entry_dynamic";
|
|
5604
6783
|
if (dynamicSegments.length === 0) {
|
|
5605
6784
|
throw new Error(`${prefix}_dynamic_segment_missing`);
|
|
5606
6785
|
}
|
|
@@ -5668,6 +6847,7 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5668
6847
|
Number(dynamic.fileOffset),
|
|
5669
6848
|
);
|
|
5670
6849
|
const neededOffsets = [];
|
|
6850
|
+
let sonameOffset = null;
|
|
5671
6851
|
const forbiddenDependencyTags = new Set([
|
|
5672
6852
|
LINUX_ELF_DYNAMIC_RPATH,
|
|
5673
6853
|
LINUX_ELF_DYNAMIC_RUNPATH,
|
|
@@ -5704,6 +6884,11 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5704
6884
|
if (neededOffsets.length > LINUX_ELF_MAX_NEEDED_ENTRIES) {
|
|
5705
6885
|
throw new Error(`${prefix}_dependency_count_exceeded`);
|
|
5706
6886
|
}
|
|
6887
|
+
} else if (tag === LINUX_ELF_DYNAMIC_SONAME) {
|
|
6888
|
+
if (sonameOffset !== null) {
|
|
6889
|
+
throw new Error(`${prefix}_soname_ambiguous`);
|
|
6890
|
+
}
|
|
6891
|
+
sonameOffset = value;
|
|
5707
6892
|
} else if (tag === LINUX_ELF_DYNAMIC_STRTAB) {
|
|
5708
6893
|
if (stringTableAddress !== null) {
|
|
5709
6894
|
throw new Error(`${prefix}_string_table_ambiguous`);
|
|
@@ -5733,17 +6918,19 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5733
6918
|
throw new Error(`${prefix}_text_relocation_unsupported`);
|
|
5734
6919
|
}
|
|
5735
6920
|
if (
|
|
6921
|
+
!dependencyObject &&
|
|
5736
6922
|
elfType === LINUX_ELF_TYPE_DYN &&
|
|
5737
6923
|
(flags1 === null || (flags1 & LINUX_ELF_DYNAMIC_FLAG_PIE) === 0n)
|
|
5738
6924
|
) {
|
|
5739
6925
|
throw new Error(`${prefix}_flag_missing`);
|
|
5740
6926
|
}
|
|
5741
|
-
if (staticPie && neededOffsets.length > 0) {
|
|
6927
|
+
if (!dependencyObject && staticPie && neededOffsets.length > 0) {
|
|
5742
6928
|
throw new Error("native_entry_static_pie_dependency_unsupported");
|
|
5743
6929
|
}
|
|
5744
6930
|
|
|
5745
6931
|
let needed = [];
|
|
5746
|
-
|
|
6932
|
+
let soname = null;
|
|
6933
|
+
if (neededOffsets.length > 0 || sonameOffset !== null) {
|
|
5747
6934
|
if (
|
|
5748
6935
|
stringTableAddress === null ||
|
|
5749
6936
|
stringTableBytes === null ||
|
|
@@ -5793,34 +6980,61 @@ function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
|
5793
6980
|
if (new Set(needed).size !== needed.length) {
|
|
5794
6981
|
throw new Error("native_entry_dynamic_dependency_ambiguous");
|
|
5795
6982
|
}
|
|
6983
|
+
if (sonameOffset !== null) {
|
|
6984
|
+
if (sonameOffset > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
6985
|
+
throw new Error(`${prefix}_soname_string_out_of_bounds`);
|
|
6986
|
+
}
|
|
6987
|
+
soname = readLinuxElfString(
|
|
6988
|
+
stringTable,
|
|
6989
|
+
Number(sonameOffset),
|
|
6990
|
+
"dynamic_soname",
|
|
6991
|
+
);
|
|
6992
|
+
if (
|
|
6993
|
+
!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(soname) ||
|
|
6994
|
+
path.posix.basename(soname) !== soname
|
|
6995
|
+
) {
|
|
6996
|
+
throw new Error(`${prefix}_soname_invalid`);
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
5796
6999
|
}
|
|
5797
7000
|
|
|
5798
|
-
if (dynamicExecutable) {
|
|
7001
|
+
if (dynamicExecutable || dependencyObject) {
|
|
5799
7002
|
dynamicMetadata = Object.freeze({
|
|
5800
|
-
interpreter,
|
|
7003
|
+
...(dynamicExecutable ? { interpreter } : {}),
|
|
5801
7004
|
needed: Object.freeze(needed),
|
|
7005
|
+
soname,
|
|
5802
7006
|
});
|
|
5803
7007
|
}
|
|
5804
7008
|
}
|
|
5805
7009
|
|
|
5806
7010
|
const sha256 = hashLinuxOpenFile(runtime, fd, Number(before.size));
|
|
5807
7011
|
const after = runtime.fs.fstatSync(fd);
|
|
5808
|
-
if (
|
|
7012
|
+
if (
|
|
7013
|
+
!linuxOpenStatMatches(before, after) ||
|
|
7014
|
+
(identity.sha256 !== undefined && sha256 !== identity.sha256)
|
|
7015
|
+
) {
|
|
5809
7016
|
throw new Error("native_entry_changed_during_elf_attestation");
|
|
5810
7017
|
}
|
|
5811
7018
|
return Object.freeze({
|
|
5812
|
-
runtime:
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
? "
|
|
5816
|
-
: "
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
7019
|
+
runtime: dependencyObject
|
|
7020
|
+
? "native-shared-object"
|
|
7021
|
+
: dynamicExecutable
|
|
7022
|
+
? "native-dynamic-elf"
|
|
7023
|
+
: "native-static-elf",
|
|
7024
|
+
format: dependencyObject
|
|
7025
|
+
? "elf64-shared-object-et-dyn"
|
|
7026
|
+
: dynamicExecutable
|
|
7027
|
+
? elfType === LINUX_ELF_TYPE_DYN
|
|
7028
|
+
? "elf64-dynamic-pie-et-dyn"
|
|
7029
|
+
: "elf64-dynamic-et-exec"
|
|
7030
|
+
: elfType === LINUX_ELF_TYPE_DYN
|
|
7031
|
+
? "elf64-static-pie-shaped-et-dyn"
|
|
7032
|
+
: "elf64-static-et-exec",
|
|
5820
7033
|
architecture: runtime.arch,
|
|
5821
7034
|
machine: expectedMachine,
|
|
5822
7035
|
loaderPageBytes: Number(runtimePageSize),
|
|
5823
7036
|
programHeaders: programHeaderCount,
|
|
7037
|
+
sha256,
|
|
5824
7038
|
...(dynamicMetadata || {}),
|
|
5825
7039
|
});
|
|
5826
7040
|
}
|
|
@@ -5856,17 +7070,22 @@ function linuxFdMountId(runtime, fd) {
|
|
|
5856
7070
|
}
|
|
5857
7071
|
|
|
5858
7072
|
function closeLinuxPinnedMounts(runtime, mounts) {
|
|
7073
|
+
const closed = new Set();
|
|
5859
7074
|
for (const mount of mounts || []) {
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
7075
|
+
for (const field of ["fd", "scrubberFd"]) {
|
|
7076
|
+
if (!Number.isInteger(mount?.[field])) continue;
|
|
7077
|
+
const fd = mount[field];
|
|
7078
|
+
// Clear ownership before close so repeated cleanup cannot close an
|
|
7079
|
+
// unrelated descriptor if the OS has already reused this number.
|
|
7080
|
+
mount[field] = null;
|
|
7081
|
+
if (closed.has(fd)) continue;
|
|
7082
|
+
closed.add(fd);
|
|
7083
|
+
try {
|
|
7084
|
+
runtime.fs.closeSync(fd);
|
|
7085
|
+
} catch {
|
|
7086
|
+
// Best effort. The process is already fail-closed if a pin cannot be
|
|
7087
|
+
// created; cleanup must not replace the original boundary error.
|
|
7088
|
+
}
|
|
5870
7089
|
}
|
|
5871
7090
|
}
|
|
5872
7091
|
}
|
|
@@ -6042,7 +7261,10 @@ function pinLinuxPluginTree(runtime, pluginRoot, rootIdentity) {
|
|
|
6042
7261
|
}
|
|
6043
7262
|
}
|
|
6044
7263
|
|
|
6045
|
-
function buildLinuxNetworkSeccompFilter(
|
|
7264
|
+
function buildLinuxNetworkSeccompFilter(
|
|
7265
|
+
arch,
|
|
7266
|
+
{ runtimePathnameClosure = false } = {},
|
|
7267
|
+
) {
|
|
6046
7268
|
const architecture = LINUX_SECCOMP_FILTERS[arch];
|
|
6047
7269
|
if (!architecture) {
|
|
6048
7270
|
throw new Error(`unsupported_seccomp_architecture:${String(arch)}`);
|
|
@@ -6071,8 +7293,54 @@ function buildLinuxNetworkSeccompFilter(arch) {
|
|
|
6071
7293
|
// syscall. No ring descriptor is inherited by this narrow launch route.
|
|
6072
7294
|
[0x15, 0, 1, architecture.ioUringSetupSyscall],
|
|
6073
7295
|
[0x06, 0, 0, 0x00050001],
|
|
6074
|
-
[0x06, 0, 0, 0x7fff0000],
|
|
6075
7296
|
);
|
|
7297
|
+
if (runtimePathnameClosure) {
|
|
7298
|
+
// clone3 receives a pointer to struct clone_args, which classic seccomp BPF
|
|
7299
|
+
// cannot safely dereference. Make the syscall unavailable so glibc can
|
|
7300
|
+
// fall back to classic clone for ordinary threads; the classic call below
|
|
7301
|
+
// rejects only namespace-producing flags.
|
|
7302
|
+
instructions.push(
|
|
7303
|
+
[0x15, 0, 1, architecture.clone3Syscall],
|
|
7304
|
+
[0x06, 0, 0, 0x00050026],
|
|
7305
|
+
);
|
|
7306
|
+
for (const syscall of [
|
|
7307
|
+
// recvmsg(2) and recvmmsg(2) are the two receive APIs capable of
|
|
7308
|
+
// importing SCM_RIGHTS descriptors. recvfrom/read cannot receive
|
|
7309
|
+
// ancillary descriptors, and sendmsg does not import one.
|
|
7310
|
+
architecture.recvmsgSyscall,
|
|
7311
|
+
architecture.recvmmsgSyscall,
|
|
7312
|
+
architecture.pidfdGetfdSyscall,
|
|
7313
|
+
architecture.openByHandleAtSyscall,
|
|
7314
|
+
// A read-only outer namespace is not a closure if the target can create
|
|
7315
|
+
// or join another namespace and mount writable bytes over a visible path.
|
|
7316
|
+
architecture.unshareSyscall,
|
|
7317
|
+
architecture.setnsSyscall,
|
|
7318
|
+
architecture.mountSyscall,
|
|
7319
|
+
architecture.umount2Syscall,
|
|
7320
|
+
architecture.pivotRootSyscall,
|
|
7321
|
+
architecture.openTreeSyscall,
|
|
7322
|
+
architecture.moveMountSyscall,
|
|
7323
|
+
architecture.fsopenSyscall,
|
|
7324
|
+
architecture.fsconfigSyscall,
|
|
7325
|
+
architecture.fsmountSyscall,
|
|
7326
|
+
architecture.fspickSyscall,
|
|
7327
|
+
architecture.mountSetattrSyscall,
|
|
7328
|
+
]) {
|
|
7329
|
+
instructions.push([0x15, 0, 1, syscall], [0x06, 0, 0, 0x00050001]);
|
|
7330
|
+
}
|
|
7331
|
+
instructions.push(
|
|
7332
|
+
// seccomp_data.args[0] starts at byte 16. All supported ABIs are
|
|
7333
|
+
// little-endian, so loading this word inspects the low classic-clone
|
|
7334
|
+
// flags. Preserve fork/pthread clone calls while rejecting NEWUSER/NEWNS.
|
|
7335
|
+
[0x15, 0, 5, architecture.cloneSyscall],
|
|
7336
|
+
[0x20, 0, 0, 16],
|
|
7337
|
+
[0x54, 0, 0, LINUX_NAMESPACE_CLONE_FLAGS],
|
|
7338
|
+
[0x15, 1, 0, 0],
|
|
7339
|
+
[0x06, 0, 0, 0x00050001],
|
|
7340
|
+
[0x06, 0, 0, 0x7fff0000],
|
|
7341
|
+
);
|
|
7342
|
+
}
|
|
7343
|
+
instructions.push([0x06, 0, 0, 0x7fff0000]);
|
|
6076
7344
|
const buffer = Buffer.alloc(instructions.length * 8);
|
|
6077
7345
|
instructions.forEach(([code, jumpTrue, jumpFalse, value], index) => {
|
|
6078
7346
|
const offset = index * 8;
|
|
@@ -6088,8 +7356,8 @@ function buildLinuxNetworkSeccompFilter(arch) {
|
|
|
6088
7356
|
};
|
|
6089
7357
|
}
|
|
6090
7358
|
|
|
6091
|
-
function pinLinuxNetworkSeccompFilter(runtime) {
|
|
6092
|
-
const filter = buildLinuxNetworkSeccompFilter(runtime.arch);
|
|
7359
|
+
function pinLinuxNetworkSeccompFilter(runtime, options = {}) {
|
|
7360
|
+
const filter = buildLinuxNetworkSeccompFilter(runtime.arch, options);
|
|
6093
7361
|
let fd;
|
|
6094
7362
|
try {
|
|
6095
7363
|
for (const method of [
|
|
@@ -6159,7 +7427,9 @@ function pinLinuxNetworkSeccompFilter(runtime) {
|
|
|
6159
7427
|
childFd: null,
|
|
6160
7428
|
arch: filter.arch,
|
|
6161
7429
|
sha256: filter.sha256,
|
|
6162
|
-
policy:
|
|
7430
|
+
policy: options.runtimePathnameClosure
|
|
7431
|
+
? "deny-network-creation-loader-fd-acquisition-and-namespace-mutation"
|
|
7432
|
+
: "deny-network-creation",
|
|
6163
7433
|
};
|
|
6164
7434
|
} catch (error) {
|
|
6165
7435
|
if (fd !== undefined) {
|
|
@@ -6336,42 +7606,119 @@ function pinLinuxBubblewrapSupervisor(runtime) {
|
|
|
6336
7606
|
}
|
|
6337
7607
|
}
|
|
6338
7608
|
|
|
6339
|
-
function
|
|
7609
|
+
function pinLinuxBubblewrapDescriptorScrubber(runtime) {
|
|
6340
7610
|
let fd;
|
|
6341
7611
|
try {
|
|
7612
|
+
for (const method of [
|
|
7613
|
+
"openSync",
|
|
7614
|
+
"readSync",
|
|
7615
|
+
"fstatSync",
|
|
7616
|
+
"statSync",
|
|
7617
|
+
"lstatSync",
|
|
7618
|
+
"closeSync",
|
|
7619
|
+
]) {
|
|
7620
|
+
if (typeof runtime.fs?.[method] !== "function") {
|
|
7621
|
+
throw new Error(`descriptor_scrubber_${method}_unavailable`);
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
if (
|
|
7625
|
+
linuxRealpath(runtime, LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH) !==
|
|
7626
|
+
LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH
|
|
7627
|
+
) {
|
|
7628
|
+
throw new Error("descriptor_scrubber_realpath_changed");
|
|
7629
|
+
}
|
|
7630
|
+
const pathBefore = runtime.fs.lstatSync(
|
|
7631
|
+
LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH,
|
|
7632
|
+
{ bigint: true },
|
|
7633
|
+
);
|
|
6342
7634
|
if (
|
|
6343
|
-
|
|
6344
|
-
!
|
|
6345
|
-
!supervisorPin.attestation
|
|
7635
|
+
pathBefore.isSymbolicLink?.() === true ||
|
|
7636
|
+
!linuxBubblewrapSupervisorStatValid(pathBefore)
|
|
6346
7637
|
) {
|
|
6347
|
-
throw new Error("
|
|
7638
|
+
throw new Error("descriptor_scrubber_path_unattested");
|
|
6348
7639
|
}
|
|
6349
7640
|
const constants = runtime.fs.constants || fs.constants;
|
|
6350
7641
|
const flags =
|
|
6351
|
-
Number(constants.O_RDONLY) |
|
|
6352
|
-
|
|
7642
|
+
Number(constants.O_RDONLY) |
|
|
7643
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
7644
|
+
Number(constants.O_NONBLOCK || 0);
|
|
7645
|
+
fd = runtime.fs.openSync(LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH, flags);
|
|
6353
7646
|
const openedBefore = runtime.fs.fstatSync(fd, { bigint: true });
|
|
7647
|
+
const currentPath = runtime.fs.statSync(
|
|
7648
|
+
LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH,
|
|
7649
|
+
{ bigint: true },
|
|
7650
|
+
);
|
|
6354
7651
|
if (
|
|
6355
|
-
!
|
|
7652
|
+
!linuxBubblewrapSupervisorStatValid(openedBefore) ||
|
|
7653
|
+
!linuxOpenStatMatches(pathBefore, openedBefore) ||
|
|
7654
|
+
!linuxOpenStatMatches(openedBefore, currentPath)
|
|
6356
7655
|
) {
|
|
6357
|
-
throw new Error("
|
|
7656
|
+
throw new Error("descriptor_scrubber_identity_changed");
|
|
6358
7657
|
}
|
|
6359
|
-
const sha256 = hashLinuxOpenFile(
|
|
6360
|
-
runtime,
|
|
6361
|
-
fd,
|
|
6362
|
-
supervisorPin.attestation.bytes,
|
|
6363
|
-
);
|
|
7658
|
+
const sha256 = hashLinuxOpenFile(runtime, fd, Number(openedBefore.size));
|
|
6364
7659
|
const openedAfter = runtime.fs.fstatSync(fd, { bigint: true });
|
|
7660
|
+
const pathAfter = runtime.fs.statSync(
|
|
7661
|
+
LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH,
|
|
7662
|
+
{ bigint: true },
|
|
7663
|
+
);
|
|
6365
7664
|
if (
|
|
6366
7665
|
!linuxOpenStatMatches(openedBefore, openedAfter) ||
|
|
6367
|
-
|
|
7666
|
+
!linuxOpenStatMatches(openedAfter, pathAfter)
|
|
6368
7667
|
) {
|
|
6369
|
-
throw new Error("
|
|
7668
|
+
throw new Error("descriptor_scrubber_identity_changed");
|
|
6370
7669
|
}
|
|
6371
7670
|
return {
|
|
6372
7671
|
fd,
|
|
6373
|
-
|
|
7672
|
+
openedStat: openedAfter,
|
|
7673
|
+
attestation: Object.freeze({
|
|
7674
|
+
path: LINUX_BWRAP_DESCRIPTOR_SCRUBBER_PATH,
|
|
7675
|
+
fileId: Object.freeze({
|
|
7676
|
+
dev: String(openedAfter.dev),
|
|
7677
|
+
ino: String(openedAfter.ino),
|
|
7678
|
+
}),
|
|
7679
|
+
sha256,
|
|
7680
|
+
bytes: Number(openedAfter.size),
|
|
7681
|
+
mtimeMs: Number(openedAfter.mtimeMs),
|
|
7682
|
+
mode: Number(openedAfter.mode),
|
|
7683
|
+
uid: Number(openedAfter.uid),
|
|
7684
|
+
gid: Number(openedAfter.gid),
|
|
7685
|
+
}),
|
|
6374
7686
|
};
|
|
7687
|
+
} catch (error) {
|
|
7688
|
+
if (fd !== undefined) {
|
|
7689
|
+
try {
|
|
7690
|
+
runtime.fs.closeSync(fd);
|
|
7691
|
+
} catch {
|
|
7692
|
+
// Preserve the original attestation error.
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
throw error;
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
|
|
7699
|
+
function openLinuxBubblewrapPinnedExecutableReader(runtime, pin, label) {
|
|
7700
|
+
let fd;
|
|
7701
|
+
try {
|
|
7702
|
+
if (!Number.isInteger(pin?.fd) || !pin.openedStat || !pin.attestation) {
|
|
7703
|
+
throw new Error(`${label}_pin_missing`);
|
|
7704
|
+
}
|
|
7705
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
7706
|
+
const flags =
|
|
7707
|
+
Number(constants.O_RDONLY) | Number(constants.O_NONBLOCK || 0);
|
|
7708
|
+
fd = runtime.fs.openSync(`/proc/self/fd/${pin.fd}`, flags);
|
|
7709
|
+
const openedBefore = runtime.fs.fstatSync(fd, { bigint: true });
|
|
7710
|
+
if (!linuxBubblewrapPinnedStatMatches(pin.openedStat, openedBefore)) {
|
|
7711
|
+
throw new Error(`${label}_launch_reader_identity_changed`);
|
|
7712
|
+
}
|
|
7713
|
+
const sha256 = hashLinuxOpenFile(runtime, fd, pin.attestation.bytes);
|
|
7714
|
+
const openedAfter = runtime.fs.fstatSync(fd, { bigint: true });
|
|
7715
|
+
if (
|
|
7716
|
+
!linuxOpenStatMatches(openedBefore, openedAfter) ||
|
|
7717
|
+
sha256 !== pin.attestation.sha256
|
|
7718
|
+
) {
|
|
7719
|
+
throw new Error(`${label}_launch_reader_identity_changed`);
|
|
7720
|
+
}
|
|
7721
|
+
return fd;
|
|
6375
7722
|
} catch (error) {
|
|
6376
7723
|
if (fd !== undefined) {
|
|
6377
7724
|
try {
|
|
@@ -6384,17 +7731,46 @@ function openLinuxBubblewrapSupervisorLaunch(runtime, supervisorPin) {
|
|
|
6384
7731
|
}
|
|
6385
7732
|
}
|
|
6386
7733
|
|
|
6387
|
-
function
|
|
7734
|
+
function openLinuxBubblewrapSupervisorLaunch(
|
|
7735
|
+
runtime,
|
|
7736
|
+
supervisorPin,
|
|
7737
|
+
descriptorScrubberPin,
|
|
7738
|
+
) {
|
|
7739
|
+
let fd;
|
|
7740
|
+
let scrubberFd;
|
|
7741
|
+
try {
|
|
7742
|
+
fd = openLinuxBubblewrapPinnedExecutableReader(
|
|
7743
|
+
runtime,
|
|
7744
|
+
supervisorPin,
|
|
7745
|
+
"supervisor",
|
|
7746
|
+
);
|
|
7747
|
+
scrubberFd = openLinuxBubblewrapPinnedExecutableReader(
|
|
7748
|
+
runtime,
|
|
7749
|
+
descriptorScrubberPin,
|
|
7750
|
+
"descriptor_scrubber",
|
|
7751
|
+
);
|
|
7752
|
+
return { fd, scrubberFd };
|
|
7753
|
+
} catch (error) {
|
|
7754
|
+
closeLinuxPinnedMounts(runtime, [{ fd }, { fd: scrubberFd }]);
|
|
7755
|
+
throw error;
|
|
7756
|
+
}
|
|
7757
|
+
}
|
|
7758
|
+
|
|
7759
|
+
function attestLinuxBubblewrapSupervisorPin(
|
|
7760
|
+
runtime,
|
|
7761
|
+
supervisorPin,
|
|
7762
|
+
label = "supervisor",
|
|
7763
|
+
) {
|
|
6388
7764
|
if (
|
|
6389
7765
|
!Number.isInteger(supervisorPin?.fd) ||
|
|
6390
7766
|
!supervisorPin.openedStat ||
|
|
6391
7767
|
!supervisorPin.attestation
|
|
6392
7768
|
) {
|
|
6393
|
-
throw new Error(
|
|
7769
|
+
throw new Error(`${label}_pin_missing`);
|
|
6394
7770
|
}
|
|
6395
7771
|
const before = runtime.fs.fstatSync(supervisorPin.fd, { bigint: true });
|
|
6396
7772
|
if (!linuxBubblewrapPinnedStatMatches(supervisorPin.openedStat, before)) {
|
|
6397
|
-
throw new Error(
|
|
7773
|
+
throw new Error(`${label}_identity_changed`);
|
|
6398
7774
|
}
|
|
6399
7775
|
const sha256 = hashLinuxOpenFile(
|
|
6400
7776
|
runtime,
|
|
@@ -6406,17 +7782,72 @@ function attestLinuxBubblewrapSupervisorPin(runtime, supervisorPin) {
|
|
|
6406
7782
|
!linuxOpenStatMatches(before, after) ||
|
|
6407
7783
|
sha256 !== supervisorPin.attestation.sha256
|
|
6408
7784
|
) {
|
|
6409
|
-
throw new Error(
|
|
7785
|
+
throw new Error(`${label}_identity_changed`);
|
|
7786
|
+
}
|
|
7787
|
+
}
|
|
7788
|
+
|
|
7789
|
+
function linuxBubblewrapScrubbedInvocation(
|
|
7790
|
+
supervisorLaunch,
|
|
7791
|
+
executableArgs,
|
|
7792
|
+
stdio,
|
|
7793
|
+
) {
|
|
7794
|
+
const scrubberChildFd = stdio.length - 1;
|
|
7795
|
+
let explicitLayout = true;
|
|
7796
|
+
const parentFds = new Set();
|
|
7797
|
+
for (
|
|
7798
|
+
let index = LINUX_BWRAP_SUPERVISOR_CHILD_FD;
|
|
7799
|
+
index <= scrubberChildFd;
|
|
7800
|
+
index += 1
|
|
7801
|
+
) {
|
|
7802
|
+
const parentFd = stdio[index];
|
|
7803
|
+
if (
|
|
7804
|
+
!Object.hasOwn(stdio, index) ||
|
|
7805
|
+
!Number.isSafeInteger(parentFd) ||
|
|
7806
|
+
parentFd < 0 ||
|
|
7807
|
+
parentFds.has(parentFd)
|
|
7808
|
+
) {
|
|
7809
|
+
explicitLayout = false;
|
|
7810
|
+
break;
|
|
7811
|
+
}
|
|
7812
|
+
parentFds.add(parentFd);
|
|
7813
|
+
}
|
|
7814
|
+
if (
|
|
7815
|
+
!Number.isInteger(supervisorLaunch?.fd) ||
|
|
7816
|
+
!Number.isInteger(supervisorLaunch?.scrubberFd) ||
|
|
7817
|
+
stdio.length < 5 ||
|
|
7818
|
+
stdio[LINUX_BWRAP_SUPERVISOR_CHILD_FD] !== supervisorLaunch.fd ||
|
|
7819
|
+
stdio[scrubberChildFd] !== supervisorLaunch.scrubberFd ||
|
|
7820
|
+
!explicitLayout
|
|
7821
|
+
) {
|
|
7822
|
+
throw new Error("descriptor_scrubber_launch_layout_invalid");
|
|
6410
7823
|
}
|
|
7824
|
+
return buildLinuxBwrapDescriptorScrubbedLaunch({
|
|
7825
|
+
scrubberChildFd,
|
|
7826
|
+
preservedMaxFd: scrubberChildFd - 1,
|
|
7827
|
+
executableChildFd: LINUX_BWRAP_SUPERVISOR_CHILD_FD,
|
|
7828
|
+
executableArgs,
|
|
7829
|
+
});
|
|
6411
7830
|
}
|
|
6412
7831
|
|
|
6413
7832
|
function attestLinuxBubblewrapCapabilities(runtime, supervisorLaunch) {
|
|
6414
7833
|
let result;
|
|
6415
7834
|
try {
|
|
6416
|
-
|
|
7835
|
+
const stdio = [
|
|
7836
|
+
"ignore",
|
|
7837
|
+
"pipe",
|
|
7838
|
+
"pipe",
|
|
7839
|
+
supervisorLaunch.fd,
|
|
7840
|
+
supervisorLaunch.scrubberFd,
|
|
7841
|
+
];
|
|
7842
|
+
const invocation = linuxBubblewrapScrubbedInvocation(
|
|
7843
|
+
supervisorLaunch,
|
|
7844
|
+
["--help"],
|
|
7845
|
+
stdio,
|
|
7846
|
+
);
|
|
7847
|
+
result = runtime.spawnSync(invocation.command, invocation.args, {
|
|
6417
7848
|
shell: false,
|
|
6418
7849
|
encoding: "utf8",
|
|
6419
|
-
stdio
|
|
7850
|
+
stdio,
|
|
6420
7851
|
timeout: 10_000,
|
|
6421
7852
|
env: {
|
|
6422
7853
|
PATH: "/usr/bin:/bin",
|
|
@@ -6452,18 +7883,65 @@ function attestLinuxBubblewrapCapabilities(runtime, supervisorLaunch) {
|
|
|
6452
7883
|
return { ok: true, reason: null };
|
|
6453
7884
|
}
|
|
6454
7885
|
|
|
6455
|
-
function
|
|
6456
|
-
const
|
|
6457
|
-
|
|
7886
|
+
function parseLinuxLddResolution(output) {
|
|
7887
|
+
const byDestination = new Map();
|
|
7888
|
+
const byLoaderName = new Map();
|
|
7889
|
+
const byBasename = new Map();
|
|
7890
|
+
for (const rawLine of String(output || "").split(/\r?\n/)) {
|
|
7891
|
+
const line = rawLine.trim();
|
|
7892
|
+
if (!line) continue;
|
|
6458
7893
|
if (line.includes("=> not found")) {
|
|
6459
7894
|
throw new Error("runtime_dependency_missing");
|
|
6460
7895
|
}
|
|
6461
|
-
|
|
6462
|
-
const
|
|
6463
|
-
|
|
6464
|
-
|
|
7896
|
+
if (/^linux-vdso(?:\.so(?:\.\d+)?)?\s+\(/.test(line)) continue;
|
|
7897
|
+
const resolved = line.match(
|
|
7898
|
+
/^([A-Za-z0-9][A-Za-z0-9._+-]{0,254})\s+=>\s+(\/[^\s(]+)\s+\(/,
|
|
7899
|
+
);
|
|
7900
|
+
const direct = line.match(/^(\/[^\s(]+)\s+\(/);
|
|
7901
|
+
if (!resolved && !direct) {
|
|
7902
|
+
throw new Error("runtime_dependency_resolution_output_invalid");
|
|
7903
|
+
}
|
|
7904
|
+
const loaderName = resolved?.[1] || path.posix.basename(direct[1]);
|
|
7905
|
+
const destination = resolved?.[2] || direct[1];
|
|
7906
|
+
if (path.posix.normalize(destination) !== destination) {
|
|
7907
|
+
throw new Error("runtime_dependency_outside_system_library_roots");
|
|
7908
|
+
}
|
|
7909
|
+
if (
|
|
7910
|
+
!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(loaderName) ||
|
|
7911
|
+
path.posix.basename(destination) !== loaderName
|
|
7912
|
+
) {
|
|
7913
|
+
throw new Error("runtime_dependency_resolution_alias_unsupported");
|
|
7914
|
+
}
|
|
7915
|
+
const priorLoaderDestination = byLoaderName.get(loaderName);
|
|
7916
|
+
if (
|
|
7917
|
+
priorLoaderDestination !== undefined &&
|
|
7918
|
+
priorLoaderDestination !== destination
|
|
7919
|
+
) {
|
|
7920
|
+
throw new Error("runtime_dependency_resolution_ambiguous");
|
|
7921
|
+
}
|
|
7922
|
+
const basename = path.posix.basename(destination);
|
|
7923
|
+
const priorBasenameDestination = byBasename.get(basename);
|
|
7924
|
+
if (
|
|
7925
|
+
priorBasenameDestination !== undefined &&
|
|
7926
|
+
priorBasenameDestination !== destination
|
|
7927
|
+
) {
|
|
7928
|
+
throw new Error("runtime_dependency_resolution_ambiguous");
|
|
7929
|
+
}
|
|
7930
|
+
byLoaderName.set(loaderName, destination);
|
|
7931
|
+
byBasename.set(basename, destination);
|
|
7932
|
+
const existing = byDestination.get(destination) || new Set();
|
|
7933
|
+
existing.add(loaderName);
|
|
7934
|
+
byDestination.set(destination, existing);
|
|
7935
|
+
}
|
|
7936
|
+
if (byDestination.size === 0) {
|
|
7937
|
+
throw new Error("runtime_dependency_resolution_empty");
|
|
6465
7938
|
}
|
|
6466
|
-
return [...
|
|
7939
|
+
return [...byDestination.entries()]
|
|
7940
|
+
.map(([destination, loaderNames]) => ({
|
|
7941
|
+
destination,
|
|
7942
|
+
loaderNames: [...loaderNames].sort(),
|
|
7943
|
+
}))
|
|
7944
|
+
.sort((left, right) => left.destination.localeCompare(right.destination));
|
|
6467
7945
|
}
|
|
6468
7946
|
|
|
6469
7947
|
function collectLinuxNodeRuntimeMounts(runtime, runtimeIdentity) {
|
|
@@ -6509,12 +7987,15 @@ function collectLinuxNodeRuntimeMounts(runtime, runtimeIdentity) {
|
|
|
6509
7987
|
{
|
|
6510
7988
|
source: runtimeIdentity.realPath,
|
|
6511
7989
|
destination: "/opt/chainless/runtime/node",
|
|
7990
|
+
loaderNames: [],
|
|
6512
7991
|
},
|
|
6513
7992
|
];
|
|
6514
|
-
for (const destination of
|
|
7993
|
+
for (const { destination, loaderNames } of parseLinuxLddResolution(
|
|
7994
|
+
result.stdout,
|
|
7995
|
+
)) {
|
|
6515
7996
|
if (
|
|
6516
7997
|
path.posix.normalize(destination) !== destination ||
|
|
6517
|
-
!["/lib/", "/lib64/", "/usr/lib/"].some((prefix) =>
|
|
7998
|
+
!["/lib/", "/lib64/", "/usr/lib/", "/usr/lib64/"].some((prefix) =>
|
|
6518
7999
|
destination.startsWith(prefix),
|
|
6519
8000
|
)
|
|
6520
8001
|
) {
|
|
@@ -6524,14 +8005,18 @@ function collectLinuxNodeRuntimeMounts(runtime, runtimeIdentity) {
|
|
|
6524
8005
|
if (!source) {
|
|
6525
8006
|
throw new Error("runtime_dependency_unattested");
|
|
6526
8007
|
}
|
|
6527
|
-
mounts.push({ source, destination });
|
|
8008
|
+
mounts.push({ source, destination, loaderNames });
|
|
6528
8009
|
}
|
|
6529
8010
|
if (runtime.fs.existsSync("/etc/ld.so.cache")) {
|
|
6530
8011
|
const source = attestLinuxRootOwnedFile(runtime, "/etc/ld.so.cache");
|
|
6531
8012
|
if (!source) {
|
|
6532
8013
|
throw new Error("loader_cache_unattested");
|
|
6533
8014
|
}
|
|
6534
|
-
mounts.push({
|
|
8015
|
+
mounts.push({
|
|
8016
|
+
source,
|
|
8017
|
+
destination: "/etc/ld.so.cache",
|
|
8018
|
+
loaderNames: [],
|
|
8019
|
+
});
|
|
6535
8020
|
}
|
|
6536
8021
|
const byDestination = new Map();
|
|
6537
8022
|
for (const mount of mounts) {
|
|
@@ -6539,7 +8024,16 @@ function collectLinuxNodeRuntimeMounts(runtime, runtimeIdentity) {
|
|
|
6539
8024
|
if (existing && existing.source !== mount.source) {
|
|
6540
8025
|
throw new Error("runtime_mount_collision");
|
|
6541
8026
|
}
|
|
6542
|
-
|
|
8027
|
+
if (existing) {
|
|
8028
|
+
existing.loaderNames = [
|
|
8029
|
+
...new Set([
|
|
8030
|
+
...(existing.loaderNames || []),
|
|
8031
|
+
...(mount.loaderNames || []),
|
|
8032
|
+
]),
|
|
8033
|
+
].sort();
|
|
8034
|
+
} else {
|
|
8035
|
+
byDestination.set(mount.destination, mount);
|
|
8036
|
+
}
|
|
6543
8037
|
}
|
|
6544
8038
|
return [...byDestination.values()].sort((left, right) =>
|
|
6545
8039
|
left.destination.localeCompare(right.destination),
|
|
@@ -6559,9 +8053,15 @@ function pinLinuxRuntimeMounts(runtime, runtimeMounts, runtimeIdentity) {
|
|
|
6559
8053
|
mtimeMs: runtimeIdentity.mtimeMs,
|
|
6560
8054
|
}
|
|
6561
8055
|
: runtime.fs.statSync(mount.source);
|
|
6562
|
-
mounts.push(
|
|
6563
|
-
pinLinuxRegularFile(
|
|
6564
|
-
|
|
8056
|
+
mounts.push({
|
|
8057
|
+
...pinLinuxRegularFile(
|
|
8058
|
+
runtime,
|
|
8059
|
+
mount.source,
|
|
8060
|
+
mount.destination,
|
|
8061
|
+
expected,
|
|
8062
|
+
),
|
|
8063
|
+
loaderNames: Object.freeze([...(mount.loaderNames || [])]),
|
|
8064
|
+
});
|
|
6565
8065
|
}
|
|
6566
8066
|
return mounts;
|
|
6567
8067
|
} catch (error) {
|
|
@@ -6570,15 +8070,22 @@ function pinLinuxRuntimeMounts(runtime, runtimeMounts, runtimeIdentity) {
|
|
|
6570
8070
|
}
|
|
6571
8071
|
}
|
|
6572
8072
|
|
|
6573
|
-
// Bind
|
|
6574
|
-
//
|
|
6575
|
-
//
|
|
6576
|
-
|
|
8073
|
+
// Bind the kernel-selected interpreter and recursively parse every DT_NEEDED
|
|
8074
|
+
// edge reachable from the entry through the exact root-owned descriptors that
|
|
8075
|
+
// will be mounted into the sandbox. This proves only the loader's initial ELF
|
|
8076
|
+
// graph. It deliberately does not claim runtime dlopen closure; writable
|
|
8077
|
+
// scratch mounts and executable-created code remain outside this evidence.
|
|
8078
|
+
function bindLinuxNativeDynamicRuntime(
|
|
8079
|
+
runtime,
|
|
8080
|
+
entryFormat,
|
|
8081
|
+
pinnedRuntimeMounts,
|
|
8082
|
+
) {
|
|
6577
8083
|
if (entryFormat?.runtime !== "native-dynamic-elf") return null;
|
|
6578
8084
|
if (
|
|
6579
8085
|
!linuxSystemDynamicPath(entryFormat.interpreter) ||
|
|
6580
8086
|
!Array.isArray(entryFormat.needed) ||
|
|
6581
|
-
entryFormat.needed.length > LINUX_ELF_MAX_NEEDED_ENTRIES
|
|
8087
|
+
entryFormat.needed.length > LINUX_ELF_MAX_NEEDED_ENTRIES ||
|
|
8088
|
+
entryFormat.soname !== null
|
|
6582
8089
|
) {
|
|
6583
8090
|
throw new Error("native_dynamic_metadata_invalid");
|
|
6584
8091
|
}
|
|
@@ -6590,64 +8097,290 @@ function bindLinuxNativeDynamicRuntime(entryFormat, pinnedRuntimeMounts) {
|
|
|
6590
8097
|
typeof mount?.fileId?.ino === "string" &&
|
|
6591
8098
|
Number.isSafeInteger(mount?.bytes) &&
|
|
6592
8099
|
mount.bytes > 0 &&
|
|
6593
|
-
Number.isFinite(mount?.mtimeMs)
|
|
8100
|
+
Number.isFinite(mount?.mtimeMs) &&
|
|
8101
|
+
Array.isArray(mount?.loaderNames) &&
|
|
8102
|
+
mount.loaderNames.every(
|
|
8103
|
+
(name) =>
|
|
8104
|
+
typeof name === "string" &&
|
|
8105
|
+
/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(name),
|
|
8106
|
+
),
|
|
6594
8107
|
);
|
|
8108
|
+
const resolveDependency = (dependency, { direct = false } = {}) => {
|
|
8109
|
+
if (
|
|
8110
|
+
typeof dependency !== "string" ||
|
|
8111
|
+
!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(dependency) ||
|
|
8112
|
+
path.posix.basename(dependency) !== dependency
|
|
8113
|
+
) {
|
|
8114
|
+
throw new Error("native_dynamic_dependency_name_invalid");
|
|
8115
|
+
}
|
|
8116
|
+
const matches = systemMounts.filter(
|
|
8117
|
+
(mount) =>
|
|
8118
|
+
path.posix.basename(mount.destination) === dependency &&
|
|
8119
|
+
mount.loaderNames.includes(dependency),
|
|
8120
|
+
);
|
|
8121
|
+
if (matches.length === 0) {
|
|
8122
|
+
throw new Error(
|
|
8123
|
+
direct
|
|
8124
|
+
? "native_dynamic_dependency_outside_direct_system_set"
|
|
8125
|
+
: "native_dynamic_recursive_dependency_outside_system_graph",
|
|
8126
|
+
);
|
|
8127
|
+
}
|
|
8128
|
+
if (matches.length !== 1) {
|
|
8129
|
+
throw new Error(
|
|
8130
|
+
direct
|
|
8131
|
+
? "native_dynamic_dependency_runtime_ambiguous"
|
|
8132
|
+
: "native_dynamic_recursive_dependency_runtime_ambiguous",
|
|
8133
|
+
);
|
|
8134
|
+
}
|
|
8135
|
+
return matches[0];
|
|
8136
|
+
};
|
|
6595
8137
|
const interpreterMount = systemMounts.find(
|
|
6596
8138
|
(mount) => mount.destination === entryFormat.interpreter,
|
|
6597
8139
|
);
|
|
6598
8140
|
if (!interpreterMount) {
|
|
6599
8141
|
throw new Error("native_dynamic_interpreter_outside_direct_system_set");
|
|
6600
8142
|
}
|
|
6601
|
-
const selected = new Map(
|
|
8143
|
+
const selected = new Map();
|
|
8144
|
+
const pending = [];
|
|
8145
|
+
let selectedBytes = 0;
|
|
8146
|
+
const select = (mount) => {
|
|
8147
|
+
if (selected.has(mount.destination)) return;
|
|
8148
|
+
if (selected.size >= LINUX_ELF_MAX_INITIAL_CLOSURE_FILES) {
|
|
8149
|
+
throw new Error("native_dynamic_recursive_file_count_exceeded");
|
|
8150
|
+
}
|
|
8151
|
+
if (
|
|
8152
|
+
!Number.isSafeInteger(mount.bytes) ||
|
|
8153
|
+
mount.bytes <= 0 ||
|
|
8154
|
+
selectedBytes > LINUX_ELF_MAX_INITIAL_CLOSURE_BYTES - mount.bytes
|
|
8155
|
+
) {
|
|
8156
|
+
throw new Error("native_dynamic_recursive_bytes_exceeded");
|
|
8157
|
+
}
|
|
8158
|
+
selectedBytes += mount.bytes;
|
|
8159
|
+
selected.set(mount.destination, mount);
|
|
8160
|
+
pending.push(mount);
|
|
8161
|
+
};
|
|
8162
|
+
select(interpreterMount);
|
|
8163
|
+
const edges = [];
|
|
6602
8164
|
for (const dependency of entryFormat.needed) {
|
|
8165
|
+
const target = resolveDependency(dependency, { direct: true });
|
|
8166
|
+
edges.push({ from: "$entry", name: dependency, to: target.destination });
|
|
8167
|
+
select(target);
|
|
8168
|
+
}
|
|
8169
|
+
const parsed = new Map();
|
|
8170
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
8171
|
+
const mount = pending[index];
|
|
8172
|
+
const metadata = inspectLinuxNativeElf(runtime, mount.fd, mount, {
|
|
8173
|
+
role: "dependency",
|
|
8174
|
+
});
|
|
8175
|
+
if (
|
|
8176
|
+
metadata.soname !== null &&
|
|
8177
|
+
(metadata.soname !== path.posix.basename(mount.destination) ||
|
|
8178
|
+
!mount.loaderNames.includes(metadata.soname))
|
|
8179
|
+
) {
|
|
8180
|
+
throw new Error("native_dependency_soname_alias_unsupported");
|
|
8181
|
+
}
|
|
8182
|
+
parsed.set(mount.destination, metadata);
|
|
8183
|
+
for (const dependency of metadata.needed || []) {
|
|
8184
|
+
if (edges.length >= LINUX_ELF_MAX_INITIAL_CLOSURE_EDGES) {
|
|
8185
|
+
throw new Error("native_dynamic_recursive_edge_count_exceeded");
|
|
8186
|
+
}
|
|
8187
|
+
const target = resolveDependency(dependency);
|
|
8188
|
+
edges.push({
|
|
8189
|
+
from: mount.destination,
|
|
8190
|
+
name: dependency,
|
|
8191
|
+
to: target.destination,
|
|
8192
|
+
});
|
|
8193
|
+
select(target);
|
|
8194
|
+
}
|
|
8195
|
+
}
|
|
8196
|
+
const members = [...selected.values()]
|
|
8197
|
+
.map((mount) => {
|
|
8198
|
+
const metadata = parsed.get(mount.destination);
|
|
8199
|
+
if (!metadata) {
|
|
8200
|
+
throw new Error("native_dynamic_recursive_member_unparsed");
|
|
8201
|
+
}
|
|
8202
|
+
return {
|
|
8203
|
+
destination: mount.destination,
|
|
8204
|
+
fileId: {
|
|
8205
|
+
dev: mount.fileId.dev,
|
|
8206
|
+
ino: mount.fileId.ino,
|
|
8207
|
+
},
|
|
8208
|
+
bytes: mount.bytes,
|
|
8209
|
+
mtimeMs: mount.mtimeMs,
|
|
8210
|
+
sha256: metadata.sha256,
|
|
8211
|
+
loaderNames: [...mount.loaderNames],
|
|
8212
|
+
needed: [...(metadata.needed || [])].sort(),
|
|
8213
|
+
soname: metadata.soname,
|
|
8214
|
+
};
|
|
8215
|
+
})
|
|
8216
|
+
.sort((left, right) => left.destination.localeCompare(right.destination));
|
|
8217
|
+
const orderedEdges = edges.sort((left, right) =>
|
|
8218
|
+
`${left.from}\0${left.name}\0${left.to}`.localeCompare(
|
|
8219
|
+
`${right.from}\0${right.name}\0${right.to}`,
|
|
8220
|
+
),
|
|
8221
|
+
);
|
|
8222
|
+
const digest = crypto
|
|
8223
|
+
.createHash("sha256")
|
|
8224
|
+
.update(
|
|
8225
|
+
JSON.stringify({
|
|
8226
|
+
version: 1,
|
|
8227
|
+
scope: LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_SCOPE,
|
|
8228
|
+
mechanism: LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_MECHANISM,
|
|
8229
|
+
interpreter: entryFormat.interpreter,
|
|
8230
|
+
directNeeded: [...entryFormat.needed].sort(),
|
|
8231
|
+
members,
|
|
8232
|
+
edges: orderedEdges,
|
|
8233
|
+
}),
|
|
8234
|
+
)
|
|
8235
|
+
.digest("hex");
|
|
8236
|
+
return Object.freeze({
|
|
8237
|
+
scope: LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_SCOPE,
|
|
8238
|
+
mechanism: LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_MECHANISM,
|
|
8239
|
+
interpreter: entryFormat.interpreter,
|
|
8240
|
+
dependencies: orderedEdges.length,
|
|
8241
|
+
files: members.length,
|
|
8242
|
+
bytes: selectedBytes,
|
|
8243
|
+
digest,
|
|
8244
|
+
});
|
|
8245
|
+
}
|
|
8246
|
+
|
|
8247
|
+
/**
|
|
8248
|
+
* Hash every regular file that will remain pathname-visible to a dynamic
|
|
8249
|
+
* native target. The final namespace is built only from these descriptor
|
|
8250
|
+
* mounts and a read-only synthetic root, so this set is the complete input to
|
|
8251
|
+
* the kernel ELF loader and libc dlopen pathname resolution. Anonymous
|
|
8252
|
+
* executable memory and a malicious program's own ELF loader are intentionally
|
|
8253
|
+
* outside this narrower, enforceable claim.
|
|
8254
|
+
*/
|
|
8255
|
+
function buildLinuxRuntimePathnameLoadSet(
|
|
8256
|
+
runtime,
|
|
8257
|
+
pinnedMounts,
|
|
8258
|
+
pluginTreeSnapshot,
|
|
8259
|
+
nativeDynamicClosure,
|
|
8260
|
+
) {
|
|
8261
|
+
if (
|
|
8262
|
+
nativeDynamicClosure?.scope !==
|
|
8263
|
+
LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_SCOPE ||
|
|
8264
|
+
nativeDynamicClosure?.mechanism !==
|
|
8265
|
+
LINUX_NATIVE_DYNAMIC_RECURSIVE_SYSTEM_GRAPH_MECHANISM ||
|
|
8266
|
+
pluginTreeSnapshot?.scope !== LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE ||
|
|
8267
|
+
!Array.isArray(pinnedMounts) ||
|
|
8268
|
+
pinnedMounts.length < 1 ||
|
|
8269
|
+
pinnedMounts.length > LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MAX_FILES
|
|
8270
|
+
) {
|
|
8271
|
+
throw new Error("native_runtime_pathname_load_set_invalid");
|
|
8272
|
+
}
|
|
8273
|
+
const destinations = new Set();
|
|
8274
|
+
let totalBytes = 0;
|
|
8275
|
+
const members = pinnedMounts.map((mount) => {
|
|
8276
|
+
const destination = mount?.destination;
|
|
8277
|
+
const pluginFile =
|
|
8278
|
+
typeof destination === "string" &&
|
|
8279
|
+
linuxPathWithin("/opt/chainless/plugin", destination);
|
|
8280
|
+
const runtimeFile = destination === "/opt/chainless/runtime/node";
|
|
8281
|
+
const systemLibrary = linuxSystemDynamicPath(destination);
|
|
8282
|
+
const loaderCache = destination === "/etc/ld.so.cache";
|
|
6603
8283
|
if (
|
|
6604
|
-
typeof
|
|
6605
|
-
|
|
8284
|
+
typeof destination !== "string" ||
|
|
8285
|
+
!path.posix.isAbsolute(destination) ||
|
|
8286
|
+
path.posix.normalize(destination) !== destination ||
|
|
8287
|
+
destinations.has(destination) ||
|
|
8288
|
+
(!pluginFile && !runtimeFile && !systemLibrary && !loaderCache) ||
|
|
8289
|
+
!Number.isInteger(mount?.fd)
|
|
6606
8290
|
) {
|
|
6607
|
-
throw new Error("
|
|
8291
|
+
throw new Error("native_runtime_pathname_load_set_member_invalid");
|
|
6608
8292
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
);
|
|
6612
|
-
if (
|
|
6613
|
-
|
|
8293
|
+
destinations.add(destination);
|
|
8294
|
+
const before = runtime.fs.fstatSync(mount.fd);
|
|
8295
|
+
const bytes = Number(before.size);
|
|
8296
|
+
if (
|
|
8297
|
+
!before.isFile() ||
|
|
8298
|
+
!Number.isSafeInteger(bytes) ||
|
|
8299
|
+
bytes < 0 ||
|
|
8300
|
+
bytes > LINUX_ATTESTED_FILE_MAX_BYTES ||
|
|
8301
|
+
String(before.dev) !== String(mount.fileId?.dev) ||
|
|
8302
|
+
String(before.ino) !== String(mount.fileId?.ino) ||
|
|
8303
|
+
bytes !== Number(mount.bytes) ||
|
|
8304
|
+
Number(before.mtimeMs) !== Number(mount.mtimeMs) ||
|
|
8305
|
+
totalBytes > LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MAX_BYTES - bytes
|
|
8306
|
+
) {
|
|
8307
|
+
throw new Error("native_runtime_pathname_load_set_member_changed");
|
|
6614
8308
|
}
|
|
6615
|
-
|
|
6616
|
-
|
|
8309
|
+
const sha256 = hashLinuxOpenFile(runtime, mount.fd, bytes);
|
|
8310
|
+
const after = runtime.fs.fstatSync(mount.fd);
|
|
8311
|
+
if (
|
|
8312
|
+
!linuxOpenStatMatches(before, after) ||
|
|
8313
|
+
((pluginFile || runtimeFile) &&
|
|
8314
|
+
(mount.contentSnapshot?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
|
|
8315
|
+
mount.contentSnapshot?.sha256 !== sha256 ||
|
|
8316
|
+
(runtimeFile &&
|
|
8317
|
+
mount.contentSnapshot?.scope !==
|
|
8318
|
+
LINUX_PLUGIN_RUNTIME_SNAPSHOT_SCOPE)))
|
|
8319
|
+
) {
|
|
8320
|
+
throw new Error("native_runtime_pathname_load_set_member_changed");
|
|
6617
8321
|
}
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
8322
|
+
totalBytes += bytes;
|
|
8323
|
+
return {
|
|
8324
|
+
destination,
|
|
8325
|
+
sourceKind: pluginFile
|
|
8326
|
+
? "plugin-content-snapshot"
|
|
8327
|
+
: runtimeFile
|
|
8328
|
+
? "broker-node-runtime"
|
|
8329
|
+
: systemLibrary
|
|
8330
|
+
? "root-owned-system-library"
|
|
8331
|
+
: "root-owned-loader-cache",
|
|
6623
8332
|
fileId: {
|
|
6624
|
-
dev:
|
|
6625
|
-
ino:
|
|
8333
|
+
dev: String(after.dev),
|
|
8334
|
+
ino: String(after.ino),
|
|
6626
8335
|
},
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
8336
|
+
sha256,
|
|
8337
|
+
bytes,
|
|
8338
|
+
mtimeMs: Number(after.mtimeMs),
|
|
8339
|
+
mountMode:
|
|
8340
|
+
mount.mountMode === "ro-bind-data" ? "ro-bind-data" : "ro-bind-fd",
|
|
8341
|
+
};
|
|
8342
|
+
});
|
|
8343
|
+
members.sort((left, right) =>
|
|
8344
|
+
left.destination.localeCompare(right.destination),
|
|
8345
|
+
);
|
|
8346
|
+
const loaderCacheVisible = members.some(
|
|
8347
|
+
({ sourceKind }) => sourceKind === "root-owned-loader-cache",
|
|
8348
|
+
);
|
|
8349
|
+
if (
|
|
8350
|
+
totalBytes < 1 ||
|
|
8351
|
+
!members.some(
|
|
8352
|
+
({ sourceKind }) => sourceKind === "plugin-content-snapshot",
|
|
8353
|
+
) ||
|
|
8354
|
+
!members.some(({ sourceKind }) => sourceKind === "broker-node-runtime") ||
|
|
8355
|
+
loaderCacheVisible !== runtime.fs.existsSync("/etc/ld.so.cache")
|
|
8356
|
+
) {
|
|
8357
|
+
throw new Error("native_runtime_pathname_load_set_incomplete");
|
|
8358
|
+
}
|
|
6631
8359
|
const digest = crypto
|
|
6632
8360
|
.createHash("sha256")
|
|
6633
8361
|
.update(
|
|
6634
8362
|
JSON.stringify({
|
|
6635
8363
|
version: 1,
|
|
6636
|
-
scope:
|
|
6637
|
-
mechanism:
|
|
6638
|
-
|
|
6639
|
-
|
|
8364
|
+
scope: LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_SCOPE,
|
|
8365
|
+
mechanism: LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MECHANISM,
|
|
8366
|
+
initialDynamicLoadClosureDigest: nativeDynamicClosure.digest,
|
|
8367
|
+
pluginTreeContentSnapshotDigest: pluginTreeSnapshot.digest,
|
|
6640
8368
|
members,
|
|
6641
8369
|
}),
|
|
6642
8370
|
)
|
|
6643
8371
|
.digest("hex");
|
|
6644
8372
|
return Object.freeze({
|
|
6645
|
-
scope:
|
|
6646
|
-
mechanism:
|
|
6647
|
-
interpreter: entryFormat.interpreter,
|
|
6648
|
-
dependencies: entryFormat.needed.length,
|
|
8373
|
+
scope: LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_SCOPE,
|
|
8374
|
+
mechanism: LINUX_NATIVE_RUNTIME_PATHNAME_CLOSURE_MECHANISM,
|
|
6649
8375
|
files: members.length,
|
|
8376
|
+
bytes: totalBytes,
|
|
6650
8377
|
digest,
|
|
8378
|
+
policyBound: true,
|
|
8379
|
+
writableFilesystems: false,
|
|
8380
|
+
procfsMounted: false,
|
|
8381
|
+
devfsMounted: false,
|
|
8382
|
+
scratchWritable: false,
|
|
8383
|
+
descriptorReopenPaths: false,
|
|
6651
8384
|
});
|
|
6652
8385
|
}
|
|
6653
8386
|
|
|
@@ -6669,6 +8402,7 @@ function buildLinuxBubblewrapPolicyArgs(
|
|
|
6669
8402
|
pinnedMounts,
|
|
6670
8403
|
environment,
|
|
6671
8404
|
seccompFilter,
|
|
8405
|
+
{ runtimePathnameClosure = false } = {},
|
|
6672
8406
|
) {
|
|
6673
8407
|
const directories = new Set([
|
|
6674
8408
|
"/opt",
|
|
@@ -6720,9 +8454,9 @@ function buildLinuxBubblewrapPolicyArgs(
|
|
|
6720
8454
|
}
|
|
6721
8455
|
// FD 3 is also the descriptor-backed executable used to start bubblewrap.
|
|
6722
8456
|
// Consume and close it during setup, then obscure this staging copy with the
|
|
6723
|
-
// later /run tmpfs. The
|
|
6724
|
-
// PID-namespace supervisor through procfs
|
|
6725
|
-
//
|
|
8457
|
+
// later /run tmpfs. The ordinary profile can still observe bubblewrap itself
|
|
8458
|
+
// as the PID-namespace supervisor through procfs. The dynamic-native runtime
|
|
8459
|
+
// closure profile does not mount procfs at all.
|
|
6726
8460
|
args.push(
|
|
6727
8461
|
"--perms",
|
|
6728
8462
|
"0000",
|
|
@@ -6743,40 +8477,56 @@ function buildLinuxBubblewrapPolicyArgs(
|
|
|
6743
8477
|
args.push("--ro-bind-fd", String(mount.childFd), mount.destination);
|
|
6744
8478
|
}
|
|
6745
8479
|
}
|
|
6746
|
-
args.push(
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
8480
|
+
args.push("--seccomp", String(seccompFilter.childFd), "--remount-ro", "/");
|
|
8481
|
+
if (runtimePathnameClosure) {
|
|
8482
|
+
// bubblewrap creates its root tmpfs with MS_NOSUID|MS_NODEV, not NOEXEC.
|
|
8483
|
+
// Keeping any writable tmpfs would therefore let an untrusted native write
|
|
8484
|
+
// a new .so and dlopen it. Only an empty /run overlay is retained to hide
|
|
8485
|
+
// the consumed supervisor staging copy, and it is remounted read-only
|
|
8486
|
+
// before the target starts. /tmp, /var/tmp, HOME, procfs, and devfs remain
|
|
8487
|
+
// empty directories on the read-only synthetic root.
|
|
8488
|
+
args.push(
|
|
8489
|
+
"--perms",
|
|
8490
|
+
"0755",
|
|
8491
|
+
"--size",
|
|
8492
|
+
String(16 * 1024 * 1024),
|
|
8493
|
+
"--tmpfs",
|
|
8494
|
+
"/run",
|
|
8495
|
+
"--remount-ro",
|
|
8496
|
+
"/run",
|
|
8497
|
+
);
|
|
8498
|
+
} else {
|
|
8499
|
+
args.push(
|
|
8500
|
+
"--perms",
|
|
8501
|
+
"1777",
|
|
8502
|
+
"--size",
|
|
8503
|
+
String(64 * 1024 * 1024),
|
|
8504
|
+
"--tmpfs",
|
|
8505
|
+
"/tmp",
|
|
8506
|
+
"--perms",
|
|
8507
|
+
"0755",
|
|
8508
|
+
"--size",
|
|
8509
|
+
String(16 * 1024 * 1024),
|
|
8510
|
+
"--tmpfs",
|
|
8511
|
+
"/run",
|
|
8512
|
+
"--perms",
|
|
8513
|
+
"1777",
|
|
8514
|
+
"--size",
|
|
8515
|
+
String(32 * 1024 * 1024),
|
|
8516
|
+
"--tmpfs",
|
|
8517
|
+
"/var/tmp",
|
|
8518
|
+
"--perms",
|
|
8519
|
+
"0700",
|
|
8520
|
+
"--size",
|
|
8521
|
+
String(16 * 1024 * 1024),
|
|
8522
|
+
"--tmpfs",
|
|
8523
|
+
"/home/sandbox",
|
|
8524
|
+
"--proc",
|
|
8525
|
+
"/proc",
|
|
8526
|
+
"--dev",
|
|
8527
|
+
"/dev",
|
|
8528
|
+
);
|
|
8529
|
+
}
|
|
6780
8530
|
for (const key of Object.keys(environment).sort()) {
|
|
6781
8531
|
args.push("--setenv", key, environment[key]);
|
|
6782
8532
|
}
|
|
@@ -6886,25 +8636,28 @@ function probeLinuxBubblewrapPolicy(
|
|
|
6886
8636
|
// These pipes connect the host Broker to bwrap and are created before
|
|
6887
8637
|
// bwrap installs the target seccomp filter. The nested runtime probe above
|
|
6888
8638
|
// inherits a regular-file descriptor and never requests UV_CREATE_PIPE.
|
|
6889
|
-
|
|
6890
|
-
|
|
8639
|
+
const stdio = linuxStdioWithPinnedMounts(null, pinnedDescriptors, {
|
|
8640
|
+
probe: true,
|
|
8641
|
+
supervisorFd: supervisorLaunch.fd,
|
|
8642
|
+
scrubberFd: supervisorLaunch.scrubberFd,
|
|
8643
|
+
});
|
|
8644
|
+
const invocation = linuxBubblewrapScrubbedInvocation(
|
|
8645
|
+
supervisorLaunch,
|
|
6891
8646
|
[...policyArgs, "--", "/opt/chainless/runtime/node", "-e", probeSource],
|
|
6892
|
-
|
|
6893
|
-
cwd: "/",
|
|
6894
|
-
shell: false,
|
|
6895
|
-
encoding: "utf8",
|
|
6896
|
-
stdio: linuxStdioWithPinnedMounts(null, pinnedDescriptors, {
|
|
6897
|
-
probe: true,
|
|
6898
|
-
supervisorFd: supervisorLaunch.fd,
|
|
6899
|
-
}),
|
|
6900
|
-
timeout: 15_000,
|
|
6901
|
-
env: {
|
|
6902
|
-
PATH: "/usr/bin:/bin",
|
|
6903
|
-
LANG: "C",
|
|
6904
|
-
LC_ALL: "C",
|
|
6905
|
-
},
|
|
6906
|
-
},
|
|
8647
|
+
stdio,
|
|
6907
8648
|
);
|
|
8649
|
+
result = runtime.spawnSync(invocation.command, invocation.args, {
|
|
8650
|
+
cwd: "/",
|
|
8651
|
+
shell: false,
|
|
8652
|
+
encoding: "utf8",
|
|
8653
|
+
stdio,
|
|
8654
|
+
timeout: 15_000,
|
|
8655
|
+
env: {
|
|
8656
|
+
PATH: "/usr/bin:/bin",
|
|
8657
|
+
LANG: "C",
|
|
8658
|
+
LC_ALL: "C",
|
|
8659
|
+
},
|
|
8660
|
+
});
|
|
6908
8661
|
} catch {
|
|
6909
8662
|
return linuxBubblewrapProbe(
|
|
6910
8663
|
true,
|
|
@@ -6948,7 +8701,99 @@ function probeLinuxBubblewrapPolicy(
|
|
|
6948
8701
|
supervisorBinding,
|
|
6949
8702
|
null,
|
|
6950
8703
|
null,
|
|
8704
|
+
null,
|
|
8705
|
+
true,
|
|
8706
|
+
);
|
|
8707
|
+
}
|
|
8708
|
+
|
|
8709
|
+
function probeLinuxRuntimePathnameClosurePolicy(
|
|
8710
|
+
runtime,
|
|
8711
|
+
supervisorLaunch,
|
|
8712
|
+
supervisorBinding,
|
|
8713
|
+
policyArgs,
|
|
8714
|
+
pinnedDescriptors,
|
|
8715
|
+
targetRuntime,
|
|
8716
|
+
contentSnapshot = null,
|
|
8717
|
+
) {
|
|
8718
|
+
const probeSource = [
|
|
8719
|
+
'const fs = require("node:fs");',
|
|
8720
|
+
`const sentinel = ${JSON.stringify(
|
|
8721
|
+
LINUX_BWRAP_NATIVE_RUNTIME_CLOSURE_PROBE_SENTINEL,
|
|
8722
|
+
)};`,
|
|
8723
|
+
"const writableCandidates = [",
|
|
8724
|
+
' "/unapproved-runtime.so", "/tmp/unapproved-runtime.so",',
|
|
8725
|
+
' "/run/unapproved-runtime.so", "/var/tmp/unapproved-runtime.so",',
|
|
8726
|
+
' "/home/sandbox/unapproved-runtime.so",',
|
|
8727
|
+
' "/opt/chainless/plugin/unapproved-runtime.so",',
|
|
8728
|
+
"];",
|
|
8729
|
+
"for (const candidate of writableCandidates) {",
|
|
8730
|
+
" try {",
|
|
8731
|
+
' fs.writeFileSync(candidate, "not-approved");',
|
|
8732
|
+
" process.exit(71);",
|
|
8733
|
+
" } catch {}",
|
|
8734
|
+
"}",
|
|
8735
|
+
"const absentPaths = [",
|
|
8736
|
+
' "/proc/self/exe", "/proc/1/exe", "/proc/self/fd/0",',
|
|
8737
|
+
' "/dev/null", "/dev/fd/0",',
|
|
8738
|
+
` ${JSON.stringify(LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH)},`,
|
|
8739
|
+
"];",
|
|
8740
|
+
"for (const candidate of absentPaths) {",
|
|
8741
|
+
" try {",
|
|
8742
|
+
" fs.lstatSync(candidate);",
|
|
8743
|
+
" process.exit(72);",
|
|
8744
|
+
" } catch (error) {",
|
|
8745
|
+
' if (error?.code !== "ENOENT") process.exit(73);',
|
|
8746
|
+
" }",
|
|
8747
|
+
"}",
|
|
8748
|
+
'if (fs.readdirSync("/proc").length !== 0) process.exit(74);',
|
|
8749
|
+
'if (fs.readdirSync("/dev").length !== 0) process.exit(75);',
|
|
8750
|
+
"process.stdout.write(sentinel);",
|
|
8751
|
+
].join("\n");
|
|
8752
|
+
let result;
|
|
8753
|
+
try {
|
|
8754
|
+
const stdio = linuxStdioWithPinnedMounts(null, pinnedDescriptors, {
|
|
8755
|
+
probe: true,
|
|
8756
|
+
supervisorFd: supervisorLaunch.fd,
|
|
8757
|
+
scrubberFd: supervisorLaunch.scrubberFd,
|
|
8758
|
+
});
|
|
8759
|
+
const invocation = linuxBubblewrapScrubbedInvocation(
|
|
8760
|
+
supervisorLaunch,
|
|
8761
|
+
[...policyArgs, "--", "/opt/chainless/runtime/node", "-e", probeSource],
|
|
8762
|
+
stdio,
|
|
8763
|
+
);
|
|
8764
|
+
result = runtime.spawnSync(invocation.command, invocation.args, {
|
|
8765
|
+
cwd: "/",
|
|
8766
|
+
shell: false,
|
|
8767
|
+
encoding: "utf8",
|
|
8768
|
+
stdio,
|
|
8769
|
+
timeout: 15_000,
|
|
8770
|
+
env: {
|
|
8771
|
+
PATH: "/usr/bin:/bin",
|
|
8772
|
+
LANG: "C",
|
|
8773
|
+
LC_ALL: "C",
|
|
8774
|
+
},
|
|
8775
|
+
});
|
|
8776
|
+
} catch {
|
|
8777
|
+
return linuxBubblewrapProbe(
|
|
8778
|
+
true,
|
|
8779
|
+
false,
|
|
8780
|
+
"runtime_pathname_closure_probe_spawn_failed",
|
|
8781
|
+
targetRuntime,
|
|
8782
|
+
contentSnapshot,
|
|
8783
|
+
supervisorBinding,
|
|
8784
|
+
);
|
|
8785
|
+
}
|
|
8786
|
+
const runnable =
|
|
8787
|
+
!result?.error &&
|
|
8788
|
+
result?.status === 0 &&
|
|
8789
|
+
String(result.stdout) === LINUX_BWRAP_NATIVE_RUNTIME_CLOSURE_PROBE_SENTINEL;
|
|
8790
|
+
return linuxBubblewrapProbe(
|
|
6951
8791
|
true,
|
|
8792
|
+
runnable,
|
|
8793
|
+
runnable ? null : "runtime_pathname_closure_probe_failed",
|
|
8794
|
+
targetRuntime,
|
|
8795
|
+
contentSnapshot,
|
|
8796
|
+
supervisorBinding,
|
|
6952
8797
|
);
|
|
6953
8798
|
}
|
|
6954
8799
|
|
|
@@ -6979,7 +8824,7 @@ export function applyLinuxSandbox(
|
|
|
6979
8824
|
profile: sandboxOpts.profileName || "default",
|
|
6980
8825
|
backend: "linux-prlimit",
|
|
6981
8826
|
command,
|
|
6982
|
-
args,
|
|
8827
|
+
args: invocation.inputArgs,
|
|
6983
8828
|
options: { ...(spawnOpts || {}) },
|
|
6984
8829
|
};
|
|
6985
8830
|
if (runtime.platform !== "linux") {
|
|
@@ -7106,6 +8951,7 @@ export function applyLinuxSandbox(
|
|
|
7106
8951
|
let nativeDynamicClosure = null;
|
|
7107
8952
|
try {
|
|
7108
8953
|
nativeDynamicClosure = bindLinuxNativeDynamicRuntime(
|
|
8954
|
+
runtime,
|
|
7109
8955
|
validation.entryFormat,
|
|
7110
8956
|
pinnedRuntimeMounts,
|
|
7111
8957
|
);
|
|
@@ -7119,7 +8965,7 @@ export function applyLinuxSandbox(
|
|
|
7119
8965
|
runtimeProbe: linuxBubblewrapProbe(
|
|
7120
8966
|
false,
|
|
7121
8967
|
false,
|
|
7122
|
-
error.message || "
|
|
8968
|
+
error.message || "native_dynamic_recursive_system_graph_unattested",
|
|
7123
8969
|
validation.entryRuntime,
|
|
7124
8970
|
),
|
|
7125
8971
|
reason: "linux_bwrap_native_runtime_unattested",
|
|
@@ -7199,6 +9045,7 @@ export function applyLinuxSandbox(
|
|
|
7199
9045
|
}
|
|
7200
9046
|
|
|
7201
9047
|
let entrySnapshot = null;
|
|
9048
|
+
let runtimeFileSnapshot = null;
|
|
7202
9049
|
let pluginTreeSnapshot = null;
|
|
7203
9050
|
const pluginTreeFileSnapshots = [];
|
|
7204
9051
|
let probePinnedMounts = pinnedMounts;
|
|
@@ -7278,6 +9125,24 @@ export function applyLinuxSandbox(
|
|
|
7278
9125
|
finalMounts[index] = fileSnapshot.finalMount;
|
|
7279
9126
|
probeMounts[index] = fileSnapshot.probeMount;
|
|
7280
9127
|
}
|
|
9128
|
+
let rawRuntimeMount = null;
|
|
9129
|
+
if (validation.entryRuntime === "native-dynamic-elf") {
|
|
9130
|
+
const runtimeIndex = pinnedMounts.findIndex(
|
|
9131
|
+
(mount) => mount.destination === "/opt/chainless/runtime/node",
|
|
9132
|
+
);
|
|
9133
|
+
if (runtimeIndex < 0) {
|
|
9134
|
+
throw new Error("native_runtime_snapshot_mount_missing");
|
|
9135
|
+
}
|
|
9136
|
+
rawRuntimeMount = pinnedMounts[runtimeIndex];
|
|
9137
|
+
runtimeFileSnapshot = createLinuxRegularFileSnapshot(
|
|
9138
|
+
runtime,
|
|
9139
|
+
rawRuntimeMount,
|
|
9140
|
+
validation.contract.runtimeIdentity,
|
|
9141
|
+
linuxPluginRuntimeSnapshotContract(),
|
|
9142
|
+
);
|
|
9143
|
+
finalMounts[runtimeIndex] = runtimeFileSnapshot.finalMount;
|
|
9144
|
+
probeMounts[runtimeIndex] = runtimeFileSnapshot.probeMount;
|
|
9145
|
+
}
|
|
7281
9146
|
pluginTreeSnapshot = buildLinuxPluginTreeSnapshotAttestation(
|
|
7282
9147
|
finalMounts,
|
|
7283
9148
|
sandboxEntry,
|
|
@@ -7286,12 +9151,14 @@ export function applyLinuxSandbox(
|
|
|
7286
9151
|
pinnedMounts = finalMounts;
|
|
7287
9152
|
probePinnedMounts = probeMounts;
|
|
7288
9153
|
entryMount = entrySnapshot.finalMount;
|
|
7289
|
-
closeLinuxPinnedMounts(runtime, rawPluginMounts);
|
|
9154
|
+
closeLinuxPinnedMounts(runtime, [...rawPluginMounts, rawRuntimeMount]);
|
|
7290
9155
|
} catch (error) {
|
|
7291
9156
|
closeLinuxPinnedMounts(runtime, pinnedMounts);
|
|
7292
9157
|
closeLinuxPinnedMounts(runtime, [
|
|
7293
9158
|
entrySnapshot?.probeMount,
|
|
7294
9159
|
entrySnapshot?.finalMount,
|
|
9160
|
+
runtimeFileSnapshot?.probeMount,
|
|
9161
|
+
runtimeFileSnapshot?.finalMount,
|
|
7295
9162
|
...pluginTreeFileSnapshots.flatMap((snapshot) => [
|
|
7296
9163
|
snapshot.probeMount,
|
|
7297
9164
|
snapshot.finalMount,
|
|
@@ -7313,14 +9180,53 @@ export function applyLinuxSandbox(
|
|
|
7313
9180
|
});
|
|
7314
9181
|
}
|
|
7315
9182
|
|
|
9183
|
+
let runtimePathnameLoadSet = null;
|
|
9184
|
+
if (validation.entryRuntime === "native-dynamic-elf") {
|
|
9185
|
+
try {
|
|
9186
|
+
runtimePathnameLoadSet = buildLinuxRuntimePathnameLoadSet(
|
|
9187
|
+
runtime,
|
|
9188
|
+
pinnedMounts,
|
|
9189
|
+
pluginTreeSnapshot,
|
|
9190
|
+
nativeDynamicClosure,
|
|
9191
|
+
);
|
|
9192
|
+
} catch (error) {
|
|
9193
|
+
closeLinuxPinnedMounts(runtime, pinnedMounts);
|
|
9194
|
+
closeLinuxPinnedMounts(runtime, [
|
|
9195
|
+
entrySnapshot?.probeMount,
|
|
9196
|
+
runtimeFileSnapshot?.probeMount,
|
|
9197
|
+
...pluginTreeFileSnapshots.map((snapshot) => snapshot.probeMount),
|
|
9198
|
+
]);
|
|
9199
|
+
return createSandboxPlan({
|
|
9200
|
+
...base,
|
|
9201
|
+
backend: null,
|
|
9202
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
9203
|
+
policyAttested: false,
|
|
9204
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
9205
|
+
false,
|
|
9206
|
+
false,
|
|
9207
|
+
error.message || "native_runtime_pathname_load_set_unattested",
|
|
9208
|
+
validation.entryRuntime,
|
|
9209
|
+
),
|
|
9210
|
+
reason: "linux_bwrap_native_runtime_load_set_unattested",
|
|
9211
|
+
guarantees: [],
|
|
9212
|
+
});
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
|
|
7316
9216
|
let probeSeccompFilter;
|
|
7317
9217
|
let seccompFilter;
|
|
7318
9218
|
const pluginTreeProbeMounts = pluginTreeFileSnapshots.map(
|
|
7319
9219
|
(snapshot) => snapshot.probeMount,
|
|
7320
9220
|
);
|
|
7321
9221
|
try {
|
|
7322
|
-
|
|
7323
|
-
|
|
9222
|
+
const runtimePathnameClosure =
|
|
9223
|
+
validation.entryRuntime === "native-dynamic-elf";
|
|
9224
|
+
probeSeccompFilter = pinLinuxNetworkSeccompFilter(runtime, {
|
|
9225
|
+
runtimePathnameClosure,
|
|
9226
|
+
});
|
|
9227
|
+
seccompFilter = pinLinuxNetworkSeccompFilter(runtime, {
|
|
9228
|
+
runtimePathnameClosure,
|
|
9229
|
+
});
|
|
7324
9230
|
const seccompChildFd = LINUX_BWRAP_FIRST_MOUNT_FD + pinnedMounts.length;
|
|
7325
9231
|
probeSeccompFilter.childFd = seccompChildFd;
|
|
7326
9232
|
seccompFilter.childFd = seccompChildFd;
|
|
@@ -7333,6 +9239,7 @@ export function applyLinuxSandbox(
|
|
|
7333
9239
|
runtime,
|
|
7334
9240
|
[
|
|
7335
9241
|
entrySnapshot?.probeMount,
|
|
9242
|
+
runtimeFileSnapshot?.probeMount,
|
|
7336
9243
|
...pluginTreeProbeMounts,
|
|
7337
9244
|
probeSeccompFilter,
|
|
7338
9245
|
seccompFilter,
|
|
@@ -7355,12 +9262,15 @@ export function applyLinuxSandbox(
|
|
|
7355
9262
|
}
|
|
7356
9263
|
const pinnedDescriptors = [...pinnedMounts, seccompFilter];
|
|
7357
9264
|
const probeDescriptors = [...probePinnedMounts, probeSeccompFilter];
|
|
9265
|
+
let descriptorScrubberPin;
|
|
7358
9266
|
const closeStrongLinuxResources = (...extra) => {
|
|
7359
9267
|
closeLinuxPinnedMounts(runtime, [
|
|
7360
9268
|
...pinnedDescriptors,
|
|
7361
9269
|
probeSeccompFilter,
|
|
7362
9270
|
entrySnapshot?.probeMount,
|
|
9271
|
+
runtimeFileSnapshot?.probeMount,
|
|
7363
9272
|
...pluginTreeProbeMounts,
|
|
9273
|
+
descriptorScrubberPin,
|
|
7364
9274
|
...extra,
|
|
7365
9275
|
]);
|
|
7366
9276
|
};
|
|
@@ -7370,9 +9280,11 @@ export function applyLinuxSandbox(
|
|
|
7370
9280
|
let finalLaunch;
|
|
7371
9281
|
try {
|
|
7372
9282
|
supervisorPin = pinLinuxBubblewrapSupervisor(runtime);
|
|
9283
|
+
descriptorScrubberPin = pinLinuxBubblewrapDescriptorScrubber(runtime);
|
|
7373
9284
|
capabilityLaunch = openLinuxBubblewrapSupervisorLaunch(
|
|
7374
9285
|
runtime,
|
|
7375
9286
|
supervisorPin,
|
|
9287
|
+
descriptorScrubberPin,
|
|
7376
9288
|
);
|
|
7377
9289
|
} catch (error) {
|
|
7378
9290
|
closeStrongLinuxResources(supervisorPin);
|
|
@@ -7417,7 +9329,11 @@ export function applyLinuxSandbox(
|
|
|
7417
9329
|
});
|
|
7418
9330
|
}
|
|
7419
9331
|
try {
|
|
7420
|
-
probeLaunch = openLinuxBubblewrapSupervisorLaunch(
|
|
9332
|
+
probeLaunch = openLinuxBubblewrapSupervisorLaunch(
|
|
9333
|
+
runtime,
|
|
9334
|
+
supervisorPin,
|
|
9335
|
+
descriptorScrubberPin,
|
|
9336
|
+
);
|
|
7421
9337
|
} catch (error) {
|
|
7422
9338
|
closeStrongLinuxResources(supervisorPin, probeLaunch);
|
|
7423
9339
|
return createSandboxPlan({
|
|
@@ -7438,15 +9354,56 @@ export function applyLinuxSandbox(
|
|
|
7438
9354
|
});
|
|
7439
9355
|
}
|
|
7440
9356
|
const environment = linuxSandboxEnvironment();
|
|
9357
|
+
const runtimePathnameClosure =
|
|
9358
|
+
validation.entryRuntime === "native-dynamic-elf" &&
|
|
9359
|
+
runtimePathnameLoadSet !== null;
|
|
7441
9360
|
const policyArgs = buildLinuxBubblewrapPolicyArgs(
|
|
7442
9361
|
pluginTree.directories,
|
|
7443
9362
|
pinnedMounts,
|
|
7444
9363
|
environment,
|
|
7445
9364
|
seccompFilter,
|
|
9365
|
+
{ runtimePathnameClosure },
|
|
9366
|
+
);
|
|
9367
|
+
const probePolicyArgs = buildLinuxBubblewrapPolicyArgs(
|
|
9368
|
+
pluginTree.directories,
|
|
9369
|
+
probePinnedMounts,
|
|
9370
|
+
environment,
|
|
9371
|
+
probeSeccompFilter,
|
|
9372
|
+
{ runtimePathnameClosure },
|
|
7446
9373
|
);
|
|
9374
|
+
// The closure probe must exercise the exact ordered mount policy later
|
|
9375
|
+
// used for the untrusted target. Only the backing parent descriptors,
|
|
9376
|
+
// stdio, and command differ between the two launches; their child FD
|
|
9377
|
+
// numbers and every bwrap policy option are identical.
|
|
9378
|
+
if (
|
|
9379
|
+
runtimePathnameClosure &&
|
|
9380
|
+
JSON.stringify(probePolicyArgs) !== JSON.stringify(policyArgs)
|
|
9381
|
+
) {
|
|
9382
|
+
closeStrongLinuxResources(supervisorPin, probeLaunch);
|
|
9383
|
+
return createSandboxPlan({
|
|
9384
|
+
...base,
|
|
9385
|
+
backend: null,
|
|
9386
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
9387
|
+
policyAttested: false,
|
|
9388
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
9389
|
+
true,
|
|
9390
|
+
false,
|
|
9391
|
+
"runtime_pathname_closure_probe_policy_mismatch",
|
|
9392
|
+
validation.entryRuntime,
|
|
9393
|
+
null,
|
|
9394
|
+
supervisorPin.attestation,
|
|
9395
|
+
),
|
|
9396
|
+
reason: "linux_bwrap_policy_probe_failed",
|
|
9397
|
+
guarantees: [],
|
|
9398
|
+
});
|
|
9399
|
+
}
|
|
7447
9400
|
const targetArgs = validation.entryRuntime.startsWith("native-")
|
|
7448
|
-
? [sandboxEntry, ...
|
|
7449
|
-
: [
|
|
9401
|
+
? [sandboxEntry, ...validation.argsSnapshot]
|
|
9402
|
+
: [
|
|
9403
|
+
"/opt/chainless/runtime/node",
|
|
9404
|
+
sandboxEntry,
|
|
9405
|
+
...validation.argsSnapshot.slice(1),
|
|
9406
|
+
];
|
|
7450
9407
|
const snapshotPolicyBinding = entrySnapshot
|
|
7451
9408
|
? {
|
|
7452
9409
|
scope: entrySnapshot.attestation.scope,
|
|
@@ -7462,13 +9419,23 @@ export function applyLinuxSandbox(
|
|
|
7462
9419
|
targetMode: entrySnapshot.attestation.targetMode,
|
|
7463
9420
|
}
|
|
7464
9421
|
: null;
|
|
9422
|
+
const descriptorScrubberPolicy = linuxBwrapDescriptorScrubberPolicyBinding(
|
|
9423
|
+
descriptorScrubberPin.attestation,
|
|
9424
|
+
{
|
|
9425
|
+
scrubberChildFd: LINUX_BWRAP_FIRST_MOUNT_FD + pinnedDescriptors.length,
|
|
9426
|
+
preservedMaxFd:
|
|
9427
|
+
LINUX_BWRAP_FIRST_MOUNT_FD + pinnedDescriptors.length - 1,
|
|
9428
|
+
executableChildFd: LINUX_BWRAP_SUPERVISOR_CHILD_FD,
|
|
9429
|
+
},
|
|
9430
|
+
);
|
|
7465
9431
|
const policyDigest = crypto
|
|
7466
9432
|
.createHash("sha256")
|
|
7467
9433
|
.update(
|
|
7468
9434
|
JSON.stringify({
|
|
7469
|
-
version:
|
|
9435
|
+
version: 6,
|
|
7470
9436
|
backend: LINUX_BWRAP_BACKEND,
|
|
7471
9437
|
supervisor: supervisorPin.attestation,
|
|
9438
|
+
descriptorScrubber: descriptorScrubberPolicy,
|
|
7472
9439
|
contract: {
|
|
7473
9440
|
kind: validation.contract.kind,
|
|
7474
9441
|
pluginRoot: validation.contract.pluginRoot,
|
|
@@ -7510,6 +9477,9 @@ export function applyLinuxSandbox(
|
|
|
7510
9477
|
...(nativeDynamicClosure
|
|
7511
9478
|
? { initialDynamicLoadClosure: nativeDynamicClosure }
|
|
7512
9479
|
: {}),
|
|
9480
|
+
...(runtimePathnameLoadSet
|
|
9481
|
+
? { runtimeSharedLibraryPathnameLoadSet: runtimePathnameLoadSet }
|
|
9482
|
+
: {}),
|
|
7513
9483
|
pluginTreeContentSnapshot: pluginTreeSnapshot,
|
|
7514
9484
|
seccomp: {
|
|
7515
9485
|
arch: seccompFilter.arch,
|
|
@@ -7524,19 +9494,30 @@ export function applyLinuxSandbox(
|
|
|
7524
9494
|
)
|
|
7525
9495
|
.digest("hex");
|
|
7526
9496
|
const policyProbe = Object.freeze(
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
9497
|
+
runtimePathnameClosure
|
|
9498
|
+
? probeLinuxRuntimePathnameClosurePolicy(
|
|
9499
|
+
runtime,
|
|
9500
|
+
probeLaunch,
|
|
9501
|
+
supervisorPin.attestation,
|
|
9502
|
+
probePolicyArgs,
|
|
9503
|
+
probeDescriptors,
|
|
9504
|
+
validation.entryRuntime,
|
|
9505
|
+
entrySnapshot?.attestation,
|
|
9506
|
+
)
|
|
9507
|
+
: probeLinuxBubblewrapPolicy(
|
|
9508
|
+
runtime,
|
|
9509
|
+
probeLaunch,
|
|
9510
|
+
supervisorPin.attestation,
|
|
9511
|
+
probePolicyArgs,
|
|
9512
|
+
probeDescriptors,
|
|
9513
|
+
validation.entryRuntime,
|
|
9514
|
+
),
|
|
7535
9515
|
);
|
|
7536
9516
|
closeLinuxPinnedMounts(runtime, [
|
|
7537
9517
|
probeLaunch,
|
|
7538
9518
|
probeSeccompFilter,
|
|
7539
9519
|
entrySnapshot?.probeMount,
|
|
9520
|
+
runtimeFileSnapshot?.probeMount,
|
|
7540
9521
|
...pluginTreeProbeMounts,
|
|
7541
9522
|
]);
|
|
7542
9523
|
probeLaunch = null;
|
|
@@ -7562,7 +9543,13 @@ export function applyLinuxSandbox(
|
|
|
7562
9543
|
sandboxOpts.sync,
|
|
7563
9544
|
{ sealedEntry: entrySnapshot !== null },
|
|
7564
9545
|
);
|
|
7565
|
-
|
|
9546
|
+
const launchArgumentsChanged =
|
|
9547
|
+
finalValidation.ok &&
|
|
9548
|
+
(finalValidation.argsSnapshot.length !== validation.argsSnapshot.length ||
|
|
9549
|
+
finalValidation.argsSnapshot.some(
|
|
9550
|
+
(value, index) => value !== validation.argsSnapshot[index],
|
|
9551
|
+
));
|
|
9552
|
+
if (!finalValidation.ok || launchArgumentsChanged) {
|
|
7566
9553
|
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
7567
9554
|
return createSandboxPlan({
|
|
7568
9555
|
...base,
|
|
@@ -7573,7 +9560,11 @@ export function applyLinuxSandbox(
|
|
|
7573
9560
|
runtimeProbe: linuxBubblewrapProbe(
|
|
7574
9561
|
true,
|
|
7575
9562
|
false,
|
|
7576
|
-
`post_probe_${
|
|
9563
|
+
`post_probe_${
|
|
9564
|
+
finalValidation.ok
|
|
9565
|
+
? "launch_arguments_changed"
|
|
9566
|
+
: finalValidation.reason
|
|
9567
|
+
}`,
|
|
7577
9568
|
validation.entryRuntime,
|
|
7578
9569
|
null,
|
|
7579
9570
|
supervisorPin.attestation,
|
|
@@ -7583,6 +9574,14 @@ export function applyLinuxSandbox(
|
|
|
7583
9574
|
});
|
|
7584
9575
|
}
|
|
7585
9576
|
try {
|
|
9577
|
+
if (runtimeFileSnapshot) {
|
|
9578
|
+
attestLinuxRegularFileSnapshot(
|
|
9579
|
+
runtime,
|
|
9580
|
+
runtimeFileSnapshot.finalMount,
|
|
9581
|
+
runtimeFileSnapshot.attestation,
|
|
9582
|
+
linuxPluginRuntimeSnapshotContract(),
|
|
9583
|
+
);
|
|
9584
|
+
}
|
|
7586
9585
|
attestLinuxPluginTreeSnapshot(
|
|
7587
9586
|
runtime,
|
|
7588
9587
|
pinnedMounts,
|
|
@@ -7612,6 +9611,11 @@ export function applyLinuxSandbox(
|
|
|
7612
9611
|
}
|
|
7613
9612
|
try {
|
|
7614
9613
|
attestLinuxBubblewrapSupervisorPin(runtime, supervisorPin);
|
|
9614
|
+
attestLinuxBubblewrapSupervisorPin(
|
|
9615
|
+
runtime,
|
|
9616
|
+
descriptorScrubberPin,
|
|
9617
|
+
"descriptor_scrubber",
|
|
9618
|
+
);
|
|
7615
9619
|
} catch (error) {
|
|
7616
9620
|
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
7617
9621
|
return createSandboxPlan({
|
|
@@ -7633,7 +9637,11 @@ export function applyLinuxSandbox(
|
|
|
7633
9637
|
});
|
|
7634
9638
|
}
|
|
7635
9639
|
try {
|
|
7636
|
-
finalLaunch = openLinuxBubblewrapSupervisorLaunch(
|
|
9640
|
+
finalLaunch = openLinuxBubblewrapSupervisorLaunch(
|
|
9641
|
+
runtime,
|
|
9642
|
+
supervisorPin,
|
|
9643
|
+
descriptorScrubberPin,
|
|
9644
|
+
);
|
|
7637
9645
|
} catch (error) {
|
|
7638
9646
|
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
7639
9647
|
return createSandboxPlan({
|
|
@@ -7655,8 +9663,9 @@ export function applyLinuxSandbox(
|
|
|
7655
9663
|
});
|
|
7656
9664
|
}
|
|
7657
9665
|
const supervisorBinding = supervisorPin.attestation;
|
|
7658
|
-
closeLinuxPinnedMounts(runtime, [supervisorPin]);
|
|
9666
|
+
closeLinuxPinnedMounts(runtime, [supervisorPin, descriptorScrubberPin]);
|
|
7659
9667
|
supervisorPin = null;
|
|
9668
|
+
descriptorScrubberPin = null;
|
|
7660
9669
|
const capsuleContract =
|
|
7661
9670
|
validation.contract.kind === MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND;
|
|
7662
9671
|
const baseRuntimeProbe = linuxBubblewrapProbe(
|
|
@@ -7668,12 +9677,17 @@ export function applyLinuxSandbox(
|
|
|
7668
9677
|
supervisorBinding,
|
|
7669
9678
|
pluginTreeSnapshot,
|
|
7670
9679
|
nativeDynamicClosure,
|
|
9680
|
+
runtimePathnameLoadSet,
|
|
7671
9681
|
policyProbe.runtimeDetachedChildSpawnVerified === true,
|
|
7672
9682
|
);
|
|
9683
|
+
const descriptorBoundRuntimeProbe = {
|
|
9684
|
+
...baseRuntimeProbe,
|
|
9685
|
+
descriptorScrubber: descriptorScrubberPolicy,
|
|
9686
|
+
};
|
|
7673
9687
|
const runtimeProbe = Object.freeze(
|
|
7674
9688
|
capsuleContract
|
|
7675
9689
|
? {
|
|
7676
|
-
...
|
|
9690
|
+
...descriptorBoundRuntimeProbe,
|
|
7677
9691
|
mcpCapsuleCodeSnapshot: true,
|
|
7678
9692
|
entrySnapshotAtomic: true,
|
|
7679
9693
|
runtimeLaunchAtomic: true,
|
|
@@ -7687,51 +9701,81 @@ export function applyLinuxSandbox(
|
|
|
7687
9701
|
runtimeLaunchPath: "/opt/chainless/runtime/node",
|
|
7688
9702
|
entrySnapshotPath: sandboxEntry,
|
|
7689
9703
|
}
|
|
7690
|
-
:
|
|
9704
|
+
: descriptorBoundRuntimeProbe,
|
|
7691
9705
|
);
|
|
7692
|
-
const options = {
|
|
7693
|
-
...spawnOpts,
|
|
7694
|
-
cwd: "/",
|
|
7695
|
-
shell: false,
|
|
7696
|
-
detached: false,
|
|
7697
|
-
env: {
|
|
7698
|
-
PATH: "/usr/bin:/bin",
|
|
7699
|
-
LANG: "C",
|
|
7700
|
-
LC_ALL: "C",
|
|
7701
|
-
},
|
|
7702
|
-
stdio: Object.freeze(
|
|
7703
|
-
linuxStdioWithPinnedMounts(spawnOpts?.stdio, pinnedDescriptors, {
|
|
7704
|
-
supervisorFd: finalLaunch.fd,
|
|
7705
|
-
}),
|
|
7706
|
-
),
|
|
7707
|
-
};
|
|
7708
9706
|
let pinsClosed = false;
|
|
7709
9707
|
const cleanup = () => {
|
|
7710
9708
|
if (pinsClosed) return;
|
|
7711
9709
|
pinsClosed = true;
|
|
7712
9710
|
closeLinuxPinnedMounts(runtime, [...pinnedDescriptors, finalLaunch]);
|
|
7713
9711
|
};
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
9712
|
+
try {
|
|
9713
|
+
const stdio = linuxStdioWithPinnedMounts(
|
|
9714
|
+
spawnOpts?.stdio,
|
|
9715
|
+
pinnedDescriptors,
|
|
9716
|
+
{
|
|
9717
|
+
supervisorFd: finalLaunch.fd,
|
|
9718
|
+
scrubberFd: finalLaunch.scrubberFd,
|
|
9719
|
+
},
|
|
9720
|
+
);
|
|
9721
|
+
const invocation = linuxBubblewrapScrubbedInvocation(
|
|
9722
|
+
finalLaunch,
|
|
9723
|
+
[...policyArgs, "--", ...targetArgs],
|
|
9724
|
+
stdio,
|
|
9725
|
+
);
|
|
9726
|
+
const options = {
|
|
9727
|
+
...spawnOpts,
|
|
9728
|
+
cwd: "/",
|
|
9729
|
+
shell: false,
|
|
9730
|
+
detached: false,
|
|
9731
|
+
env: {
|
|
9732
|
+
PATH: "/usr/bin:/bin",
|
|
9733
|
+
LANG: "C",
|
|
9734
|
+
LC_ALL: "C",
|
|
9735
|
+
},
|
|
9736
|
+
stdio: Object.freeze(stdio),
|
|
9737
|
+
};
|
|
9738
|
+
return createSandboxPlan({
|
|
9739
|
+
...base,
|
|
9740
|
+
applied: true,
|
|
9741
|
+
enforcement: LINUX_BWRAP_BACKEND,
|
|
9742
|
+
backend: LINUX_BWRAP_BACKEND,
|
|
9743
|
+
candidateBackend: null,
|
|
9744
|
+
policyAttested: true,
|
|
9745
|
+
policyDigest,
|
|
9746
|
+
runtimeProbe,
|
|
9747
|
+
reason: null,
|
|
9748
|
+
guarantees: [
|
|
9749
|
+
SANDBOX_BOUNDARIES.FILESYSTEM,
|
|
9750
|
+
SANDBOX_BOUNDARIES.NETWORK,
|
|
9751
|
+
SANDBOX_BOUNDARIES.PROCESS_TREE,
|
|
9752
|
+
...(capsuleContract ? [SANDBOX_BOUNDARIES.CODE_SNAPSHOT] : []),
|
|
9753
|
+
],
|
|
9754
|
+
command: invocation.command,
|
|
9755
|
+
args: invocation.args,
|
|
9756
|
+
options,
|
|
9757
|
+
cleanup,
|
|
9758
|
+
});
|
|
9759
|
+
} catch (error) {
|
|
9760
|
+
cleanup();
|
|
9761
|
+
return createSandboxPlan({
|
|
9762
|
+
...base,
|
|
9763
|
+
backend: null,
|
|
9764
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
9765
|
+
policyAttested: false,
|
|
9766
|
+
policyDigest,
|
|
9767
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
9768
|
+
true,
|
|
9769
|
+
false,
|
|
9770
|
+
`post_probe_${error.message || "final_plan_construction_failed"}`,
|
|
9771
|
+
validation.entryRuntime,
|
|
9772
|
+
null,
|
|
9773
|
+
supervisorBinding,
|
|
9774
|
+
),
|
|
9775
|
+
reason: "linux_bwrap_execution_contract_changed",
|
|
9776
|
+
guarantees: [],
|
|
9777
|
+
});
|
|
9778
|
+
}
|
|
7735
9779
|
}
|
|
7736
9780
|
|
|
7737
9781
|
const prlimitParts = [];
|
|
@@ -7864,7 +9908,14 @@ export function applySandbox(
|
|
|
7864
9908
|
|
|
7865
9909
|
// Dispatch to platform handler
|
|
7866
9910
|
if (runtime.platform === "darwin") {
|
|
7867
|
-
return applyMacSandbox(
|
|
9911
|
+
return applyMacSandbox(
|
|
9912
|
+
command,
|
|
9913
|
+
args,
|
|
9914
|
+
spawnOpts,
|
|
9915
|
+
profile,
|
|
9916
|
+
runtimeOverrides,
|
|
9917
|
+
runtimeInjected ? null : MACOS_MCP_CODE_SNAPSHOT_ISSUER,
|
|
9918
|
+
);
|
|
7868
9919
|
}
|
|
7869
9920
|
if (runtime.platform === "win32") {
|
|
7870
9921
|
return applyWindowsSandbox(
|