chainlesschain 0.162.182 → 0.162.183
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DzZ5_xoj.js → AIOps-BapS7lz4.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DeLXddIg.js → ActionButton-CXYdXEUI.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-rvDCmet1.js → Analytics-CKR5Lexn.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Yt6QVDnp.js → AppLayout-DATVUOXB.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BS112K5L.js → Artifacts-vb_0ZlV1.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-0wJuQrNu.js → Audit-BI1l9GWt.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-B90PMgbn.js → BackgroundAgents-BFXbaUEf.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-TkgMglQQ.js → Backup-DEYBJJ6H.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CQVS9p1l.js → BaseInput-COdzeO0M.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-YTjQHoC7.js → Chat-oSN6sfM5.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CI_jT0ru.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-5bwaOabq.js → Checkbox-BezEa4tm.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BFmNQ_Xe.js → Codegen-CNEL0Ife.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BT_u4Dzp.js → Col-Bf2Y2Bn1.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CJP52u5b.js → Community-BfUlu4Hc.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BpzhJE9i.js → Compact-BN4xWP9e.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Cg7rfslp.js → Compliance-Bt6BKmEP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-JisxL6Yy.js → Cowork-DKLp8YxO.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Bu7Ug_MC.js → Cron-o7Hhw6ky.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CvuqoLdj.js → Crosschain-DaNXWJIw.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C2u9G8Pu.js → DID-B-Qa2mha.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BFt5Yv3d.js → Dashboard-CMX0Qnxw.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DIaT9Wxq.js → Dropdown-BLuLyRu_.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D_A601Y4.js → EmailListRenderer-CFBAeMij.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard--IyQ8I8w.js → FamilyGuardDashboard-DWzXQ67V.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BihWHxuY.js → Federation-DB1QSLgr.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BB8dOXc_.js → FormItemContext-C-OKCToP.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DQYhj1HH.js → GenericCardRenderer-DMJFtFZf.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B-2QZxGf.js → Git-DsGSujOV.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CusSb-nG.js → Governance-B0iS9NEW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BAjmvPPL.js → Inference-CH004H7c.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DoNYWKYq.js → KnowledgeGraph-Q9MTQ83c.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B8fKWD-n.js → Logs-B_fyjZK9.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BlJm1J7E.js → MarkdownRenderer-C0o4FtpD.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CyOscbSL.js → Marketplace-BIStF9d1.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Ex6Om5da.js → McpTools-pGfal768.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-CDcD8l8r.js → Memory-xbNqpIqB.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-xfBqoFDg.js → MobileBridge-B5EQGrIK.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CIWd36OV.js → Mtc-a_rmfxJH.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CPX0ouuA.js → MtcAudit-BmKuofUr.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BDJsIUuW.js → Multisig-BQbJ-CAN.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DEkbuD3N.js → NLProgramming-JtPJKX25.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DJpHRhH1.js → Notes-ZFKznTSG.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C1UZ-Qd5.js → NotificationSettings-B6aHRBBR.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-GlJtqLjT.js → OrderTableRenderer-DdcsOqEz.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Bbhj0fac.js → Organization-BFDTAUgS.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-1H04x3kx.js → Overflow-0FoTfbWH.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CDpPe3et.js → P2P-CWd90etw.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-MUX0Bpvw.js → PdhVaultBrowser-DtCGC-W-.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-A07-0Ci1.js → Permissions-CvQ7nr7T.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DhhDWxAZ.js → PersonalDataHub-rdNPcR-Z.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-CyfbJfe-.js → Pipeline-CmrfiNOs.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CNFps3ED.js → Privacy-D7ep1h_t.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit--T-u3Dr5.js → ProjectInit-C0xKINHS.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DJmrK2fC.js → ProjectSettings-AOmZFp-F.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BWHSkXZP.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DLzzbOZd.js → Providers-Cb7JCTPL.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CexOomKS.js → QrScannerModal-BDMehcXC.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-QklGi19_.js → QuickAsk-BF6_1nvx.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bb9kwSw8.js → Recommend-BHfT-A28.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-Dr5cLP0N.js → Reputation-CsWHLEcj.js} +1 -1
- package/src/assets/web-panel/assets/{Row-oCQHYv5t.js → Row-D6srnWhm.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-IFDEykIR.js → RssFeed-Xhr-KyVd.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CspVIxDc.js → Search-vIbIJvwL.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Bm66o_DH.js → Security-Bzi5Aiko.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DvanNqXl.js → Services-BHtVX52P.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CHjEe9_O.js → Skeleton-C0R8B2jf.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Cb8jWjMX.js → Skills-DtT6rWOX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DxZm1491.js → Sla-0B37mxVU.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DWxEEclC.js → SpeechSettings-0V2jE1Bs.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-FIvOFw5p.js → SyncSettings-CYedRzaj.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-D-R1x8vm.js +1 -0
- package/src/assets/web-panel/assets/{Templates-CaNjzV_t.js → Templates-BKoHztGT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DjlP6TmH.js → Tenant--17nwVNZ.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-DoZ8y7y8.js → TimelineRenderer-C5jx4AE3.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-390cg2TC.js → Tokens-DYJKYa9t.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CLrtCj5_.js → Trigger-DChpWiQ9.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DpESfSPn.js → Trust-DfWG52EA.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CN6279O0.js → UkeySign-CMugYcyA.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DPtAUlSe.js → VideoEditing-BOSmrHAd.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-nKOMV_1n.js → Wallet-D5g3k3Sa.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-HuXlSOc4.js → WebAuthn-D-7u4Mkc.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CUfFjARi.js → WorkflowEditor-CFzZMSUJ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-6Eq0PcZJ.js → chat-VbR6l6mc.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BP_Sf6Iy.js → colors-BSZn8EDS.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BufMKIh5.js → compact-item-y74obMEq.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BBMXxWSW.js → createContext-C326bjex.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-fUK_tRO2.js → hasIn-D_INJQT_.js} +1 -1
- package/src/assets/web-panel/assets/{index-LvCBfTLo.js → index-4PIeTM--.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgiECHu2.js → index-4eyH-eC2.js} +1 -1
- package/src/assets/web-panel/assets/index-5iAFwYx1.js +1 -0
- package/src/assets/web-panel/assets/{index-DLpIs_P_.js → index-9PIVuXqo.js} +1 -1
- package/src/assets/web-panel/assets/{index-cpEli44X.js → index-B7SrC-On.js} +1 -1
- package/src/assets/web-panel/assets/{index-6GmBa4Kg.js → index-BJi3UqBt.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9vU25ij.js → index-BY8-nCgs.js} +1 -1
- package/src/assets/web-panel/assets/index-BfnB7LPx.js +1 -0
- package/src/assets/web-panel/assets/{index-B2vS66eI.js → index-Bfw2hf2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVsqvI4M.js → index-BjcWKg1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-C44atv4k.js → index-BnSfnSMP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWKoBU8R.js → index-Bnua8IX3.js} +1 -1
- package/src/assets/web-panel/assets/{index-GwdRUZ-x.js → index-C-ZCaUJc.js} +1 -1
- package/src/assets/web-panel/assets/{index-pum7HYBA.js → index-C2NyllnW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9WYSn4m.js → index-C327pLbw.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bkg4ZePE.js → index-CMHqs3Jb.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmkMXwnR.js → index-CfLJ42kv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dyngbd29.js → index-Cr3uuz0C.js} +19 -19
- package/src/assets/web-panel/assets/{index-t8N1tSeX.js → index-CsovTpx9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocdRcbd.js → index-CzE53HiB.js} +1 -1
- package/src/assets/web-panel/assets/{index-fzaMtSJ8.js → index-DBkzrpiL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE2DdTP2.js → index-DNMxIsDg.js} +1 -1
- package/src/assets/web-panel/assets/{index-wKTLbKyc.js → index-DUU2606o.js} +1 -1
- package/src/assets/web-panel/assets/{index-uCbEN7cY.js → index-DdyjrCAu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZ6Yt17L.js → index-DkH1JvEt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwSf5pnf.js → index-Dlxh9D8D.js} +1 -1
- package/src/assets/web-panel/assets/{index-3FuwBewR.js → index-DuIkfnTF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW_G-MXw.js → index-DwHSZLXg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHy6yNiF.js → index-E_v1ZCTP.js} +1 -1
- package/src/assets/web-panel/assets/{index-wV6iGdpY.js → index-FQBsuExX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DASluSqY.js → index-GA80ir_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMLHv389.js → index-IZAWt0H8.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7Etk0Te.js → index-Iw0FfqUz.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4_MuGkd.js → index-KF9Zmqln.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cp8vhyZy.js → index-abmn_8Yz.js} +1 -1
- package/src/assets/web-panel/assets/{index-2A5ph8b2.js → index-gINyI1rh.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeASi4fr.js → index-jYaogFE8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGjmILDm.js → index-nSn2-C4N.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPdUNk1i.js → index-qMA4GYdQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-V5Fo95FM.js → initDefaultProps-DI0Zaf8-.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BYWU0hUT.js → motion-BZDKSy3Z.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cr1M-J2h.js → move-DsIW01Vp.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BysQKi2N.js → mtc-parser-CohkDhsf.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D1zHU8GC.js → omit-DpiN5rzg.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CUArxtfi.js → pickAttrs-qbuqni3y.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-ChoYEys0.js → placementArrow-mBky6714.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BQRs3Mo7.js → responsiveObserve-Bas3eQ2K.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B8zbsCkg.js → slide-CBx_SKoK.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C2U8Gs6h.js → statusUtils-BlYguJSV.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dd8MgL_V.js → styleChecker-BCYQmRGv.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-hknqY5NH.js → useFlexGapSupport-Dp5dybLM.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CLTAmcfw.js → useFs-DHAMBgA2.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-ByegHbiB.js → usePersonalDataHub-W6jsSw76.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DqlAy5DP.js → vnode-BkI6aayc.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DiXiztQn.js → zoom-C_IMGueq.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +9 -0
- package/src/commands/background-session.js +49 -0
- package/src/commands/plugin.js +235 -23
- package/src/commands/session.js +13 -0
- package/src/data/changelog.json +13 -0
- package/src/harness/jsonl-session-store.js +6 -4
- package/src/lib/agent-team/team-runner.js +19 -1
- package/src/lib/background-agent-supervisor.js +49 -0
- package/src/lib/plugin-runtime/install.js +226 -7
- package/src/lib/plugin-runtime/scopes.js +13 -2
- package/src/lib/plugin-usage-attribution.js +44 -0
- package/src/lib/process-execution-broker/index.js +252 -1
- package/src/lib/process-execution-broker/platform-sandbox.js +1251 -101
- package/src/lib/session-usage.js +77 -0
- package/src/repl/agent-repl.js +6 -6
- package/src/runtime/headless-runner.js +9 -1
- package/src/runtime/headless-stream.js +20 -1
- package/src/runtime/mcp-config.js +15 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-PR074hUN.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-BwObMMA2.js +0 -1
- package/src/assets/web-panel/assets/Projects-Qx_MdX9k.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-DZuVNNN5.js +0 -5
- package/src/assets/web-panel/assets/Tasks-CyuLnDFR.js +0 -1
- package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +0 -3
- package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +0 -1
- package/src/assets/web-panel/assets/index-DOZTSoS7.js +0 -1
- package/src/assets/web-panel/assets/index-DhpvjpqI.js +0 -1
|
@@ -146,9 +146,24 @@ const LINUX_LDD_PATH = "/usr/bin/ldd";
|
|
|
146
146
|
const LINUX_BWRAP_BACKEND = "linux-bwrap";
|
|
147
147
|
const LINUX_BWRAP_NODE_PROBE_SENTINEL = "chainless-linux-bwrap-plugin-node-v1";
|
|
148
148
|
const LINUX_BWRAP_MAX_PLUGIN_ENTRIES = 512;
|
|
149
|
-
const
|
|
149
|
+
const LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES = 256;
|
|
150
|
+
const LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES = 256 * 1024 * 1024;
|
|
151
|
+
const LINUX_BWRAP_SUPERVISOR_CHILD_FD = 3;
|
|
152
|
+
const LINUX_BWRAP_FIRST_MOUNT_FD = 4;
|
|
153
|
+
const LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH = "/run/.chainless-bwrap-supervisor";
|
|
154
|
+
const LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM =
|
|
155
|
+
"pinned-child-fd3-file-consume-run-overmount-v1";
|
|
150
156
|
const LINUX_ATTESTED_FILE_MAX_BYTES = 256 * 1024 * 1024;
|
|
151
157
|
const LINUX_ATTESTATION_HASH_CHUNK_BYTES = 1024 * 1024;
|
|
158
|
+
const LINUX_ENTRY_SNAPSHOT_MECHANISM =
|
|
159
|
+
"verified-o_tmpfile-copy-bwrap-ro-bind-data-v1";
|
|
160
|
+
const LINUX_ENTRY_SNAPSHOT_SOURCE_MODE = 0o400;
|
|
161
|
+
const LINUX_NODE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-source";
|
|
162
|
+
const LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-executable";
|
|
163
|
+
const LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE = "all-pinned-plugin-regular-files";
|
|
164
|
+
const LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY = "per-file-pin-to-launch";
|
|
165
|
+
const LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE = "0400";
|
|
166
|
+
const LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE = "0500";
|
|
152
167
|
const LINUX_ELF64_HEADER_BYTES = 64;
|
|
153
168
|
const LINUX_ELF64_PROGRAM_HEADER_BYTES = 56;
|
|
154
169
|
const LINUX_ELF64_DYNAMIC_ENTRY_BYTES = 16;
|
|
@@ -2582,8 +2597,40 @@ function linuxBubblewrapProbe(
|
|
|
2582
2597
|
runnable,
|
|
2583
2598
|
reason,
|
|
2584
2599
|
targetRuntime = "node",
|
|
2600
|
+
contentSnapshot = null,
|
|
2601
|
+
supervisorBinding = null,
|
|
2602
|
+
pluginTreeSnapshot = null,
|
|
2585
2603
|
) {
|
|
2586
2604
|
const native = targetRuntime === "native-static-elf";
|
|
2605
|
+
const expectedSnapshotScope = native
|
|
2606
|
+
? LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE
|
|
2607
|
+
: LINUX_NODE_ENTRY_SNAPSHOT_SCOPE;
|
|
2608
|
+
const snapshot =
|
|
2609
|
+
contentSnapshot?.scope === expectedSnapshotScope &&
|
|
2610
|
+
contentSnapshot?.mechanism === LINUX_ENTRY_SNAPSHOT_MECHANISM;
|
|
2611
|
+
const supervisorDescriptorBound =
|
|
2612
|
+
supervisorBinding?.mechanism === LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM;
|
|
2613
|
+
const pluginTreeContentSnapshot =
|
|
2614
|
+
!native &&
|
|
2615
|
+
attempted &&
|
|
2616
|
+
snapshot &&
|
|
2617
|
+
runnable &&
|
|
2618
|
+
reason === null &&
|
|
2619
|
+
supervisorDescriptorBound &&
|
|
2620
|
+
pluginTreeSnapshot?.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
|
|
2621
|
+
pluginTreeSnapshot?.mechanism === LINUX_ENTRY_SNAPSHOT_MECHANISM &&
|
|
2622
|
+
Number.isSafeInteger(pluginTreeSnapshot?.files) &&
|
|
2623
|
+
pluginTreeSnapshot.files > 0 &&
|
|
2624
|
+
pluginTreeSnapshot.files <= LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES &&
|
|
2625
|
+
Number.isSafeInteger(pluginTreeSnapshot?.bytes) &&
|
|
2626
|
+
pluginTreeSnapshot.bytes >= 0 &&
|
|
2627
|
+
pluginTreeSnapshot.bytes <= LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES &&
|
|
2628
|
+
typeof pluginTreeSnapshot?.digest === "string" &&
|
|
2629
|
+
/^[a-f0-9]{64}$/.test(pluginTreeSnapshot.digest) &&
|
|
2630
|
+
pluginTreeSnapshot?.consistency ===
|
|
2631
|
+
LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
|
|
2632
|
+
pluginTreeSnapshot?.contractBound === false &&
|
|
2633
|
+
pluginTreeSnapshot?.atomic === false;
|
|
2587
2634
|
return {
|
|
2588
2635
|
kind: native
|
|
2589
2636
|
? "linux-bwrap-plugin-native-static-elf-policy-v1"
|
|
@@ -2593,7 +2640,50 @@ function linuxBubblewrapProbe(
|
|
|
2593
2640
|
reason,
|
|
2594
2641
|
probeRuntime: "node",
|
|
2595
2642
|
targetRuntime,
|
|
2596
|
-
contentSnapshot:
|
|
2643
|
+
contentSnapshot: snapshot,
|
|
2644
|
+
...(snapshot
|
|
2645
|
+
? {
|
|
2646
|
+
contentSnapshotScope: contentSnapshot.scope,
|
|
2647
|
+
contentSnapshotMechanism: contentSnapshot.mechanism,
|
|
2648
|
+
}
|
|
2649
|
+
: {}),
|
|
2650
|
+
...(pluginTreeContentSnapshot
|
|
2651
|
+
? {
|
|
2652
|
+
pluginTreeContentSnapshot: true,
|
|
2653
|
+
pluginTreeContentSnapshotScope: pluginTreeSnapshot.scope,
|
|
2654
|
+
pluginTreeContentSnapshotMechanism: pluginTreeSnapshot.mechanism,
|
|
2655
|
+
pluginTreeContentSnapshotFiles: pluginTreeSnapshot.files,
|
|
2656
|
+
pluginTreeContentSnapshotBytes: pluginTreeSnapshot.bytes,
|
|
2657
|
+
pluginTreeContentSnapshotDigest: pluginTreeSnapshot.digest,
|
|
2658
|
+
pluginTreeSnapshotConsistency: pluginTreeSnapshot.consistency,
|
|
2659
|
+
pluginTreeSnapshotContractBound: pluginTreeSnapshot.contractBound,
|
|
2660
|
+
pluginTreeSnapshotAtomic: pluginTreeSnapshot.atomic,
|
|
2661
|
+
}
|
|
2662
|
+
: {}),
|
|
2663
|
+
supervisorDescriptorBound,
|
|
2664
|
+
...(supervisorDescriptorBound
|
|
2665
|
+
? {
|
|
2666
|
+
supervisorExecutablePinned: true,
|
|
2667
|
+
supervisorBindingScope: supervisorBinding.scope,
|
|
2668
|
+
supervisorDescriptorBindingMechanism: supervisorBinding.mechanism,
|
|
2669
|
+
supervisorDescriptorContained: runnable,
|
|
2670
|
+
supervisorDescriptorConsumedBeforeTarget: runnable,
|
|
2671
|
+
supervisorStagingPathHidden: runnable,
|
|
2672
|
+
supervisorTemporaryCopyObscured: runnable,
|
|
2673
|
+
supervisorPid1ExecutableExposure:
|
|
2674
|
+
supervisorBinding.pid1ExecutableExposure,
|
|
2675
|
+
supervisorExecutableIdentity: {
|
|
2676
|
+
path: supervisorBinding.path,
|
|
2677
|
+
fileId: supervisorBinding.fileId,
|
|
2678
|
+
sha256: supervisorBinding.sha256,
|
|
2679
|
+
bytes: supervisorBinding.bytes,
|
|
2680
|
+
mtimeMs: supervisorBinding.mtimeMs,
|
|
2681
|
+
mode: supervisorBinding.mode,
|
|
2682
|
+
uid: supervisorBinding.uid,
|
|
2683
|
+
gid: supervisorBinding.gid,
|
|
2684
|
+
},
|
|
2685
|
+
}
|
|
2686
|
+
: {}),
|
|
2597
2687
|
handleAtomic: false,
|
|
2598
2688
|
};
|
|
2599
2689
|
}
|
|
@@ -2836,7 +2926,7 @@ function assignLinuxMountChildFds(mounts) {
|
|
|
2836
2926
|
function linuxStdioWithPinnedMounts(
|
|
2837
2927
|
stdio,
|
|
2838
2928
|
descriptors,
|
|
2839
|
-
{ probe = false } = {},
|
|
2929
|
+
{ probe = false, supervisorFd = null } = {},
|
|
2840
2930
|
) {
|
|
2841
2931
|
let standard;
|
|
2842
2932
|
if (probe) {
|
|
@@ -2849,8 +2939,10 @@ function linuxStdioWithPinnedMounts(
|
|
|
2849
2939
|
standard = ["pipe", "pipe", "pipe"];
|
|
2850
2940
|
}
|
|
2851
2941
|
while (standard.length < 3) standard.push(undefined);
|
|
2942
|
+
const supervisor = Number.isInteger(supervisorFd) ? [supervisorFd] : [];
|
|
2852
2943
|
return [
|
|
2853
2944
|
...standard.slice(0, 3),
|
|
2945
|
+
...supervisor,
|
|
2854
2946
|
...descriptors.map((descriptor) => descriptor.fd),
|
|
2855
2947
|
];
|
|
2856
2948
|
}
|
|
@@ -2862,10 +2954,13 @@ function validateLinuxPluginContract(
|
|
|
2862
2954
|
contract,
|
|
2863
2955
|
runtime,
|
|
2864
2956
|
sync,
|
|
2957
|
+
{ sealedEntry = false } = {},
|
|
2865
2958
|
) {
|
|
2866
2959
|
const nodeContract = contract?.kind === "strict-plugin-node-bin";
|
|
2867
2960
|
const nativeContract =
|
|
2868
2961
|
contract?.kind === "strict-plugin-native-static-elf-bin";
|
|
2962
|
+
const entryIsSealed =
|
|
2963
|
+
(nodeContract || nativeContract) && sealedEntry === true;
|
|
2869
2964
|
if (
|
|
2870
2965
|
!contract ||
|
|
2871
2966
|
contract.contractVersion !== 1 ||
|
|
@@ -2973,9 +3068,10 @@ function validateLinuxPluginContract(
|
|
|
2973
3068
|
}
|
|
2974
3069
|
if (
|
|
2975
3070
|
!linuxDirectoryIdentityMatches(runtime, contract.rootIdentity) ||
|
|
2976
|
-
!
|
|
2977
|
-
|
|
2978
|
-
|
|
3071
|
+
(!entryIsSealed &&
|
|
3072
|
+
!linuxIdentityMatches(runtime, contract.entryIdentity, {
|
|
3073
|
+
executable: nativeContract,
|
|
3074
|
+
})) ||
|
|
2979
3075
|
!linuxIdentityMatches(runtime, contract.runtimeIdentity, {
|
|
2980
3076
|
executable: true,
|
|
2981
3077
|
})
|
|
@@ -2983,7 +3079,7 @@ function validateLinuxPluginContract(
|
|
|
2983
3079
|
return { ok: false, reason: "execution_identity_changed" };
|
|
2984
3080
|
}
|
|
2985
3081
|
let entryFormat = null;
|
|
2986
|
-
if (nativeContract) {
|
|
3082
|
+
if (nativeContract && !entryIsSealed) {
|
|
2987
3083
|
try {
|
|
2988
3084
|
entryFormat = inspectLinuxStaticNativePath(
|
|
2989
3085
|
runtime,
|
|
@@ -3073,6 +3169,500 @@ function hashLinuxOpenFile(runtime, fd, bytes) {
|
|
|
3073
3169
|
return digest.digest("hex");
|
|
3074
3170
|
}
|
|
3075
3171
|
|
|
3172
|
+
function linuxEntrySnapshotContract(targetRuntime) {
|
|
3173
|
+
if (targetRuntime === "native-static-elf") {
|
|
3174
|
+
return {
|
|
3175
|
+
scope: LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE,
|
|
3176
|
+
targetMode: LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE,
|
|
3177
|
+
minimumBytes: LINUX_ELF64_HEADER_BYTES,
|
|
3178
|
+
errorPrefix: "native_entry_snapshot",
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
if (targetRuntime === "node") {
|
|
3182
|
+
return {
|
|
3183
|
+
scope: LINUX_NODE_ENTRY_SNAPSHOT_SCOPE,
|
|
3184
|
+
targetMode: LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE,
|
|
3185
|
+
minimumBytes: 0,
|
|
3186
|
+
errorPrefix: "node_entry_snapshot",
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
3189
|
+
throw new Error("entry_snapshot_runtime_unsupported");
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
function linuxNodePluginTreeFileSnapshotContract(sourceMode) {
|
|
3193
|
+
if (!Number.isSafeInteger(sourceMode) || sourceMode < 0) {
|
|
3194
|
+
throw new Error("node_plugin_tree_snapshot_source_mode_invalid");
|
|
3195
|
+
}
|
|
3196
|
+
return {
|
|
3197
|
+
scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
|
|
3198
|
+
targetMode: (sourceMode & 0o111) !== 0 ? "0500" : "0400",
|
|
3199
|
+
minimumBytes: 0,
|
|
3200
|
+
errorPrefix: "node_plugin_tree_snapshot",
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
function createLinuxRegularFileSnapshot(
|
|
3205
|
+
runtime,
|
|
3206
|
+
entryMount,
|
|
3207
|
+
entryIdentity,
|
|
3208
|
+
snapshotContract,
|
|
3209
|
+
) {
|
|
3210
|
+
let writerFd;
|
|
3211
|
+
let probeFd;
|
|
3212
|
+
let finalFd;
|
|
3213
|
+
try {
|
|
3214
|
+
if (
|
|
3215
|
+
!snapshotContract ||
|
|
3216
|
+
typeof snapshotContract.scope !== "string" ||
|
|
3217
|
+
typeof snapshotContract.targetMode !== "string" ||
|
|
3218
|
+
!Number.isSafeInteger(snapshotContract.minimumBytes) ||
|
|
3219
|
+
typeof snapshotContract.errorPrefix !== "string"
|
|
3220
|
+
) {
|
|
3221
|
+
throw new Error("file_snapshot_contract_invalid");
|
|
3222
|
+
}
|
|
3223
|
+
const snapshotError = (reason) =>
|
|
3224
|
+
new Error(`${snapshotContract.errorPrefix}_${reason}`);
|
|
3225
|
+
const expectedSha256 = entryIdentity?.sha256 ?? null;
|
|
3226
|
+
for (const method of [
|
|
3227
|
+
"openSync",
|
|
3228
|
+
"readSync",
|
|
3229
|
+
"writeSync",
|
|
3230
|
+
"fchmodSync",
|
|
3231
|
+
"fsyncSync",
|
|
3232
|
+
"fstatSync",
|
|
3233
|
+
"closeSync",
|
|
3234
|
+
]) {
|
|
3235
|
+
if (typeof runtime.fs[method] !== "function") {
|
|
3236
|
+
throw snapshotError(`${method}_unavailable`);
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
if (
|
|
3240
|
+
!Number.isInteger(entryMount?.fd) ||
|
|
3241
|
+
!entryIdentity ||
|
|
3242
|
+
(expectedSha256 !== null &&
|
|
3243
|
+
(typeof expectedSha256 !== "string" ||
|
|
3244
|
+
!/^[a-f0-9]{64}$/.test(expectedSha256))) ||
|
|
3245
|
+
!Number.isSafeInteger(entryIdentity.bytes) ||
|
|
3246
|
+
entryIdentity.bytes < snapshotContract.minimumBytes ||
|
|
3247
|
+
entryIdentity.bytes > LINUX_ATTESTED_FILE_MAX_BYTES
|
|
3248
|
+
) {
|
|
3249
|
+
throw snapshotError("identity_invalid");
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
const sourceBefore = runtime.fs.fstatSync(entryMount.fd);
|
|
3253
|
+
const sourceFileMode = Number(sourceBefore.mode) & 0o7777;
|
|
3254
|
+
if (
|
|
3255
|
+
!sourceBefore.isFile() ||
|
|
3256
|
+
Number(sourceBefore.nlink) !== 1 ||
|
|
3257
|
+
String(sourceBefore.dev) !== String(entryIdentity.fileId?.dev) ||
|
|
3258
|
+
String(sourceBefore.ino) !== String(entryIdentity.fileId?.ino) ||
|
|
3259
|
+
Number(sourceBefore.size) !== entryIdentity.bytes ||
|
|
3260
|
+
Number(sourceBefore.mtimeMs) !== Number(entryIdentity.mtimeMs)
|
|
3261
|
+
) {
|
|
3262
|
+
throw snapshotError("source_changed");
|
|
3263
|
+
}
|
|
3264
|
+
if (
|
|
3265
|
+
snapshotContract.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
|
|
3266
|
+
snapshotContract.targetMode !==
|
|
3267
|
+
linuxNodePluginTreeFileSnapshotContract(sourceFileMode).targetMode
|
|
3268
|
+
) {
|
|
3269
|
+
throw snapshotError("source_mode_changed");
|
|
3270
|
+
}
|
|
3271
|
+
if (
|
|
3272
|
+
snapshotContract.scope === LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE &&
|
|
3273
|
+
(sourceFileMode & 0o111) === 0
|
|
3274
|
+
) {
|
|
3275
|
+
throw snapshotError("source_mode_changed");
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
3279
|
+
const writerFlags =
|
|
3280
|
+
Number(constants.O_RDWR) |
|
|
3281
|
+
Number(constants.O_EXCL) |
|
|
3282
|
+
Number(constants.O_TMPFILE ?? LINUX_O_TMPFILE) |
|
|
3283
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
3284
|
+
Number(constants.O_NONBLOCK || 0);
|
|
3285
|
+
writerFd = runtime.fs.openSync(
|
|
3286
|
+
"/tmp",
|
|
3287
|
+
writerFlags,
|
|
3288
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE,
|
|
3289
|
+
);
|
|
3290
|
+
|
|
3291
|
+
const sourceDigest = crypto.createHash("sha256");
|
|
3292
|
+
const chunk = Buffer.allocUnsafe(
|
|
3293
|
+
Math.max(
|
|
3294
|
+
1,
|
|
3295
|
+
Math.min(LINUX_ATTESTATION_HASH_CHUNK_BYTES, entryIdentity.bytes),
|
|
3296
|
+
),
|
|
3297
|
+
);
|
|
3298
|
+
let copied = 0;
|
|
3299
|
+
while (copied < entryIdentity.bytes) {
|
|
3300
|
+
const read = runtime.fs.readSync(
|
|
3301
|
+
entryMount.fd,
|
|
3302
|
+
chunk,
|
|
3303
|
+
0,
|
|
3304
|
+
Math.min(chunk.length, entryIdentity.bytes - copied),
|
|
3305
|
+
copied,
|
|
3306
|
+
);
|
|
3307
|
+
if (read <= 0) {
|
|
3308
|
+
throw snapshotError("source_ended_early");
|
|
3309
|
+
}
|
|
3310
|
+
sourceDigest.update(chunk.subarray(0, read));
|
|
3311
|
+
let written = 0;
|
|
3312
|
+
while (written < read) {
|
|
3313
|
+
const count = runtime.fs.writeSync(
|
|
3314
|
+
writerFd,
|
|
3315
|
+
chunk,
|
|
3316
|
+
written,
|
|
3317
|
+
read - written,
|
|
3318
|
+
copied + written,
|
|
3319
|
+
);
|
|
3320
|
+
if (count <= 0) {
|
|
3321
|
+
throw snapshotError("write_failed");
|
|
3322
|
+
}
|
|
3323
|
+
written += count;
|
|
3324
|
+
}
|
|
3325
|
+
copied += read;
|
|
3326
|
+
}
|
|
3327
|
+
const sourceAfter = runtime.fs.fstatSync(entryMount.fd);
|
|
3328
|
+
const sourceSha256 = sourceDigest.digest("hex");
|
|
3329
|
+
if (
|
|
3330
|
+
!linuxOpenStatMatches(sourceBefore, sourceAfter) ||
|
|
3331
|
+
(expectedSha256 !== null && sourceSha256 !== expectedSha256)
|
|
3332
|
+
) {
|
|
3333
|
+
throw snapshotError("source_changed");
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
runtime.fs.fsyncSync(writerFd);
|
|
3337
|
+
const snapshotBeforeRead = runtime.fs.fstatSync(writerFd);
|
|
3338
|
+
if (
|
|
3339
|
+
!snapshotBeforeRead.isFile() ||
|
|
3340
|
+
Number(snapshotBeforeRead.nlink) !== 0 ||
|
|
3341
|
+
Number(snapshotBeforeRead.size) !== entryIdentity.bytes
|
|
3342
|
+
) {
|
|
3343
|
+
throw snapshotError("identity_changed");
|
|
3344
|
+
}
|
|
3345
|
+
const snapshotSha256 = hashLinuxOpenFile(
|
|
3346
|
+
runtime,
|
|
3347
|
+
writerFd,
|
|
3348
|
+
entryIdentity.bytes,
|
|
3349
|
+
);
|
|
3350
|
+
const snapshotAfterRead = runtime.fs.fstatSync(writerFd);
|
|
3351
|
+
if (
|
|
3352
|
+
!linuxOpenStatMatches(snapshotBeforeRead, snapshotAfterRead) ||
|
|
3353
|
+
snapshotSha256 !== sourceSha256
|
|
3354
|
+
) {
|
|
3355
|
+
throw snapshotError("identity_changed");
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
runtime.fs.fchmodSync(writerFd, LINUX_ENTRY_SNAPSHOT_SOURCE_MODE);
|
|
3359
|
+
runtime.fs.fsyncSync(writerFd);
|
|
3360
|
+
const sealedBeforeRead = runtime.fs.fstatSync(writerFd);
|
|
3361
|
+
if (
|
|
3362
|
+
!sealedBeforeRead.isFile() ||
|
|
3363
|
+
Number(sealedBeforeRead.nlink) !== 0 ||
|
|
3364
|
+
Number(sealedBeforeRead.size) !== entryIdentity.bytes ||
|
|
3365
|
+
(Number(sealedBeforeRead.mode) & 0o777) !==
|
|
3366
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE
|
|
3367
|
+
) {
|
|
3368
|
+
throw snapshotError("readonly_mode_unattested");
|
|
3369
|
+
}
|
|
3370
|
+
const sealedSha256 = hashLinuxOpenFile(
|
|
3371
|
+
runtime,
|
|
3372
|
+
writerFd,
|
|
3373
|
+
entryIdentity.bytes,
|
|
3374
|
+
);
|
|
3375
|
+
const sealed = runtime.fs.fstatSync(writerFd);
|
|
3376
|
+
if (
|
|
3377
|
+
!linuxOpenStatMatches(sealedBeforeRead, sealed) ||
|
|
3378
|
+
sealedSha256 !== sourceSha256
|
|
3379
|
+
) {
|
|
3380
|
+
throw snapshotError("identity_changed");
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
const readerFlags =
|
|
3384
|
+
Number(constants.O_RDONLY) | Number(constants.O_NONBLOCK || 0);
|
|
3385
|
+
probeFd = runtime.fs.openSync(`/proc/self/fd/${writerFd}`, readerFlags);
|
|
3386
|
+
finalFd = runtime.fs.openSync(`/proc/self/fd/${writerFd}`, readerFlags);
|
|
3387
|
+
if (probeFd === finalFd) {
|
|
3388
|
+
throw snapshotError("readers_not_independent");
|
|
3389
|
+
}
|
|
3390
|
+
for (const fd of [probeFd, finalFd]) {
|
|
3391
|
+
const reader = runtime.fs.fstatSync(fd);
|
|
3392
|
+
if (!linuxOpenStatMatches(sealed, reader)) {
|
|
3393
|
+
throw snapshotError("reader_identity_changed");
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
const ownedWriterFd = writerFd;
|
|
3397
|
+
writerFd = undefined;
|
|
3398
|
+
runtime.fs.closeSync(ownedWriterFd);
|
|
3399
|
+
|
|
3400
|
+
const verifyClosedWriterSnapshot = runtime.fs.fstatSync(finalFd);
|
|
3401
|
+
if (!linuxOpenStatMatches(sealed, verifyClosedWriterSnapshot)) {
|
|
3402
|
+
throw snapshotError("reader_identity_changed");
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
const attestation = Object.freeze({
|
|
3406
|
+
scope: snapshotContract.scope,
|
|
3407
|
+
mechanism: LINUX_ENTRY_SNAPSHOT_MECHANISM,
|
|
3408
|
+
sha256: sourceSha256,
|
|
3409
|
+
bytes: entryIdentity.bytes,
|
|
3410
|
+
sourceFileId: Object.freeze({
|
|
3411
|
+
dev: String(entryIdentity.fileId.dev),
|
|
3412
|
+
ino: String(entryIdentity.fileId.ino),
|
|
3413
|
+
}),
|
|
3414
|
+
sourceMtimeMs: Number(entryIdentity.mtimeMs),
|
|
3415
|
+
sourceFileMode: sourceFileMode.toString(8).padStart(4, "0"),
|
|
3416
|
+
sourceMode: LINUX_ENTRY_SNAPSHOT_SOURCE_MODE.toString(8).padStart(4, "0"),
|
|
3417
|
+
targetMode: snapshotContract.targetMode,
|
|
3418
|
+
});
|
|
3419
|
+
const descriptor = (fd) => {
|
|
3420
|
+
const stat = runtime.fs.fstatSync(fd);
|
|
3421
|
+
return {
|
|
3422
|
+
...entryMount,
|
|
3423
|
+
fd,
|
|
3424
|
+
fileId: {
|
|
3425
|
+
dev: String(stat.dev),
|
|
3426
|
+
ino: String(stat.ino),
|
|
3427
|
+
},
|
|
3428
|
+
bytes: Number(stat.size),
|
|
3429
|
+
mtimeMs: Number(stat.mtimeMs),
|
|
3430
|
+
mountMode: "ro-bind-data",
|
|
3431
|
+
mountPermissions: snapshotContract.targetMode,
|
|
3432
|
+
contentSnapshot: attestation,
|
|
3433
|
+
snapshotIdentity: Object.freeze({
|
|
3434
|
+
sha256: sourceSha256,
|
|
3435
|
+
bytes: entryIdentity.bytes,
|
|
3436
|
+
fileId: Object.freeze({
|
|
3437
|
+
dev: String(stat.dev),
|
|
3438
|
+
ino: String(stat.ino),
|
|
3439
|
+
}),
|
|
3440
|
+
mtimeMs: Number(stat.mtimeMs),
|
|
3441
|
+
}),
|
|
3442
|
+
};
|
|
3443
|
+
};
|
|
3444
|
+
const result = {
|
|
3445
|
+
attestation,
|
|
3446
|
+
probeMount: descriptor(probeFd),
|
|
3447
|
+
finalMount: descriptor(finalFd),
|
|
3448
|
+
};
|
|
3449
|
+
probeFd = undefined;
|
|
3450
|
+
finalFd = undefined;
|
|
3451
|
+
return result;
|
|
3452
|
+
} catch (error) {
|
|
3453
|
+
closeLinuxPinnedMounts(
|
|
3454
|
+
runtime,
|
|
3455
|
+
[probeFd, finalFd, writerFd]
|
|
3456
|
+
.filter(Number.isInteger)
|
|
3457
|
+
.map((fd) => ({ fd })),
|
|
3458
|
+
);
|
|
3459
|
+
throw error;
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
function createLinuxEntrySnapshot(
|
|
3464
|
+
runtime,
|
|
3465
|
+
entryMount,
|
|
3466
|
+
entryIdentity,
|
|
3467
|
+
targetRuntime,
|
|
3468
|
+
) {
|
|
3469
|
+
return createLinuxRegularFileSnapshot(
|
|
3470
|
+
runtime,
|
|
3471
|
+
entryMount,
|
|
3472
|
+
entryIdentity,
|
|
3473
|
+
linuxEntrySnapshotContract(targetRuntime),
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
function attestLinuxRegularFileSnapshot(
|
|
3478
|
+
runtime,
|
|
3479
|
+
mount,
|
|
3480
|
+
attestation,
|
|
3481
|
+
snapshotContract,
|
|
3482
|
+
) {
|
|
3483
|
+
const errorPrefix = snapshotContract.errorPrefix;
|
|
3484
|
+
const before = runtime.fs.fstatSync(mount?.fd);
|
|
3485
|
+
if (
|
|
3486
|
+
attestation?.scope !== snapshotContract.scope ||
|
|
3487
|
+
attestation?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
|
|
3488
|
+
attestation?.sourceMode !==
|
|
3489
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE.toString(8).padStart(4, "0") ||
|
|
3490
|
+
typeof attestation?.sourceFileMode !== "string" ||
|
|
3491
|
+
!/^[0-7]{4}$/.test(attestation.sourceFileMode) ||
|
|
3492
|
+
attestation?.targetMode !== snapshotContract.targetMode ||
|
|
3493
|
+
mount?.mountMode !== "ro-bind-data" ||
|
|
3494
|
+
mount?.mountPermissions !== snapshotContract.targetMode ||
|
|
3495
|
+
mount?.contentSnapshot !== attestation ||
|
|
3496
|
+
!before.isFile() ||
|
|
3497
|
+
Number(before.nlink) !== 0 ||
|
|
3498
|
+
(Number(before.mode) & 0o777) !== LINUX_ENTRY_SNAPSHOT_SOURCE_MODE ||
|
|
3499
|
+
String(before.dev) !== String(mount?.snapshotIdentity?.fileId?.dev) ||
|
|
3500
|
+
String(before.ino) !== String(mount?.snapshotIdentity?.fileId?.ino) ||
|
|
3501
|
+
Number(before.size) !== Number(attestation?.bytes) ||
|
|
3502
|
+
Number(before.size) !== Number(mount?.snapshotIdentity?.bytes) ||
|
|
3503
|
+
Number(before.mtimeMs) !== Number(mount?.snapshotIdentity?.mtimeMs)
|
|
3504
|
+
) {
|
|
3505
|
+
throw new Error(`${errorPrefix}_identity_changed`);
|
|
3506
|
+
}
|
|
3507
|
+
const sha256 = hashLinuxOpenFile(runtime, mount.fd, Number(before.size));
|
|
3508
|
+
const after = runtime.fs.fstatSync(mount.fd);
|
|
3509
|
+
if (
|
|
3510
|
+
!linuxOpenStatMatches(before, after) ||
|
|
3511
|
+
sha256 !== attestation?.sha256 ||
|
|
3512
|
+
sha256 !== mount.snapshotIdentity.sha256
|
|
3513
|
+
) {
|
|
3514
|
+
throw new Error(`${errorPrefix}_identity_changed`);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
function attestLinuxEntrySnapshot(runtime, mount, attestation, targetRuntime) {
|
|
3519
|
+
attestLinuxRegularFileSnapshot(
|
|
3520
|
+
runtime,
|
|
3521
|
+
mount,
|
|
3522
|
+
attestation,
|
|
3523
|
+
linuxEntrySnapshotContract(targetRuntime),
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
|
|
3528
|
+
const members = [];
|
|
3529
|
+
const destinations = new Set();
|
|
3530
|
+
for (const mount of mounts || []) {
|
|
3531
|
+
if (!linuxPathWithin("/opt/chainless/plugin", mount?.destination)) continue;
|
|
3532
|
+
if (
|
|
3533
|
+
path.posix.normalize(mount.destination) !== mount.destination ||
|
|
3534
|
+
destinations.has(mount.destination)
|
|
3535
|
+
) {
|
|
3536
|
+
throw new Error("node_plugin_tree_snapshot_destination_invalid");
|
|
3537
|
+
}
|
|
3538
|
+
destinations.add(mount.destination);
|
|
3539
|
+
const snapshot = mount.contentSnapshot;
|
|
3540
|
+
const entry = mount.destination === entryDestination;
|
|
3541
|
+
const expectedScope = entry
|
|
3542
|
+
? LINUX_NODE_ENTRY_SNAPSHOT_SCOPE
|
|
3543
|
+
: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE;
|
|
3544
|
+
const sourceMode = Number.parseInt(snapshot?.sourceFileMode, 8);
|
|
3545
|
+
const expectedTargetMode = entry
|
|
3546
|
+
? LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE
|
|
3547
|
+
: linuxNodePluginTreeFileSnapshotContract(sourceMode).targetMode;
|
|
3548
|
+
if (
|
|
3549
|
+
snapshot?.scope !== expectedScope ||
|
|
3550
|
+
snapshot?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
|
|
3551
|
+
typeof snapshot?.sha256 !== "string" ||
|
|
3552
|
+
!/^[a-f0-9]{64}$/.test(snapshot.sha256) ||
|
|
3553
|
+
typeof snapshot?.sourceFileId !== "object" ||
|
|
3554
|
+
typeof snapshot.sourceFileId?.dev !== "string" ||
|
|
3555
|
+
!/^\d+$/.test(snapshot.sourceFileId.dev) ||
|
|
3556
|
+
typeof snapshot.sourceFileId?.ino !== "string" ||
|
|
3557
|
+
!/^\d+$/.test(snapshot.sourceFileId.ino) ||
|
|
3558
|
+
!Number.isFinite(snapshot?.sourceMtimeMs) ||
|
|
3559
|
+
!Number.isSafeInteger(snapshot?.bytes) ||
|
|
3560
|
+
snapshot.bytes < 0 ||
|
|
3561
|
+
snapshot.bytes > LINUX_ATTESTED_FILE_MAX_BYTES ||
|
|
3562
|
+
typeof snapshot?.sourceFileMode !== "string" ||
|
|
3563
|
+
!/^[0-7]{4}$/.test(snapshot.sourceFileMode) ||
|
|
3564
|
+
snapshot.sourceMode !==
|
|
3565
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE.toString(8).padStart(4, "0") ||
|
|
3566
|
+
snapshot.targetMode !== expectedTargetMode ||
|
|
3567
|
+
mount.mountMode !== "ro-bind-data" ||
|
|
3568
|
+
mount.mountPermissions !== expectedTargetMode
|
|
3569
|
+
) {
|
|
3570
|
+
throw new Error("node_plugin_tree_snapshot_member_invalid");
|
|
3571
|
+
}
|
|
3572
|
+
members.push({
|
|
3573
|
+
destination: mount.destination,
|
|
3574
|
+
sourceFileId: snapshot.sourceFileId,
|
|
3575
|
+
sourceMtimeMs: snapshot.sourceMtimeMs,
|
|
3576
|
+
sourceFileMode: snapshot.sourceFileMode,
|
|
3577
|
+
sha256: snapshot.sha256,
|
|
3578
|
+
bytes: snapshot.bytes,
|
|
3579
|
+
sourceMode: snapshot.sourceMode,
|
|
3580
|
+
targetMode: snapshot.targetMode,
|
|
3581
|
+
scope: snapshot.scope,
|
|
3582
|
+
mechanism: snapshot.mechanism,
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
members.sort((left, right) =>
|
|
3586
|
+
left.destination < right.destination
|
|
3587
|
+
? -1
|
|
3588
|
+
: left.destination > right.destination
|
|
3589
|
+
? 1
|
|
3590
|
+
: 0,
|
|
3591
|
+
);
|
|
3592
|
+
if (
|
|
3593
|
+
members.length < 1 ||
|
|
3594
|
+
members.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES ||
|
|
3595
|
+
!members.some((member) => member.destination === entryDestination)
|
|
3596
|
+
) {
|
|
3597
|
+
throw new Error("node_plugin_tree_snapshot_file_count_invalid");
|
|
3598
|
+
}
|
|
3599
|
+
const bytes = members.reduce((total, member) => total + member.bytes, 0);
|
|
3600
|
+
if (
|
|
3601
|
+
!Number.isSafeInteger(bytes) ||
|
|
3602
|
+
bytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
|
|
3603
|
+
) {
|
|
3604
|
+
throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
|
|
3605
|
+
}
|
|
3606
|
+
const digest = crypto
|
|
3607
|
+
.createHash("sha256")
|
|
3608
|
+
.update(
|
|
3609
|
+
JSON.stringify({
|
|
3610
|
+
version: 1,
|
|
3611
|
+
scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
|
|
3612
|
+
consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
|
|
3613
|
+
members,
|
|
3614
|
+
}),
|
|
3615
|
+
)
|
|
3616
|
+
.digest("hex");
|
|
3617
|
+
return Object.freeze({
|
|
3618
|
+
scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
|
|
3619
|
+
mechanism: LINUX_ENTRY_SNAPSHOT_MECHANISM,
|
|
3620
|
+
files: members.length,
|
|
3621
|
+
bytes,
|
|
3622
|
+
digest,
|
|
3623
|
+
consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
|
|
3624
|
+
contractBound: false,
|
|
3625
|
+
atomic: false,
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
function attestLinuxNodePluginTreeSnapshot(
|
|
3630
|
+
runtime,
|
|
3631
|
+
mounts,
|
|
3632
|
+
entryDestination,
|
|
3633
|
+
expected,
|
|
3634
|
+
) {
|
|
3635
|
+
for (const mount of mounts || []) {
|
|
3636
|
+
if (!linuxPathWithin("/opt/chainless/plugin", mount?.destination)) continue;
|
|
3637
|
+
const snapshot = mount.contentSnapshot;
|
|
3638
|
+
const contract =
|
|
3639
|
+
mount.destination === entryDestination
|
|
3640
|
+
? linuxEntrySnapshotContract("node")
|
|
3641
|
+
: linuxNodePluginTreeFileSnapshotContract(
|
|
3642
|
+
Number.parseInt(snapshot?.sourceFileMode, 8),
|
|
3643
|
+
);
|
|
3644
|
+
attestLinuxRegularFileSnapshot(runtime, mount, snapshot, contract);
|
|
3645
|
+
}
|
|
3646
|
+
const actual = buildLinuxNodePluginTreeSnapshotAttestation(
|
|
3647
|
+
mounts,
|
|
3648
|
+
entryDestination,
|
|
3649
|
+
);
|
|
3650
|
+
for (const field of [
|
|
3651
|
+
"scope",
|
|
3652
|
+
"mechanism",
|
|
3653
|
+
"files",
|
|
3654
|
+
"bytes",
|
|
3655
|
+
"digest",
|
|
3656
|
+
"consistency",
|
|
3657
|
+
"contractBound",
|
|
3658
|
+
"atomic",
|
|
3659
|
+
]) {
|
|
3660
|
+
if (actual[field] !== expected?.[field]) {
|
|
3661
|
+
throw new Error("node_plugin_tree_snapshot_identity_changed");
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3076
3666
|
/**
|
|
3077
3667
|
* Classify one already-pinned plugin entry without invoking it or any host
|
|
3078
3668
|
* inspection utility. The native slice accepts conventional fully static
|
|
@@ -3355,8 +3945,12 @@ function linuxFdMountId(runtime, fd) {
|
|
|
3355
3945
|
function closeLinuxPinnedMounts(runtime, mounts) {
|
|
3356
3946
|
for (const mount of mounts || []) {
|
|
3357
3947
|
if (!Number.isInteger(mount?.fd)) continue;
|
|
3948
|
+
const fd = mount.fd;
|
|
3949
|
+
// Clear ownership before close so repeated cleanup cannot close an
|
|
3950
|
+
// unrelated descriptor if the OS has already reused this number.
|
|
3951
|
+
mount.fd = null;
|
|
3358
3952
|
try {
|
|
3359
|
-
runtime.fs.closeSync(
|
|
3953
|
+
runtime.fs.closeSync(fd);
|
|
3360
3954
|
} catch {
|
|
3361
3955
|
// Best effort. The process is already fail-closed if a pin cannot be
|
|
3362
3956
|
// created; cleanup must not replace the original boundary error.
|
|
@@ -3706,13 +4300,209 @@ function attestLinuxBubblewrapBinary(runtime) {
|
|
|
3706
4300
|
);
|
|
3707
4301
|
}
|
|
3708
4302
|
|
|
3709
|
-
function
|
|
4303
|
+
function linuxBubblewrapSupervisorStatValid(
|
|
4304
|
+
stat,
|
|
4305
|
+
{ allowUnlinked = false } = {},
|
|
4306
|
+
) {
|
|
4307
|
+
const linkCount = Number(stat?.nlink);
|
|
4308
|
+
return (
|
|
4309
|
+
stat?.isFile?.() === true &&
|
|
4310
|
+
Number(stat.uid) === 0 &&
|
|
4311
|
+
(linkCount === 1 || (allowUnlinked && linkCount === 0)) &&
|
|
4312
|
+
Number(stat.size) > 0 &&
|
|
4313
|
+
Number(stat.size) <= LINUX_ATTESTED_FILE_MAX_BYTES &&
|
|
4314
|
+
(Number(stat.mode) & 0o111) !== 0 &&
|
|
4315
|
+
(Number(stat.mode) & 0o022) === 0
|
|
4316
|
+
);
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
function linuxBubblewrapPinnedStatMatches(expected, current) {
|
|
4320
|
+
return (
|
|
4321
|
+
linuxBubblewrapSupervisorStatValid(current, { allowUnlinked: true }) &&
|
|
4322
|
+
String(expected.dev) === String(current.dev) &&
|
|
4323
|
+
String(expected.ino) === String(current.ino) &&
|
|
4324
|
+
Number(expected.size) === Number(current.size) &&
|
|
4325
|
+
Number(expected.mtimeMs) === Number(current.mtimeMs) &&
|
|
4326
|
+
Number(expected.mode) === Number(current.mode) &&
|
|
4327
|
+
Number(expected.uid) === Number(current.uid) &&
|
|
4328
|
+
Number(expected.gid) === Number(current.gid)
|
|
4329
|
+
);
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
function pinLinuxBubblewrapSupervisor(runtime) {
|
|
4333
|
+
let fd;
|
|
4334
|
+
try {
|
|
4335
|
+
for (const method of [
|
|
4336
|
+
"openSync",
|
|
4337
|
+
"readSync",
|
|
4338
|
+
"fstatSync",
|
|
4339
|
+
"statSync",
|
|
4340
|
+
"lstatSync",
|
|
4341
|
+
"closeSync",
|
|
4342
|
+
]) {
|
|
4343
|
+
if (typeof runtime.fs?.[method] !== "function") {
|
|
4344
|
+
throw new Error(`supervisor_${method}_unavailable`);
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
if (linuxRealpath(runtime, LINUX_BWRAP_PATH) !== LINUX_BWRAP_PATH) {
|
|
4348
|
+
throw new Error("supervisor_realpath_changed");
|
|
4349
|
+
}
|
|
4350
|
+
const pathBefore = runtime.fs.lstatSync(LINUX_BWRAP_PATH, {
|
|
4351
|
+
bigint: true,
|
|
4352
|
+
});
|
|
4353
|
+
if (
|
|
4354
|
+
pathBefore.isSymbolicLink?.() === true ||
|
|
4355
|
+
!linuxBubblewrapSupervisorStatValid(pathBefore)
|
|
4356
|
+
) {
|
|
4357
|
+
throw new Error("supervisor_path_unattested");
|
|
4358
|
+
}
|
|
4359
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
4360
|
+
const flags =
|
|
4361
|
+
Number(constants.O_RDONLY) |
|
|
4362
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
4363
|
+
Number(constants.O_NONBLOCK || 0);
|
|
4364
|
+
fd = runtime.fs.openSync(LINUX_BWRAP_PATH, flags);
|
|
4365
|
+
const openedBefore = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4366
|
+
const currentPath = runtime.fs.statSync(LINUX_BWRAP_PATH, {
|
|
4367
|
+
bigint: true,
|
|
4368
|
+
});
|
|
4369
|
+
if (
|
|
4370
|
+
!linuxBubblewrapSupervisorStatValid(openedBefore) ||
|
|
4371
|
+
!linuxOpenStatMatches(pathBefore, openedBefore) ||
|
|
4372
|
+
!linuxOpenStatMatches(openedBefore, currentPath)
|
|
4373
|
+
) {
|
|
4374
|
+
throw new Error("supervisor_identity_changed");
|
|
4375
|
+
}
|
|
4376
|
+
const sha256 = hashLinuxOpenFile(runtime, fd, Number(openedBefore.size));
|
|
4377
|
+
const openedAfter = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4378
|
+
const pathAfter = runtime.fs.statSync(LINUX_BWRAP_PATH, {
|
|
4379
|
+
bigint: true,
|
|
4380
|
+
});
|
|
4381
|
+
if (
|
|
4382
|
+
!linuxOpenStatMatches(openedBefore, openedAfter) ||
|
|
4383
|
+
!linuxOpenStatMatches(openedAfter, pathAfter)
|
|
4384
|
+
) {
|
|
4385
|
+
throw new Error("supervisor_identity_changed");
|
|
4386
|
+
}
|
|
4387
|
+
const attestation = Object.freeze({
|
|
4388
|
+
path: LINUX_BWRAP_PATH,
|
|
4389
|
+
fileId: Object.freeze({
|
|
4390
|
+
dev: String(openedAfter.dev),
|
|
4391
|
+
ino: String(openedAfter.ino),
|
|
4392
|
+
}),
|
|
4393
|
+
sha256,
|
|
4394
|
+
bytes: Number(openedAfter.size),
|
|
4395
|
+
mtimeMs: Number(openedAfter.mtimeMs),
|
|
4396
|
+
mode: Number(openedAfter.mode),
|
|
4397
|
+
uid: Number(openedAfter.uid),
|
|
4398
|
+
gid: Number(openedAfter.gid),
|
|
4399
|
+
mechanism: LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM,
|
|
4400
|
+
scope: "host-path-replacement",
|
|
4401
|
+
childFd: LINUX_BWRAP_SUPERVISOR_CHILD_FD,
|
|
4402
|
+
consumeOperation: "file-copy-close",
|
|
4403
|
+
consumePath: LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH,
|
|
4404
|
+
consumeMode: "0000",
|
|
4405
|
+
obscuredBy: "tmpfs:/run",
|
|
4406
|
+
pid1ExecutableExposure: "procfs",
|
|
4407
|
+
});
|
|
4408
|
+
return {
|
|
4409
|
+
fd,
|
|
4410
|
+
openedStat: openedAfter,
|
|
4411
|
+
attestation,
|
|
4412
|
+
};
|
|
4413
|
+
} catch (error) {
|
|
4414
|
+
if (fd !== undefined) {
|
|
4415
|
+
try {
|
|
4416
|
+
runtime.fs.closeSync(fd);
|
|
4417
|
+
} catch {
|
|
4418
|
+
// Preserve the original attestation error.
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
throw error;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
function openLinuxBubblewrapSupervisorLaunch(runtime, supervisorPin) {
|
|
4426
|
+
let fd;
|
|
4427
|
+
try {
|
|
4428
|
+
if (
|
|
4429
|
+
!Number.isInteger(supervisorPin?.fd) ||
|
|
4430
|
+
!supervisorPin.openedStat ||
|
|
4431
|
+
!supervisorPin.attestation
|
|
4432
|
+
) {
|
|
4433
|
+
throw new Error("supervisor_pin_missing");
|
|
4434
|
+
}
|
|
4435
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
4436
|
+
const flags =
|
|
4437
|
+
Number(constants.O_RDONLY) | Number(constants.O_NONBLOCK || 0);
|
|
4438
|
+
fd = runtime.fs.openSync(`/proc/self/fd/${supervisorPin.fd}`, flags);
|
|
4439
|
+
const openedBefore = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4440
|
+
if (
|
|
4441
|
+
!linuxBubblewrapPinnedStatMatches(supervisorPin.openedStat, openedBefore)
|
|
4442
|
+
) {
|
|
4443
|
+
throw new Error("supervisor_launch_reader_identity_changed");
|
|
4444
|
+
}
|
|
4445
|
+
const sha256 = hashLinuxOpenFile(
|
|
4446
|
+
runtime,
|
|
4447
|
+
fd,
|
|
4448
|
+
supervisorPin.attestation.bytes,
|
|
4449
|
+
);
|
|
4450
|
+
const openedAfter = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4451
|
+
if (
|
|
4452
|
+
!linuxOpenStatMatches(openedBefore, openedAfter) ||
|
|
4453
|
+
sha256 !== supervisorPin.attestation.sha256
|
|
4454
|
+
) {
|
|
4455
|
+
throw new Error("supervisor_launch_reader_identity_changed");
|
|
4456
|
+
}
|
|
4457
|
+
return {
|
|
4458
|
+
fd,
|
|
4459
|
+
command: `/proc/self/fd/${LINUX_BWRAP_SUPERVISOR_CHILD_FD}`,
|
|
4460
|
+
};
|
|
4461
|
+
} catch (error) {
|
|
4462
|
+
if (fd !== undefined) {
|
|
4463
|
+
try {
|
|
4464
|
+
runtime.fs.closeSync(fd);
|
|
4465
|
+
} catch {
|
|
4466
|
+
// Preserve the original launch-reader error.
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
throw error;
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
function attestLinuxBubblewrapSupervisorPin(runtime, supervisorPin) {
|
|
4474
|
+
if (
|
|
4475
|
+
!Number.isInteger(supervisorPin?.fd) ||
|
|
4476
|
+
!supervisorPin.openedStat ||
|
|
4477
|
+
!supervisorPin.attestation
|
|
4478
|
+
) {
|
|
4479
|
+
throw new Error("supervisor_pin_missing");
|
|
4480
|
+
}
|
|
4481
|
+
const before = runtime.fs.fstatSync(supervisorPin.fd, { bigint: true });
|
|
4482
|
+
if (!linuxBubblewrapPinnedStatMatches(supervisorPin.openedStat, before)) {
|
|
4483
|
+
throw new Error("supervisor_identity_changed");
|
|
4484
|
+
}
|
|
4485
|
+
const sha256 = hashLinuxOpenFile(
|
|
4486
|
+
runtime,
|
|
4487
|
+
supervisorPin.fd,
|
|
4488
|
+
supervisorPin.attestation.bytes,
|
|
4489
|
+
);
|
|
4490
|
+
const after = runtime.fs.fstatSync(supervisorPin.fd, { bigint: true });
|
|
4491
|
+
if (
|
|
4492
|
+
!linuxOpenStatMatches(before, after) ||
|
|
4493
|
+
sha256 !== supervisorPin.attestation.sha256
|
|
4494
|
+
) {
|
|
4495
|
+
throw new Error("supervisor_identity_changed");
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4499
|
+
function attestLinuxBubblewrapCapabilities(runtime, supervisorLaunch) {
|
|
3710
4500
|
let result;
|
|
3711
4501
|
try {
|
|
3712
|
-
result = runtime.spawnSync(
|
|
4502
|
+
result = runtime.spawnSync(supervisorLaunch.command, ["--help"], {
|
|
3713
4503
|
shell: false,
|
|
3714
4504
|
encoding: "utf8",
|
|
3715
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4505
|
+
stdio: ["ignore", "pipe", "pipe", supervisorLaunch.fd],
|
|
3716
4506
|
timeout: 10_000,
|
|
3717
4507
|
env: {
|
|
3718
4508
|
PATH: "/usr/bin:/bin",
|
|
@@ -3727,13 +4517,18 @@ function attestLinuxBubblewrapCapabilities(runtime) {
|
|
|
3727
4517
|
return { ok: false, reason: "capability_probe_failed" };
|
|
3728
4518
|
}
|
|
3729
4519
|
const help = `${String(result.stdout || "")}\n${String(result.stderr || "")}`;
|
|
3730
|
-
|
|
4520
|
+
const supportedOptions = new Set(help.split(/\s+/).filter(Boolean));
|
|
4521
|
+
const requiredOptions = [
|
|
4522
|
+
"--file",
|
|
4523
|
+
"--perms",
|
|
3731
4524
|
"--ro-bind-fd",
|
|
4525
|
+
"--ro-bind-data",
|
|
3732
4526
|
"--disable-userns",
|
|
3733
4527
|
"--assert-userns-disabled",
|
|
3734
4528
|
"--seccomp",
|
|
3735
|
-
]
|
|
3736
|
-
|
|
4529
|
+
];
|
|
4530
|
+
for (const option of requiredOptions) {
|
|
4531
|
+
if (!supportedOptions.has(option)) {
|
|
3737
4532
|
return {
|
|
3738
4533
|
ok: false,
|
|
3739
4534
|
reason: `required_option_missing:${option.slice(2)}`,
|
|
@@ -3928,8 +4723,30 @@ function buildLinuxBubblewrapPolicyArgs(
|
|
|
3928
4723
|
for (const directory of orderedDirectories) {
|
|
3929
4724
|
args.push("--dir", directory);
|
|
3930
4725
|
}
|
|
4726
|
+
// FD 3 is also the descriptor-backed executable used to start bubblewrap.
|
|
4727
|
+
// Consume and close it during setup, then obscure this staging copy with the
|
|
4728
|
+
// later /run tmpfs. The target can still observe bubblewrap itself as the
|
|
4729
|
+
// PID-namespace supervisor through procfs; this only contains the inherited
|
|
4730
|
+
// launch descriptor and its temporary setup path.
|
|
4731
|
+
args.push(
|
|
4732
|
+
"--perms",
|
|
4733
|
+
"0000",
|
|
4734
|
+
"--file",
|
|
4735
|
+
String(LINUX_BWRAP_SUPERVISOR_CHILD_FD),
|
|
4736
|
+
LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH,
|
|
4737
|
+
);
|
|
3931
4738
|
for (const mount of pinnedMounts) {
|
|
3932
|
-
|
|
4739
|
+
if (mount.mountMode === "ro-bind-data") {
|
|
4740
|
+
args.push(
|
|
4741
|
+
"--perms",
|
|
4742
|
+
mount.mountPermissions,
|
|
4743
|
+
"--ro-bind-data",
|
|
4744
|
+
String(mount.childFd),
|
|
4745
|
+
mount.destination,
|
|
4746
|
+
);
|
|
4747
|
+
} else {
|
|
4748
|
+
args.push("--ro-bind-fd", String(mount.childFd), mount.destination);
|
|
4749
|
+
}
|
|
3933
4750
|
}
|
|
3934
4751
|
args.push(
|
|
3935
4752
|
"--seccomp",
|
|
@@ -3974,29 +4791,50 @@ function buildLinuxBubblewrapPolicyArgs(
|
|
|
3974
4791
|
|
|
3975
4792
|
function probeLinuxBubblewrapPolicy(
|
|
3976
4793
|
runtime,
|
|
4794
|
+
supervisorLaunch,
|
|
4795
|
+
supervisorBinding,
|
|
3977
4796
|
policyArgs,
|
|
3978
4797
|
pinnedDescriptors,
|
|
3979
4798
|
targetRuntime,
|
|
4799
|
+
contentSnapshot = null,
|
|
3980
4800
|
) {
|
|
4801
|
+
const supervisorIdentity = JSON.stringify({
|
|
4802
|
+
dev: supervisorBinding.fileId.dev,
|
|
4803
|
+
ino: supervisorBinding.fileId.ino,
|
|
4804
|
+
bytes: supervisorBinding.bytes,
|
|
4805
|
+
});
|
|
4806
|
+
const probeSource = [
|
|
4807
|
+
'const fs = require("node:fs");',
|
|
4808
|
+
`const expected = ${supervisorIdentity};`,
|
|
4809
|
+
'for (const name of fs.readdirSync("/proc/self/fd")) {',
|
|
4810
|
+
" let current;",
|
|
4811
|
+
" try { current = fs.fstatSync(Number(name), { bigint: true }); } catch { continue; }",
|
|
4812
|
+
" if (String(current.dev) === expected.dev &&",
|
|
4813
|
+
" String(current.ino) === expected.ino &&",
|
|
4814
|
+
" Number(current.size) === expected.bytes) {",
|
|
4815
|
+
' throw new Error("bubblewrap supervisor descriptor leaked");',
|
|
4816
|
+
" }",
|
|
4817
|
+
"}",
|
|
4818
|
+
"try {",
|
|
4819
|
+
` fs.lstatSync(${JSON.stringify(LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH)});`,
|
|
4820
|
+
' throw new Error("bubblewrap supervisor staging path visible");',
|
|
4821
|
+
"} catch (error) {",
|
|
4822
|
+
' if (error?.code !== "ENOENT") throw error;',
|
|
4823
|
+
"}",
|
|
4824
|
+
`process.stdout.write(${JSON.stringify(LINUX_BWRAP_NODE_PROBE_SENTINEL)});`,
|
|
4825
|
+
].join("\n");
|
|
3981
4826
|
let result;
|
|
3982
4827
|
try {
|
|
3983
4828
|
result = runtime.spawnSync(
|
|
3984
|
-
|
|
3985
|
-
[
|
|
3986
|
-
...policyArgs,
|
|
3987
|
-
"--",
|
|
3988
|
-
"/opt/chainless/runtime/node",
|
|
3989
|
-
"-e",
|
|
3990
|
-
`process.stdout.write(${JSON.stringify(
|
|
3991
|
-
LINUX_BWRAP_NODE_PROBE_SENTINEL,
|
|
3992
|
-
)})`,
|
|
3993
|
-
],
|
|
4829
|
+
supervisorLaunch.command,
|
|
4830
|
+
[...policyArgs, "--", "/opt/chainless/runtime/node", "-e", probeSource],
|
|
3994
4831
|
{
|
|
3995
4832
|
cwd: "/",
|
|
3996
4833
|
shell: false,
|
|
3997
4834
|
encoding: "utf8",
|
|
3998
4835
|
stdio: linuxStdioWithPinnedMounts(null, pinnedDescriptors, {
|
|
3999
4836
|
probe: true,
|
|
4837
|
+
supervisorFd: supervisorLaunch.fd,
|
|
4000
4838
|
}),
|
|
4001
4839
|
timeout: 15_000,
|
|
4002
4840
|
env: {
|
|
@@ -4012,6 +4850,8 @@ function probeLinuxBubblewrapPolicy(
|
|
|
4012
4850
|
false,
|
|
4013
4851
|
"probe_spawn_failed",
|
|
4014
4852
|
targetRuntime,
|
|
4853
|
+
contentSnapshot,
|
|
4854
|
+
supervisorBinding,
|
|
4015
4855
|
);
|
|
4016
4856
|
}
|
|
4017
4857
|
if (result?.error || result?.status !== 0) {
|
|
@@ -4020,6 +4860,8 @@ function probeLinuxBubblewrapPolicy(
|
|
|
4020
4860
|
false,
|
|
4021
4861
|
result?.error ? "probe_spawn_failed" : "probe_failed",
|
|
4022
4862
|
targetRuntime,
|
|
4863
|
+
contentSnapshot,
|
|
4864
|
+
supervisorBinding,
|
|
4023
4865
|
);
|
|
4024
4866
|
}
|
|
4025
4867
|
if (String(result.stdout) !== LINUX_BWRAP_NODE_PROBE_SENTINEL) {
|
|
@@ -4028,9 +4870,18 @@ function probeLinuxBubblewrapPolicy(
|
|
|
4028
4870
|
false,
|
|
4029
4871
|
"node_runtime_probe_failed",
|
|
4030
4872
|
targetRuntime,
|
|
4873
|
+
contentSnapshot,
|
|
4874
|
+
supervisorBinding,
|
|
4031
4875
|
);
|
|
4032
4876
|
}
|
|
4033
|
-
return linuxBubblewrapProbe(
|
|
4877
|
+
return linuxBubblewrapProbe(
|
|
4878
|
+
true,
|
|
4879
|
+
true,
|
|
4880
|
+
null,
|
|
4881
|
+
targetRuntime,
|
|
4882
|
+
contentSnapshot,
|
|
4883
|
+
supervisorBinding,
|
|
4884
|
+
);
|
|
4034
4885
|
}
|
|
4035
4886
|
|
|
4036
4887
|
/**
|
|
@@ -4129,24 +4980,6 @@ export function applyLinuxSandbox(
|
|
|
4129
4980
|
guarantees: [],
|
|
4130
4981
|
});
|
|
4131
4982
|
}
|
|
4132
|
-
const capabilities = attestLinuxBubblewrapCapabilities(runtime);
|
|
4133
|
-
if (!capabilities.ok) {
|
|
4134
|
-
return createSandboxPlan({
|
|
4135
|
-
...base,
|
|
4136
|
-
backend: null,
|
|
4137
|
-
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
4138
|
-
policyAttested: false,
|
|
4139
|
-
runtimeProbe: linuxBubblewrapProbe(
|
|
4140
|
-
true,
|
|
4141
|
-
false,
|
|
4142
|
-
capabilities.reason,
|
|
4143
|
-
validation.entryRuntime,
|
|
4144
|
-
),
|
|
4145
|
-
reason: "linux_bwrap_unavailable",
|
|
4146
|
-
guarantees: [],
|
|
4147
|
-
});
|
|
4148
|
-
}
|
|
4149
|
-
|
|
4150
4983
|
let runtimeMounts;
|
|
4151
4984
|
let pinnedRuntimeMounts = [];
|
|
4152
4985
|
try {
|
|
@@ -4181,6 +5014,14 @@ export function applyLinuxSandbox(
|
|
|
4181
5014
|
let pluginTree;
|
|
4182
5015
|
let entryFormat = null;
|
|
4183
5016
|
let entryMount;
|
|
5017
|
+
const entryRelative = path.posix.relative(
|
|
5018
|
+
validation.contract.pluginRoot,
|
|
5019
|
+
validation.contract.entryIdentity.realPath,
|
|
5020
|
+
);
|
|
5021
|
+
const sandboxEntry = path.posix.join(
|
|
5022
|
+
"/opt/chainless/plugin",
|
|
5023
|
+
entryRelative,
|
|
5024
|
+
);
|
|
4184
5025
|
try {
|
|
4185
5026
|
pluginTree = pinLinuxPluginTree(
|
|
4186
5027
|
runtime,
|
|
@@ -4240,8 +5081,124 @@ export function applyLinuxSandbox(
|
|
|
4240
5081
|
});
|
|
4241
5082
|
}
|
|
4242
5083
|
|
|
5084
|
+
let entrySnapshot = null;
|
|
5085
|
+
let pluginTreeSnapshot = null;
|
|
5086
|
+
const pluginTreeFileSnapshots = [];
|
|
5087
|
+
let probePinnedMounts = pinnedMounts;
|
|
5088
|
+
try {
|
|
5089
|
+
const entryIndex = pinnedMounts.indexOf(entryMount);
|
|
5090
|
+
if (entryIndex < 0) {
|
|
5091
|
+
throw new Error("plugin_entry_pin_missing");
|
|
5092
|
+
}
|
|
5093
|
+
const pluginMountIndexes = pinnedMounts
|
|
5094
|
+
.map((mount, index) =>
|
|
5095
|
+
linuxPathWithin("/opt/chainless/plugin", mount.destination)
|
|
5096
|
+
? index
|
|
5097
|
+
: -1,
|
|
5098
|
+
)
|
|
5099
|
+
.filter((index) => index >= 0);
|
|
5100
|
+
const rawPluginMounts = pluginMountIndexes.map(
|
|
5101
|
+
(index) => pinnedMounts[index],
|
|
5102
|
+
);
|
|
5103
|
+
if (validation.entryRuntime === "node") {
|
|
5104
|
+
if (
|
|
5105
|
+
pluginMountIndexes.length < 1 ||
|
|
5106
|
+
pluginMountIndexes.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES
|
|
5107
|
+
) {
|
|
5108
|
+
throw new Error("node_plugin_tree_snapshot_file_count_invalid");
|
|
5109
|
+
}
|
|
5110
|
+
const pluginBytes = pluginMountIndexes.reduce((total, index) => {
|
|
5111
|
+
const bytes = pinnedMounts[index]?.bytes;
|
|
5112
|
+
if (
|
|
5113
|
+
!Number.isSafeInteger(bytes) ||
|
|
5114
|
+
bytes < 0 ||
|
|
5115
|
+
bytes > LINUX_ATTESTED_FILE_MAX_BYTES
|
|
5116
|
+
) {
|
|
5117
|
+
throw new Error("node_plugin_tree_snapshot_member_size_invalid");
|
|
5118
|
+
}
|
|
5119
|
+
return total + bytes;
|
|
5120
|
+
}, 0);
|
|
5121
|
+
if (
|
|
5122
|
+
!Number.isSafeInteger(pluginBytes) ||
|
|
5123
|
+
pluginBytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
|
|
5124
|
+
) {
|
|
5125
|
+
throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
const rawEntryMount = entryMount;
|
|
5129
|
+
const snapshot = createLinuxEntrySnapshot(
|
|
5130
|
+
runtime,
|
|
5131
|
+
rawEntryMount,
|
|
5132
|
+
validation.contract.entryIdentity,
|
|
5133
|
+
validation.entryRuntime,
|
|
5134
|
+
);
|
|
5135
|
+
const finalMounts = [...pinnedMounts];
|
|
5136
|
+
const probeMounts = [...pinnedMounts];
|
|
5137
|
+
finalMounts[entryIndex] = snapshot.finalMount;
|
|
5138
|
+
probeMounts[entryIndex] = snapshot.probeMount;
|
|
5139
|
+
entrySnapshot = snapshot;
|
|
5140
|
+
if (validation.entryRuntime === "node") {
|
|
5141
|
+
for (const index of pluginMountIndexes) {
|
|
5142
|
+
if (index === entryIndex) continue;
|
|
5143
|
+
const rawMount = pinnedMounts[index];
|
|
5144
|
+
const source = runtime.fs.fstatSync(rawMount.fd);
|
|
5145
|
+
const fileSnapshot = createLinuxRegularFileSnapshot(
|
|
5146
|
+
runtime,
|
|
5147
|
+
rawMount,
|
|
5148
|
+
{
|
|
5149
|
+
fileId: rawMount.fileId,
|
|
5150
|
+
bytes: rawMount.bytes,
|
|
5151
|
+
mtimeMs: rawMount.mtimeMs,
|
|
5152
|
+
},
|
|
5153
|
+
linuxNodePluginTreeFileSnapshotContract(Number(source.mode)),
|
|
5154
|
+
);
|
|
5155
|
+
pluginTreeFileSnapshots.push(fileSnapshot);
|
|
5156
|
+
finalMounts[index] = fileSnapshot.finalMount;
|
|
5157
|
+
probeMounts[index] = fileSnapshot.probeMount;
|
|
5158
|
+
}
|
|
5159
|
+
pluginTreeSnapshot = buildLinuxNodePluginTreeSnapshotAttestation(
|
|
5160
|
+
finalMounts,
|
|
5161
|
+
sandboxEntry,
|
|
5162
|
+
);
|
|
5163
|
+
}
|
|
5164
|
+
pinnedMounts = finalMounts;
|
|
5165
|
+
probePinnedMounts = probeMounts;
|
|
5166
|
+
entryMount = entrySnapshot.finalMount;
|
|
5167
|
+
closeLinuxPinnedMounts(
|
|
5168
|
+
runtime,
|
|
5169
|
+
validation.entryRuntime === "node" ? rawPluginMounts : [rawEntryMount],
|
|
5170
|
+
);
|
|
5171
|
+
} catch (error) {
|
|
5172
|
+
closeLinuxPinnedMounts(runtime, pinnedMounts);
|
|
5173
|
+
closeLinuxPinnedMounts(runtime, [
|
|
5174
|
+
entrySnapshot?.probeMount,
|
|
5175
|
+
entrySnapshot?.finalMount,
|
|
5176
|
+
...pluginTreeFileSnapshots.flatMap((snapshot) => [
|
|
5177
|
+
snapshot.probeMount,
|
|
5178
|
+
snapshot.finalMount,
|
|
5179
|
+
]),
|
|
5180
|
+
]);
|
|
5181
|
+
return createSandboxPlan({
|
|
5182
|
+
...base,
|
|
5183
|
+
backend: null,
|
|
5184
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5185
|
+
policyAttested: false,
|
|
5186
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5187
|
+
false,
|
|
5188
|
+
false,
|
|
5189
|
+
error.message || "entry_snapshot_unattested",
|
|
5190
|
+
validation.entryRuntime,
|
|
5191
|
+
),
|
|
5192
|
+
reason: "linux_bwrap_plugin_snapshot_unattested",
|
|
5193
|
+
guarantees: [],
|
|
5194
|
+
});
|
|
5195
|
+
}
|
|
5196
|
+
|
|
4243
5197
|
let probeSeccompFilter;
|
|
4244
5198
|
let seccompFilter;
|
|
5199
|
+
const pluginTreeProbeMounts = pluginTreeFileSnapshots.map(
|
|
5200
|
+
(snapshot) => snapshot.probeMount,
|
|
5201
|
+
);
|
|
4245
5202
|
try {
|
|
4246
5203
|
probeSeccompFilter = pinLinuxNetworkSeccompFilter(runtime);
|
|
4247
5204
|
seccompFilter = pinLinuxNetworkSeccompFilter(runtime);
|
|
@@ -4255,7 +5212,12 @@ export function applyLinuxSandbox(
|
|
|
4255
5212
|
closeLinuxPinnedMounts(runtime, pinnedMounts);
|
|
4256
5213
|
closeLinuxPinnedMounts(
|
|
4257
5214
|
runtime,
|
|
4258
|
-
[
|
|
5215
|
+
[
|
|
5216
|
+
entrySnapshot?.probeMount,
|
|
5217
|
+
...pluginTreeProbeMounts,
|
|
5218
|
+
probeSeccompFilter,
|
|
5219
|
+
seccompFilter,
|
|
5220
|
+
].filter(Boolean),
|
|
4259
5221
|
);
|
|
4260
5222
|
return createSandboxPlan({
|
|
4261
5223
|
...base,
|
|
@@ -4273,7 +5235,89 @@ export function applyLinuxSandbox(
|
|
|
4273
5235
|
});
|
|
4274
5236
|
}
|
|
4275
5237
|
const pinnedDescriptors = [...pinnedMounts, seccompFilter];
|
|
4276
|
-
const probeDescriptors = [...
|
|
5238
|
+
const probeDescriptors = [...probePinnedMounts, probeSeccompFilter];
|
|
5239
|
+
const closeStrongLinuxResources = (...extra) => {
|
|
5240
|
+
closeLinuxPinnedMounts(runtime, [
|
|
5241
|
+
...pinnedDescriptors,
|
|
5242
|
+
probeSeccompFilter,
|
|
5243
|
+
entrySnapshot?.probeMount,
|
|
5244
|
+
...pluginTreeProbeMounts,
|
|
5245
|
+
...extra,
|
|
5246
|
+
]);
|
|
5247
|
+
};
|
|
5248
|
+
let supervisorPin;
|
|
5249
|
+
let capabilityLaunch;
|
|
5250
|
+
let probeLaunch;
|
|
5251
|
+
let finalLaunch;
|
|
5252
|
+
try {
|
|
5253
|
+
supervisorPin = pinLinuxBubblewrapSupervisor(runtime);
|
|
5254
|
+
capabilityLaunch = openLinuxBubblewrapSupervisorLaunch(
|
|
5255
|
+
runtime,
|
|
5256
|
+
supervisorPin,
|
|
5257
|
+
);
|
|
5258
|
+
} catch (error) {
|
|
5259
|
+
closeStrongLinuxResources(supervisorPin);
|
|
5260
|
+
return createSandboxPlan({
|
|
5261
|
+
...base,
|
|
5262
|
+
backend: null,
|
|
5263
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5264
|
+
policyAttested: false,
|
|
5265
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5266
|
+
false,
|
|
5267
|
+
false,
|
|
5268
|
+
error.message || "supervisor_descriptor_unattested",
|
|
5269
|
+
validation.entryRuntime,
|
|
5270
|
+
),
|
|
5271
|
+
reason: "linux_bwrap_unavailable",
|
|
5272
|
+
guarantees: [],
|
|
5273
|
+
});
|
|
5274
|
+
}
|
|
5275
|
+
const capabilities = attestLinuxBubblewrapCapabilities(
|
|
5276
|
+
runtime,
|
|
5277
|
+
capabilityLaunch,
|
|
5278
|
+
);
|
|
5279
|
+
closeLinuxPinnedMounts(runtime, [capabilityLaunch]);
|
|
5280
|
+
capabilityLaunch = null;
|
|
5281
|
+
if (!capabilities.ok) {
|
|
5282
|
+
closeStrongLinuxResources(supervisorPin);
|
|
5283
|
+
return createSandboxPlan({
|
|
5284
|
+
...base,
|
|
5285
|
+
backend: null,
|
|
5286
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5287
|
+
policyAttested: false,
|
|
5288
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5289
|
+
true,
|
|
5290
|
+
false,
|
|
5291
|
+
capabilities.reason,
|
|
5292
|
+
validation.entryRuntime,
|
|
5293
|
+
null,
|
|
5294
|
+
supervisorPin.attestation,
|
|
5295
|
+
),
|
|
5296
|
+
reason: "linux_bwrap_unavailable",
|
|
5297
|
+
guarantees: [],
|
|
5298
|
+
});
|
|
5299
|
+
}
|
|
5300
|
+
try {
|
|
5301
|
+
probeLaunch = openLinuxBubblewrapSupervisorLaunch(runtime, supervisorPin);
|
|
5302
|
+
} catch (error) {
|
|
5303
|
+
closeStrongLinuxResources(supervisorPin, probeLaunch);
|
|
5304
|
+
return createSandboxPlan({
|
|
5305
|
+
...base,
|
|
5306
|
+
backend: null,
|
|
5307
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5308
|
+
policyAttested: false,
|
|
5309
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5310
|
+
true,
|
|
5311
|
+
false,
|
|
5312
|
+
error.message || "supervisor_launch_reader_unattested",
|
|
5313
|
+
validation.entryRuntime,
|
|
5314
|
+
null,
|
|
5315
|
+
supervisorPin.attestation,
|
|
5316
|
+
),
|
|
5317
|
+
reason: "linux_bwrap_unavailable",
|
|
5318
|
+
guarantees: [],
|
|
5319
|
+
});
|
|
5320
|
+
}
|
|
4277
5321
|
const environment = linuxSandboxEnvironment();
|
|
4278
5322
|
const policyArgs = buildLinuxBubblewrapPolicyArgs(
|
|
4279
5323
|
pluginTree.directories,
|
|
@@ -4281,24 +5325,32 @@ export function applyLinuxSandbox(
|
|
|
4281
5325
|
environment,
|
|
4282
5326
|
seccompFilter,
|
|
4283
5327
|
);
|
|
4284
|
-
const entryRelative = path.posix.relative(
|
|
4285
|
-
validation.contract.pluginRoot,
|
|
4286
|
-
validation.contract.entryIdentity.realPath,
|
|
4287
|
-
);
|
|
4288
|
-
const sandboxEntry = path.posix.join(
|
|
4289
|
-
"/opt/chainless/plugin",
|
|
4290
|
-
entryRelative,
|
|
4291
|
-
);
|
|
4292
5328
|
const targetArgs =
|
|
4293
5329
|
validation.entryRuntime === "native-static-elf"
|
|
4294
5330
|
? [sandboxEntry, ...args]
|
|
4295
5331
|
: ["/opt/chainless/runtime/node", sandboxEntry, ...args.slice(1)];
|
|
5332
|
+
const snapshotPolicyBinding = entrySnapshot
|
|
5333
|
+
? {
|
|
5334
|
+
scope: entrySnapshot.attestation.scope,
|
|
5335
|
+
mechanism: entrySnapshot.attestation.mechanism,
|
|
5336
|
+
sourceIdentity: validation.contract.entryIdentity,
|
|
5337
|
+
sha256: entrySnapshot.attestation.sha256,
|
|
5338
|
+
bytes: entrySnapshot.attestation.bytes,
|
|
5339
|
+
destination: sandboxEntry,
|
|
5340
|
+
...(validation.entryRuntime === "node"
|
|
5341
|
+
? { sourceFileMode: entrySnapshot.attestation.sourceFileMode }
|
|
5342
|
+
: {}),
|
|
5343
|
+
sourceMode: entrySnapshot.attestation.sourceMode,
|
|
5344
|
+
targetMode: entrySnapshot.attestation.targetMode,
|
|
5345
|
+
}
|
|
5346
|
+
: null;
|
|
4296
5347
|
const policyDigest = crypto
|
|
4297
5348
|
.createHash("sha256")
|
|
4298
5349
|
.update(
|
|
4299
5350
|
JSON.stringify({
|
|
4300
|
-
version:
|
|
5351
|
+
version: validation.entryRuntime === "node" ? 4 : 3,
|
|
4301
5352
|
backend: LINUX_BWRAP_BACKEND,
|
|
5353
|
+
supervisor: supervisorPin.attestation,
|
|
4302
5354
|
contract: {
|
|
4303
5355
|
kind: validation.contract.kind,
|
|
4304
5356
|
pluginRoot: validation.contract.pluginRoot,
|
|
@@ -4308,12 +5360,38 @@ export function applyLinuxSandbox(
|
|
|
4308
5360
|
entryRuntime: validation.entryRuntime,
|
|
4309
5361
|
entryFormat,
|
|
4310
5362
|
},
|
|
4311
|
-
mounts: pinnedMounts.map((mount) =>
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
5363
|
+
mounts: pinnedMounts.map((mount) =>
|
|
5364
|
+
mount.contentSnapshot
|
|
5365
|
+
? {
|
|
5366
|
+
destination: mount.destination,
|
|
5367
|
+
fileId: mount.contentSnapshot.sourceFileId,
|
|
5368
|
+
bytes: mount.contentSnapshot.bytes,
|
|
5369
|
+
mtimeMs: mount.contentSnapshot.sourceMtimeMs,
|
|
5370
|
+
contentSnapshot: {
|
|
5371
|
+
scope: mount.contentSnapshot.scope,
|
|
5372
|
+
mechanism: mount.contentSnapshot.mechanism,
|
|
5373
|
+
sha256: mount.contentSnapshot.sha256,
|
|
5374
|
+
bytes: mount.contentSnapshot.bytes,
|
|
5375
|
+
...(validation.entryRuntime === "node"
|
|
5376
|
+
? {
|
|
5377
|
+
sourceFileMode: mount.contentSnapshot.sourceFileMode,
|
|
5378
|
+
}
|
|
5379
|
+
: {}),
|
|
5380
|
+
sourceMode: mount.contentSnapshot.sourceMode,
|
|
5381
|
+
targetMode: mount.contentSnapshot.targetMode,
|
|
5382
|
+
},
|
|
5383
|
+
}
|
|
5384
|
+
: {
|
|
5385
|
+
destination: mount.destination,
|
|
5386
|
+
fileId: mount.fileId,
|
|
5387
|
+
bytes: mount.bytes,
|
|
5388
|
+
mtimeMs: mount.mtimeMs,
|
|
5389
|
+
},
|
|
5390
|
+
),
|
|
5391
|
+
contentSnapshot: snapshotPolicyBinding,
|
|
5392
|
+
...(validation.entryRuntime === "node"
|
|
5393
|
+
? { pluginTreeContentSnapshot: pluginTreeSnapshot }
|
|
5394
|
+
: {}),
|
|
4317
5395
|
seccomp: {
|
|
4318
5396
|
arch: seccompFilter.arch,
|
|
4319
5397
|
policy: seccompFilter.policy,
|
|
@@ -4327,24 +5405,32 @@ export function applyLinuxSandbox(
|
|
|
4327
5405
|
}),
|
|
4328
5406
|
)
|
|
4329
5407
|
.digest("hex");
|
|
4330
|
-
const
|
|
5408
|
+
const policyProbe = Object.freeze(
|
|
4331
5409
|
probeLinuxBubblewrapPolicy(
|
|
4332
5410
|
runtime,
|
|
5411
|
+
probeLaunch,
|
|
5412
|
+
supervisorPin.attestation,
|
|
4333
5413
|
policyArgs,
|
|
4334
5414
|
probeDescriptors,
|
|
4335
5415
|
validation.entryRuntime,
|
|
4336
5416
|
),
|
|
4337
5417
|
);
|
|
4338
|
-
closeLinuxPinnedMounts(runtime, [
|
|
4339
|
-
|
|
4340
|
-
|
|
5418
|
+
closeLinuxPinnedMounts(runtime, [
|
|
5419
|
+
probeLaunch,
|
|
5420
|
+
probeSeccompFilter,
|
|
5421
|
+
entrySnapshot?.probeMount,
|
|
5422
|
+
...pluginTreeProbeMounts,
|
|
5423
|
+
]);
|
|
5424
|
+
probeLaunch = null;
|
|
5425
|
+
if (!policyProbe.runnable) {
|
|
5426
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
4341
5427
|
return createSandboxPlan({
|
|
4342
5428
|
...base,
|
|
4343
5429
|
backend: null,
|
|
4344
5430
|
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
4345
5431
|
policyAttested: false,
|
|
4346
5432
|
policyDigest,
|
|
4347
|
-
runtimeProbe,
|
|
5433
|
+
runtimeProbe: policyProbe,
|
|
4348
5434
|
reason: "linux_bwrap_policy_probe_failed",
|
|
4349
5435
|
guarantees: [],
|
|
4350
5436
|
});
|
|
@@ -4356,9 +5442,10 @@ export function applyLinuxSandbox(
|
|
|
4356
5442
|
validation.contract,
|
|
4357
5443
|
runtime,
|
|
4358
5444
|
sandboxOpts.sync,
|
|
5445
|
+
{ sealedEntry: entrySnapshot !== null },
|
|
4359
5446
|
);
|
|
4360
5447
|
if (!finalValidation.ok) {
|
|
4361
|
-
|
|
5448
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
4362
5449
|
return createSandboxPlan({
|
|
4363
5450
|
...base,
|
|
4364
5451
|
backend: null,
|
|
@@ -4370,46 +5457,107 @@ export function applyLinuxSandbox(
|
|
|
4370
5457
|
false,
|
|
4371
5458
|
`post_probe_${finalValidation.reason}`,
|
|
4372
5459
|
validation.entryRuntime,
|
|
5460
|
+
null,
|
|
5461
|
+
supervisorPin.attestation,
|
|
4373
5462
|
),
|
|
4374
5463
|
reason: "linux_bwrap_execution_contract_changed",
|
|
4375
5464
|
guarantees: [],
|
|
4376
5465
|
});
|
|
4377
5466
|
}
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
5467
|
+
try {
|
|
5468
|
+
if (validation.entryRuntime === "node") {
|
|
5469
|
+
attestLinuxNodePluginTreeSnapshot(
|
|
4381
5470
|
runtime,
|
|
4382
|
-
|
|
4383
|
-
|
|
5471
|
+
pinnedMounts,
|
|
5472
|
+
sandboxEntry,
|
|
5473
|
+
pluginTreeSnapshot,
|
|
5474
|
+
);
|
|
5475
|
+
} else {
|
|
5476
|
+
attestLinuxEntrySnapshot(
|
|
5477
|
+
runtime,
|
|
5478
|
+
entryMount,
|
|
5479
|
+
entrySnapshot.attestation,
|
|
5480
|
+
validation.entryRuntime,
|
|
4384
5481
|
);
|
|
4385
|
-
if (JSON.stringify(finalEntryFormat) !== JSON.stringify(entryFormat)) {
|
|
4386
|
-
throw new Error("native_entry_format_changed");
|
|
4387
|
-
}
|
|
4388
|
-
if (
|
|
4389
|
-
JSON.stringify(finalValidation.entryFormat) !==
|
|
4390
|
-
JSON.stringify(entryFormat)
|
|
4391
|
-
) {
|
|
4392
|
-
throw new Error("native_entry_path_format_changed");
|
|
4393
|
-
}
|
|
4394
|
-
} catch (error) {
|
|
4395
|
-
closeLinuxPinnedMounts(runtime, pinnedDescriptors);
|
|
4396
|
-
return createSandboxPlan({
|
|
4397
|
-
...base,
|
|
4398
|
-
backend: null,
|
|
4399
|
-
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
4400
|
-
policyAttested: false,
|
|
4401
|
-
policyDigest,
|
|
4402
|
-
runtimeProbe: linuxBubblewrapProbe(
|
|
4403
|
-
true,
|
|
4404
|
-
false,
|
|
4405
|
-
`post_probe_${error.message || "native_entry_changed"}`,
|
|
4406
|
-
validation.entryRuntime,
|
|
4407
|
-
),
|
|
4408
|
-
reason: "linux_bwrap_execution_contract_changed",
|
|
4409
|
-
guarantees: [],
|
|
4410
|
-
});
|
|
4411
5482
|
}
|
|
5483
|
+
} catch (error) {
|
|
5484
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
5485
|
+
return createSandboxPlan({
|
|
5486
|
+
...base,
|
|
5487
|
+
backend: null,
|
|
5488
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5489
|
+
policyAttested: false,
|
|
5490
|
+
policyDigest,
|
|
5491
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5492
|
+
true,
|
|
5493
|
+
false,
|
|
5494
|
+
`post_probe_${error.message || "entry_snapshot_changed"}`,
|
|
5495
|
+
validation.entryRuntime,
|
|
5496
|
+
null,
|
|
5497
|
+
supervisorPin.attestation,
|
|
5498
|
+
),
|
|
5499
|
+
reason: "linux_bwrap_execution_contract_changed",
|
|
5500
|
+
guarantees: [],
|
|
5501
|
+
});
|
|
5502
|
+
}
|
|
5503
|
+
try {
|
|
5504
|
+
attestLinuxBubblewrapSupervisorPin(runtime, supervisorPin);
|
|
5505
|
+
} catch (error) {
|
|
5506
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
5507
|
+
return createSandboxPlan({
|
|
5508
|
+
...base,
|
|
5509
|
+
backend: null,
|
|
5510
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5511
|
+
policyAttested: false,
|
|
5512
|
+
policyDigest,
|
|
5513
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5514
|
+
true,
|
|
5515
|
+
false,
|
|
5516
|
+
`post_probe_${error.message || "supervisor_identity_changed"}`,
|
|
5517
|
+
validation.entryRuntime,
|
|
5518
|
+
null,
|
|
5519
|
+
supervisorPin.attestation,
|
|
5520
|
+
),
|
|
5521
|
+
reason: "linux_bwrap_execution_contract_changed",
|
|
5522
|
+
guarantees: [],
|
|
5523
|
+
});
|
|
5524
|
+
}
|
|
5525
|
+
try {
|
|
5526
|
+
finalLaunch = openLinuxBubblewrapSupervisorLaunch(runtime, supervisorPin);
|
|
5527
|
+
} catch (error) {
|
|
5528
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
5529
|
+
return createSandboxPlan({
|
|
5530
|
+
...base,
|
|
5531
|
+
backend: null,
|
|
5532
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5533
|
+
policyAttested: false,
|
|
5534
|
+
policyDigest,
|
|
5535
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5536
|
+
true,
|
|
5537
|
+
false,
|
|
5538
|
+
`post_probe_${error.message || "supervisor_launch_reader_unattested"}`,
|
|
5539
|
+
validation.entryRuntime,
|
|
5540
|
+
null,
|
|
5541
|
+
supervisorPin.attestation,
|
|
5542
|
+
),
|
|
5543
|
+
reason: "linux_bwrap_execution_contract_changed",
|
|
5544
|
+
guarantees: [],
|
|
5545
|
+
});
|
|
4412
5546
|
}
|
|
5547
|
+
const supervisorBinding = supervisorPin.attestation;
|
|
5548
|
+
closeLinuxPinnedMounts(runtime, [supervisorPin]);
|
|
5549
|
+
supervisorPin = null;
|
|
5550
|
+
const runtimeProbe = Object.freeze(
|
|
5551
|
+
linuxBubblewrapProbe(
|
|
5552
|
+
true,
|
|
5553
|
+
true,
|
|
5554
|
+
null,
|
|
5555
|
+
validation.entryRuntime,
|
|
5556
|
+
entrySnapshot?.attestation,
|
|
5557
|
+
supervisorBinding,
|
|
5558
|
+
pluginTreeSnapshot,
|
|
5559
|
+
),
|
|
5560
|
+
);
|
|
4413
5561
|
const options = {
|
|
4414
5562
|
...spawnOpts,
|
|
4415
5563
|
cwd: "/",
|
|
@@ -4421,14 +5569,16 @@ export function applyLinuxSandbox(
|
|
|
4421
5569
|
LC_ALL: "C",
|
|
4422
5570
|
},
|
|
4423
5571
|
stdio: Object.freeze(
|
|
4424
|
-
linuxStdioWithPinnedMounts(spawnOpts?.stdio, pinnedDescriptors
|
|
5572
|
+
linuxStdioWithPinnedMounts(spawnOpts?.stdio, pinnedDescriptors, {
|
|
5573
|
+
supervisorFd: finalLaunch.fd,
|
|
5574
|
+
}),
|
|
4425
5575
|
),
|
|
4426
5576
|
};
|
|
4427
5577
|
let pinsClosed = false;
|
|
4428
5578
|
const cleanup = () => {
|
|
4429
5579
|
if (pinsClosed) return;
|
|
4430
5580
|
pinsClosed = true;
|
|
4431
|
-
closeLinuxPinnedMounts(runtime, pinnedDescriptors);
|
|
5581
|
+
closeLinuxPinnedMounts(runtime, [...pinnedDescriptors, finalLaunch]);
|
|
4432
5582
|
};
|
|
4433
5583
|
return createSandboxPlan({
|
|
4434
5584
|
...base,
|
|
@@ -4441,7 +5591,7 @@ export function applyLinuxSandbox(
|
|
|
4441
5591
|
runtimeProbe,
|
|
4442
5592
|
reason: null,
|
|
4443
5593
|
guarantees: [SANDBOX_BOUNDARIES.FILESYSTEM, SANDBOX_BOUNDARIES.NETWORK],
|
|
4444
|
-
command:
|
|
5594
|
+
command: finalLaunch.command,
|
|
4445
5595
|
args: [...policyArgs, "--", ...targetArgs],
|
|
4446
5596
|
options,
|
|
4447
5597
|
cleanup,
|