chainlesschain 0.166.77 → 0.166.79
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/README.md +9 -8
- package/completions/_cc +1 -1
- package/completions/cc.bash +1 -1
- package/completions/cc.fish +1 -1
- package/completions/cc.ps1 +1 -1
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +10 -0
- package/src/assets/linux-subreaper/linux-arm64/supervisor +0 -0
- package/src/assets/linux-subreaper/linux-x64/manifest.json +10 -0
- package/src/assets/linux-subreaper/linux-x64/supervisor +0 -0
- package/src/assets/web-panel/assets/{AIOps-CpiCBhRh.js → AIOps-CwD6Pm_Y.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CnU-Xvx-.js → ActionButton-Du5Rp-O6.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BJW-CNtf.js → Analytics-DY9l72ma.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-UFQJL3G9.js → AppLayout-7t1WIaCJ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dr_IUDoY.js → Artifacts-BLIA5yny.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-aglWo6rr.js → Audit-BC_N0QTg.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dm_TCiXC.js → BackgroundAgents-CA7SxDtg.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DUdX4-gA.js → Backup-BZ4AzS1_.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BTcLjy1o.js → BaseInput-BhiggGaH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Bhy9qZrl.js → Chat-CDdBoIBI.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-hsBsrl31.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CblrPa21.js → Checkbox-asoMKSEK.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-SEAHzOlr.js → Codegen-B0gElLdm.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DJz8iFOR.js → Col-DrNzLpF6.js} +1 -1
- package/src/assets/web-panel/assets/{Community-RufKhcQt.js → Community-DCzB5qkI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dlifx08U.js → Compact-Db-Eua_j.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DdhQWcHh.js → Compliance-B76NByjl.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BLNn6oOm.js → Cowork-C-nK6Qpk.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Dv-JWY2d.js → Cron-C-GlQO19.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-k7Pz7O-b.js → Crosschain-Cuuz9IIh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C_10syUW.js → DID-gwFaT973.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CMyH6Szx.js → Dashboard-DHDt5jEf.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DjmD2Saa.js → Dropdown-CJ9kog-Y.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-36_IRUSo.js → EmailListRenderer-DOSXFRiQ.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-CmtU0XmM.js → EvolutionSettings-CHYMP7rR.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DEgf9kyP.js → FamilyGuardDashboard-C1gzG5EK.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DGO1wQls.js → Federation-CEdNVWUw.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Doj1FmKW.js → FormItemContext-CwVOWPX8.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-GzOU27iT.js → GenericCardRenderer-CgNUs3kT.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Dd7D1EDK.js → Git-DM4MNnQo.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-6IY8LcjI.js → Governance-DdOtjkvd.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BB9gl8q1.js → Inference-28PidFfr.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CeCed5wd.js → KnowledgeGraph-Bpj_qNUK.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CYOeHuaI.js → Logs-C6Ix19Ay.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-Bk8Zqc2R.js → MarkdownRenderer-Bj6mtPbf.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D-_xgHgM.js → Marketplace-DqkhRSMP.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-NnaUTF5t.js → McpTools-1WQ2SjaE.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CndN9pMt.js → Memory-BWTi7v6R.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BXblFmhY.js → MobileBridge-DB4wu3UE.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CC26Dm2k.js → MobileProjects-Bh9pWx2f.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BhaqWCiW.js → Mtc-BVF1Wrno.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-D6fktM2w.js → MtcAudit-DtNfslPf.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CFvahz7s.js → Multisig-4qcXP0bf.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Co73Ivya.js → NLProgramming-BRJl5Ehw.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B4g33L1o.js → Notes-BRY_bgMk.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-lXd17EbN.js → NotificationSettings-DirXMFz4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BiOB0GIi.js → OrderTableRenderer-BlrY8lwu.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-JQ7_wQIA.js → Organization-CVzQ1vO-.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-gwgJgozQ.js → Overflow-A3rXTPG4.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D9L_0gie.js → P2P-Db9ABWz0.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CbOPsFE9.js → PdhVaultBrowser-CPv0H2OZ.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Cz42HNQf.js → Permissions-COnW4sIR.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DjorYtrF.js → PersonalDataHub-C1hG7rp7.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-DyWPhmm5.js → Pipeline---GLRDEe.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DyfLMl2j.js → Privacy-aaN9R3Lu.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyqDx1OE.js → ProjectInit-C-kw49hE.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DsBw0Csy.js → ProjectSettings-y5tTkBTC.js} +2 -2
- package/src/assets/web-panel/assets/Projects-DZHlhyPB.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CCI_klN-.js → Providers-LqxnV72o.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DNsnlDdS.js → QrScannerModal-D_q-Y5F0.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-B9r2iaee.js → QuickAsk-_E_KB5o8.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BJDe03GR.js → Recommend-BN8S5WJA.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-J7JR3rRo.js → RemoteSession-BFzrY_aW.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DR39AJeV.js → Reputation-C1HCLaTV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dh77m3NZ.js → Row-B8T2-Qv7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BUdg28nJ.js → RssFeed-CNQLk8v1.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BNC7sGhj.js → Search-BOx720Kc.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DISENGI7.js → Security-Ds8z6PF6.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BBBGpJ1-.js → Services-C0-YkRSF.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DMcIoIv2.js → Skeleton-DUHlSySD.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-QAyFbI3r.js → Skills-C8CBbbh0.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CSg5wLH7.js → Sla-lQltyYfB.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DMPxTc5x.js → SpeechSettings-B5vyOma-.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CPG7l30y.js → SyncSettings-BKDPu_y-.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Y1kOVvVL.js → Tasks-Bgvf7LTx.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-jt9NagL0.js → Templates-BMZyYYS7.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DauuJICx.js → Tenant-7PYTig8f.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-E9rnKeGq.js → Terminal-C2W0LMdR.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Cc7z2APG.js → TimelineRenderer-CqREew_l.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CV0UthRA.js → Tokens-BhTyxTuD.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BFcJJGfu.js → Trigger-DxNSSZy_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BAydbNJA.js → Trust-Dy_wfbIh.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DcOS6sqL.js → UkeySign-DJC5ANqx.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BLHJZvif.js → VideoEditing-CNFevrO4.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-4QFLKHwW.js → Wallet-CXzuS1YK.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BocEOOSj.js → WebAuthn-BWS6HP8c.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DCfF7D0B.js → WorkflowEditor-swiN_7Vj.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CxvfsYGr.js → chat-NwH7qlBs.js} +1 -1
- package/src/assets/web-panel/assets/{colors-KtQB9DmQ.js → colors-h6nRhBTw.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Dx4J6arN.js → compact-item-BQ6B6rP2.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Cq0olgzo.js → createContext-CQkJuRcG.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Bz6vqBGV.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Bf9-h3D-.js → hasIn-Diwl1WyI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpO6DKuu.js → index-0ZztPzIm.js} +1 -1
- package/src/assets/web-panel/assets/index-1b7DEw-w.js +1 -0
- package/src/assets/web-panel/assets/{index-DQDR-_ai.js → index-AzeSQJDK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcC4cbj.js → index-B3QgcUBV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO6XvO-C.js → index-B5gj_uCC.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClUfSChI.js → index-BI3ALCgN.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3xfiItB.js → index-BRl3KUj0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFdEDvFn.js → index-BYYb8oeV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqnggDos.js → index-BcKDUGYf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_8UcDan.js → index-BcjlIMP9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmQuG0fr.js → index-BheCvudN.js} +1 -1
- package/src/assets/web-panel/assets/{index-iE9tqEN5.js → index-BjcARUtr.js} +1 -1
- package/src/assets/web-panel/assets/{index-nmyDOh_b.js → index-BnQUin2K.js} +1 -1
- package/src/assets/web-panel/assets/{index-Br1mDMfO.js → index-Bs6HyCfP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZWjYfWt.js → index-BvPz6vZ8.js} +1 -1
- package/src/assets/web-panel/assets/{index-0fi8DIVQ.js → index-C3Ye1ISy.js} +1 -1
- package/src/assets/web-panel/assets/{index-hSzyoXri.js → index-C7-T-u0L.js} +3 -3
- package/src/assets/web-panel/assets/{index-DfrFNMvz.js → index-CRWpixu2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9yaUHm9.js → index-CatBQDSQ.js} +1 -1
- package/src/assets/web-panel/assets/index-CfD4tMuW.js +1 -0
- package/src/assets/web-panel/assets/{index-DMR-keCL.js → index-CqH-oOR3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cy865Hhq.js → index-CwsoVDi-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DI5bRqNG.js → index-CyBtEVLM.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt5N6VM_.js → index-D5P498Rk.js} +1 -1
- package/src/assets/web-panel/assets/{index-6dDDSCHD.js → index-DKaB4xTf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKtQyb2o.js → index-DQYZa1Ci.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaIclw-E.js → index-DZ9CNcbJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-_FIz-0tp.js → index-DZAsByy4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBRlZdZj.js → index-D_HY0nyN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsCdl2Od.js → index-Da8frO_A.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cfsn7N18.js → index-DjB8IC9D.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeYP_kT9.js → index-Dwpthe2b.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8WuPI-h.js → index-Fm95dO4u.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK2U_AHi.js → index-GTefQSbY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnU-dUWd.js → index-OEWSrqit.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDAe1UmZ.js → index-TYMmUsxZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2BfSIrn.js → index-iHNOr_wL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz7f4Jaq.js → index-mxxZtsHp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHmdMKYl.js → index-pYwYBVeQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DvzKJ6o5.js → initDefaultProps-BeUrpC_H.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D1yTvmxW.js → motion-CHW5EXt1.js} +1 -1
- package/src/assets/web-panel/assets/{move-DxtSIF8K.js → move-CWaoWJvF.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BDsQgIHY.js → mtc-parser-CEC6Us1u.js} +1 -1
- package/src/assets/web-panel/assets/{omit-UVBJW8kh.js → omit-1D-kP_Qj.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D4QqtTyX.js → pickAttrs-CsS0myLl.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-QXmu6vBf.js → placementArrow-bMThOvkI.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-D-PmXO9M.js → responsiveObserve-B72knbW8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CKbGxEZJ.js → slide-C3bI_2yy.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BbgXPkXm.js → statusUtils-0-FmcCAS.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DOr9rk68.js → styleChecker-DXUyqoWY.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B2wO2r36.js → useFlexGapSupport-BSSnL3f1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-SeVGBK34.js → useFs-B0Cvvl0S.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DUJBCxjD.js → usePersonalDataHub-6D-d4WU5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CF4EYsig.js → vnode-zjIrlxT_.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-pRUs5pqI.js → zoom-Hw7NEisu.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/command-manifest.json +1 -1
- package/src/commands/checkpoint.js +42 -7
- package/src/commands/orchestrate.js +85 -25
- package/src/commands/session-location.js +5 -0
- package/src/commands/session-show.js +109 -1
- package/src/commands/session.js +26 -2
- package/src/commands/tokens.js +9 -4
- package/src/data/changelog.json +26 -0
- package/src/generated/cc-agent-protocol.schema.json +71 -0
- package/src/harness/jsonl-session-store.js +357 -114
- package/src/harness/mcp-client.js +44 -1
- package/src/lib/agent-router.js +35 -7
- package/src/lib/agent-sandbox.js +302 -2
- package/src/lib/app-server/server.js +14 -0
- package/src/lib/background-agent-supervisor.js +14 -4
- package/src/lib/causal-observability.js +64 -1
- package/src/lib/checkpoint-summary-projection.js +98 -0
- package/src/lib/checkpoint-timeline-authority.js +15 -51
- package/src/lib/claude-code-bridge.js +302 -60
- package/src/lib/codex-app-server-adapter.js +161 -35
- package/src/lib/cost-budget.js +4 -0
- package/src/lib/doctor-checkup.js +31 -2
- package/src/lib/durable-security-store.js +6 -1
- package/src/lib/evolution/evolution-artifact-identity.js +21 -0
- package/src/lib/evolution/evolution-artifact-ports.js +5 -11
- package/src/lib/evolution/evolution-eval-cohort-receipt-reconciliation.js +389 -0
- package/src/lib/execution-location-failure-site.js +63 -0
- package/src/lib/execution-location-target.js +102 -8
- package/src/lib/external-agent-adapters.js +2 -0
- package/src/lib/image-file-boundary.js +154 -0
- package/src/lib/image-input.js +16 -3
- package/src/lib/llm-pricing.js +114 -8
- package/src/lib/lsp/code-intelligence.js +7 -1
- package/src/lib/mcp-call-ledger-store.js +7 -2
- package/src/lib/model-capabilities.js +21 -10
- package/src/lib/model-context-catalog.js +60 -1
- package/src/lib/openai-responses.js +7 -1
- package/src/lib/orchestrator.js +8 -2
- package/src/lib/process-execution-broker/index.js +93 -1
- package/src/lib/process-execution-broker/linux-subreaper-artifact.js +120 -0
- package/src/lib/process-execution-broker/linux-subreaper-helper.js +344 -0
- package/src/lib/process-execution-broker/linux-subreaper-process.js +326 -0
- package/src/lib/process-execution-broker/linux-subreaper-supervisor.c +360 -0
- package/src/lib/process-execution-broker/owned-posix-process-group-worker.mjs +131 -0
- package/src/lib/process-execution-broker/owned-posix-process-group.js +267 -0
- package/src/lib/process-execution-broker/process-ownership-journal.js +297 -0
- package/src/lib/process-execution-broker/process-ownership-quarantine.js +91 -0
- package/src/lib/runtime-usage-ledger.js +3 -0
- package/src/lib/sandbox-docker-egress.js +507 -0
- package/src/lib/sandbox-egress-proxy.js +167 -10
- package/src/lib/sandbox-egress-worker-thread.js +38 -0
- package/src/lib/sandbox-egress-worker.js +196 -0
- package/src/lib/secure-fs.js +158 -42
- package/src/lib/session-branch-history.js +240 -0
- package/src/lib/session-history-origins.js +285 -0
- package/src/lib/session-input-receipt.js +110 -0
- package/src/lib/session-resource-budget.js +8 -8
- package/src/lib/session-transcript-history.js +456 -0
- package/src/lib/session-transcript-page.js +164 -0
- package/src/lib/session-transcript-reference.js +38 -0
- package/src/lib/session-usage.js +8 -0
- package/src/lib/token-tracker.js +32 -54
- package/src/lib/usage-pricing-context.js +93 -0
- package/src/runtime/agent-core.js +141 -21
- package/src/runtime/headless-stream.js +202 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D0WrJQaQ.js +0 -1
- package/src/assets/web-panel/assets/Projects-CRaSI25Z.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CmgDNkoO.js +0 -1
- package/src/assets/web-panel/assets/index-BpIZ2S01.js +0 -1
- package/src/assets/web-panel/assets/index-C-VmKuyv.js +0 -1
|
@@ -46,6 +46,14 @@ import {
|
|
|
46
46
|
SANDBOX_BOUNDARIES,
|
|
47
47
|
} from "./platform-sandbox.js";
|
|
48
48
|
import { normalizeLinuxCgroupPolicy } from "./linux-cgroup-v2.js";
|
|
49
|
+
import { acquireLinuxSubreaperHelper } from "./linux-subreaper-helper.js";
|
|
50
|
+
import { spawnLinuxSubreaperChild } from "./linux-subreaper-process.js";
|
|
51
|
+
import {
|
|
52
|
+
assertProcessOwnershipAvailable,
|
|
53
|
+
getProcessOwnershipStatus,
|
|
54
|
+
observeProcessOwnership,
|
|
55
|
+
prepareProcessOwnership,
|
|
56
|
+
} from "./process-ownership-quarantine.js";
|
|
49
57
|
import {
|
|
50
58
|
MACOS_MCP_LAUNCHER_INPUTS,
|
|
51
59
|
isMacosMcpLauncherPackageVersion,
|
|
@@ -1187,6 +1195,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1187
1195
|
_sanitizeOptions(options) {
|
|
1188
1196
|
if (!options) return {};
|
|
1189
1197
|
const safe = { ...options };
|
|
1198
|
+
delete safe.linuxSubreaper;
|
|
1190
1199
|
if (process.platform === "win32" && safe.shell === true) {
|
|
1191
1200
|
safe.windowsHide = true;
|
|
1192
1201
|
}
|
|
@@ -4479,6 +4488,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4479
4488
|
* and durable evidence APIs.
|
|
4480
4489
|
*/
|
|
4481
4490
|
beginWorkspaceTransaction(options = {}) {
|
|
4491
|
+
assertProcessOwnershipAvailable();
|
|
4482
4492
|
return this._workspaceTransactionManager(
|
|
4483
4493
|
options.stateDir,
|
|
4484
4494
|
options.lockDir,
|
|
@@ -4486,6 +4496,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4486
4496
|
}
|
|
4487
4497
|
|
|
4488
4498
|
recoverWorkspaceTransactions(options = {}) {
|
|
4499
|
+
assertProcessOwnershipAvailable();
|
|
4489
4500
|
return this._workspaceTransactionManager(
|
|
4490
4501
|
options.stateDir,
|
|
4491
4502
|
options.lockDir,
|
|
@@ -4499,6 +4510,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4499
4510
|
).inspect(id);
|
|
4500
4511
|
}
|
|
4501
4512
|
|
|
4513
|
+
getProcessOwnershipStatus() {
|
|
4514
|
+
return getProcessOwnershipStatus();
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4502
4517
|
listWorkspaceTransactions(options = {}) {
|
|
4503
4518
|
return this._workspaceTransactionManager(
|
|
4504
4519
|
options.stateDir,
|
|
@@ -4507,6 +4522,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4507
4522
|
}
|
|
4508
4523
|
|
|
4509
4524
|
restoreWorkspaceTransaction(id, options = {}) {
|
|
4525
|
+
assertProcessOwnershipAvailable();
|
|
4510
4526
|
return this._workspaceTransactionManager(
|
|
4511
4527
|
options.stateDir,
|
|
4512
4528
|
options.lockDir,
|
|
@@ -4514,6 +4530,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4514
4530
|
}
|
|
4515
4531
|
|
|
4516
4532
|
undoWorkspaceTransactionRestore(id, options = {}) {
|
|
4533
|
+
assertProcessOwnershipAvailable();
|
|
4517
4534
|
return this._workspaceTransactionManager(
|
|
4518
4535
|
options.stateDir,
|
|
4519
4536
|
options.lockDir,
|
|
@@ -4569,6 +4586,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4569
4586
|
}
|
|
4570
4587
|
|
|
4571
4588
|
spawn(command, args, options = {}) {
|
|
4589
|
+
assertProcessOwnershipAvailable();
|
|
4572
4590
|
options = this._withAmbientProcessContext(command, options);
|
|
4573
4591
|
const executionId = crypto.randomUUID();
|
|
4574
4592
|
const startTime = Date.now();
|
|
@@ -4800,7 +4818,71 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4800
4818
|
);
|
|
4801
4819
|
auditEntry.mcpStdioExecutableIdentityDigest = admitted.identityDigest;
|
|
4802
4820
|
}
|
|
4803
|
-
|
|
4821
|
+
if (
|
|
4822
|
+
process.platform === "linux" &&
|
|
4823
|
+
options.linuxSubreaper &&
|
|
4824
|
+
!sandboxPlan.guarantees.includes(SANDBOX_BOUNDARIES.PROCESS_TREE)
|
|
4825
|
+
) {
|
|
4826
|
+
const request = options.linuxSubreaper;
|
|
4827
|
+
const stdio = optsForSpawn.stdio;
|
|
4828
|
+
if (
|
|
4829
|
+
!request ||
|
|
4830
|
+
typeof request !== "object" ||
|
|
4831
|
+
Array.isArray(request) ||
|
|
4832
|
+
Object.keys(request).some((key) => key !== "graceMs") ||
|
|
4833
|
+
!Number.isSafeInteger(request.graceMs) ||
|
|
4834
|
+
request.graceMs < 0 ||
|
|
4835
|
+
request.graceMs > 2147483647 ||
|
|
4836
|
+
sandboxPlan.postSpawn.required ||
|
|
4837
|
+
options.shell ||
|
|
4838
|
+
optsForSpawn.shell ||
|
|
4839
|
+
optsForSpawn.detached ||
|
|
4840
|
+
(stdio !== undefined && stdio !== "pipe") ||
|
|
4841
|
+
["uid", "gid", "signal", "timeout", "input", "argv0"].some(
|
|
4842
|
+
(key) => optsForSpawn[key] !== undefined,
|
|
4843
|
+
) ||
|
|
4844
|
+
(sandboxPlan.applied && sandboxPlan.backend !== "linux-prlimit")
|
|
4845
|
+
) {
|
|
4846
|
+
const error = new Error(
|
|
4847
|
+
"Linux external-agent supervision cannot preserve this launch plan",
|
|
4848
|
+
);
|
|
4849
|
+
error.code = "EXTERNAL_AGENT_SUPERVISION_UNSUPPORTED_PLAN";
|
|
4850
|
+
throw error;
|
|
4851
|
+
}
|
|
4852
|
+
const ownershipLease = prepareProcessOwnership(executionId);
|
|
4853
|
+
let nativeEntered = false;
|
|
4854
|
+
try {
|
|
4855
|
+
const helper = acquireLinuxSubreaperHelper({
|
|
4856
|
+
spawnSync: this._native?.spawnSync || nativeSpawnSync,
|
|
4857
|
+
});
|
|
4858
|
+
proc = spawnLinuxSubreaperChild(
|
|
4859
|
+
command,
|
|
4860
|
+
args,
|
|
4861
|
+
{
|
|
4862
|
+
cwd: path.resolve(optsForSpawn.cwd || cwd),
|
|
4863
|
+
env: optsForSpawn.env || process.env,
|
|
4864
|
+
graceMs: request.graceMs,
|
|
4865
|
+
helper,
|
|
4866
|
+
},
|
|
4867
|
+
{
|
|
4868
|
+
spawn: (...nativeArgs) => {
|
|
4869
|
+
nativeEntered = true;
|
|
4870
|
+
return nativeSpawnFn(...nativeArgs);
|
|
4871
|
+
},
|
|
4872
|
+
},
|
|
4873
|
+
);
|
|
4874
|
+
} catch (error) {
|
|
4875
|
+
// Before native entry no process could exist. Once entered, retain
|
|
4876
|
+
// the durable record even if a synchronous exception hides its handle.
|
|
4877
|
+
if (!nativeEntered) ownershipLease?.settle();
|
|
4878
|
+
else ownershipLease?.retain();
|
|
4879
|
+
throw error;
|
|
4880
|
+
}
|
|
4881
|
+
auditEntry.processLifecycleOwner = "linux-subreaper";
|
|
4882
|
+
observeProcessOwnership(proc, executionId, ownershipLease);
|
|
4883
|
+
} else {
|
|
4884
|
+
proc = nativeSpawnFn(command, args, optsForSpawn);
|
|
4885
|
+
}
|
|
4804
4886
|
if (builtInMacMcpPlan) {
|
|
4805
4887
|
const callerLifeline = proc?.stdio?.[8];
|
|
4806
4888
|
if (
|
|
@@ -4912,6 +4994,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4912
4994
|
|
|
4913
4995
|
proc.on("exit", (code, signal) => {
|
|
4914
4996
|
const endTime = Date.now();
|
|
4997
|
+
if (proc.ownedProcessTreeEvidence) {
|
|
4998
|
+
auditEntry.processLifecycleReceipt = proc.ownedProcessTreeEvidence;
|
|
4999
|
+
auditEntry.sandboxTargetPid = proc.sandboxTargetPid;
|
|
5000
|
+
}
|
|
4915
5001
|
auditEntry.exitCode = code;
|
|
4916
5002
|
auditEntry.signal = signal;
|
|
4917
5003
|
auditEntry.endTime = endTime;
|
|
@@ -4929,6 +5015,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4929
5015
|
this.emit("exit", auditEntry);
|
|
4930
5016
|
});
|
|
4931
5017
|
proc.on("error", (err) => {
|
|
5018
|
+
if (proc.ownedProcessTreeEvidence) {
|
|
5019
|
+
auditEntry.processLifecycleReceipt = proc.ownedProcessTreeEvidence;
|
|
5020
|
+
auditEntry.sandboxTargetPid = proc.sandboxTargetPid;
|
|
5021
|
+
}
|
|
4932
5022
|
auditEntry.error = err.message;
|
|
4933
5023
|
auditEntry.endTime = Date.now();
|
|
4934
5024
|
auditEntry.durationMs = auditEntry.endTime - startTime;
|
|
@@ -4957,6 +5047,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4957
5047
|
}
|
|
4958
5048
|
|
|
4959
5049
|
spawnSync(command, args, options = {}) {
|
|
5050
|
+
assertProcessOwnershipAvailable();
|
|
4960
5051
|
options = this._withAmbientProcessContext(command, options);
|
|
4961
5052
|
const executionId = crypto.randomUUID();
|
|
4962
5053
|
const startTime = Date.now();
|
|
@@ -5203,6 +5294,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
5203
5294
|
* together with the descriptor-pinned generic bwrap plan.
|
|
5204
5295
|
*/
|
|
5205
5296
|
spawnPty(ptyModule, command, args = [], options = {}) {
|
|
5297
|
+
assertProcessOwnershipAvailable();
|
|
5206
5298
|
if (!ptyModule || typeof ptyModule.spawn !== "function") {
|
|
5207
5299
|
throw new TypeError("pty_module_spawn_unavailable");
|
|
5208
5300
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const LINUX_SUBREAPER_SOURCE_DIGEST =
|
|
4
|
+
"sha256:e920e24b4a79121484f2e8e97886755f88eaa1af93b55326dfa68c7a7f571424";
|
|
5
|
+
export const LINUX_SUBREAPER_ARCHITECTURES = Object.freeze(["x64", "arm64"]);
|
|
6
|
+
export const MAX_SUBREAPER_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
export const MAX_SUBREAPER_MANIFEST_BYTES = 4096;
|
|
8
|
+
|
|
9
|
+
export function subreaperDigest(bytes) {
|
|
10
|
+
return "sha256:" + crypto.createHash("sha256").update(bytes).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function invalid(reason) {
|
|
14
|
+
const error = new Error(
|
|
15
|
+
`Linux process supervision helper unavailable: ${reason}`,
|
|
16
|
+
);
|
|
17
|
+
error.code = "EXTERNAL_AGENT_HELPER_UNAVAILABLE";
|
|
18
|
+
return error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Bounded ELF64 inspection; release images have no dynamic loader dependency. */
|
|
22
|
+
export function validateLinuxSubreaperElf(bytes, { arch, staticOnly = false }) {
|
|
23
|
+
const machine = arch === "x64" ? 62 : arch === "arm64" ? 183 : null;
|
|
24
|
+
if (
|
|
25
|
+
!machine ||
|
|
26
|
+
!Buffer.isBuffer(bytes) ||
|
|
27
|
+
bytes.length < 64 ||
|
|
28
|
+
bytes.length > MAX_SUBREAPER_IMAGE_BYTES ||
|
|
29
|
+
!bytes.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])) ||
|
|
30
|
+
bytes[4] !== 2 ||
|
|
31
|
+
bytes[5] !== 1 ||
|
|
32
|
+
bytes[6] !== 1 ||
|
|
33
|
+
bytes.readUInt16LE(18) !== machine ||
|
|
34
|
+
![2, 3].includes(bytes.readUInt16LE(16)) ||
|
|
35
|
+
bytes.readUInt32LE(20) !== 1 ||
|
|
36
|
+
bytes.readUInt16LE(52) !== 64 ||
|
|
37
|
+
bytes.readUInt16LE(54) !== 56
|
|
38
|
+
)
|
|
39
|
+
throw invalid("unexpected-helper-image");
|
|
40
|
+
const offset = bytes.readBigUInt64LE(32);
|
|
41
|
+
const count = bytes.readUInt16LE(56);
|
|
42
|
+
if (
|
|
43
|
+
offset < 64n ||
|
|
44
|
+
count < 1 ||
|
|
45
|
+
count > 128 ||
|
|
46
|
+
offset + BigInt(count * 56) > BigInt(bytes.length)
|
|
47
|
+
)
|
|
48
|
+
throw invalid("invalid-helper-program-headers");
|
|
49
|
+
const entry = bytes.readBigUInt64LE(24);
|
|
50
|
+
let executableEntry = false;
|
|
51
|
+
for (let index = 0; index < count; index++) {
|
|
52
|
+
const at = Number(offset) + index * 56;
|
|
53
|
+
const type = bytes.readUInt32LE(at);
|
|
54
|
+
if (staticOnly && [2, 3].includes(type))
|
|
55
|
+
throw invalid("helper-is-not-static");
|
|
56
|
+
if (type !== 1) continue;
|
|
57
|
+
const fileOffset = bytes.readBigUInt64LE(at + 8);
|
|
58
|
+
const address = bytes.readBigUInt64LE(at + 16);
|
|
59
|
+
const fileSize = bytes.readBigUInt64LE(at + 32);
|
|
60
|
+
const memorySize = bytes.readBigUInt64LE(at + 40);
|
|
61
|
+
if (fileSize > memorySize || fileOffset + fileSize > BigInt(bytes.length))
|
|
62
|
+
throw invalid("invalid-helper-load-segment");
|
|
63
|
+
if (
|
|
64
|
+
bytes.readUInt32LE(at + 4) & 1 &&
|
|
65
|
+
entry >= address &&
|
|
66
|
+
entry < address + fileSize
|
|
67
|
+
)
|
|
68
|
+
executableEntry = true;
|
|
69
|
+
}
|
|
70
|
+
if (!executableEntry || (staticOnly && bytes.readUInt16LE(16) !== 2))
|
|
71
|
+
throw invalid("invalid-helper-entry");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function validateLinuxSubreaperArtifact(
|
|
75
|
+
manifestBytes,
|
|
76
|
+
imageBytes,
|
|
77
|
+
{ arch, commit } = {},
|
|
78
|
+
) {
|
|
79
|
+
if (
|
|
80
|
+
!Buffer.isBuffer(manifestBytes) ||
|
|
81
|
+
manifestBytes.length > MAX_SUBREAPER_MANIFEST_BYTES
|
|
82
|
+
)
|
|
83
|
+
throw invalid("invalid-packaged-manifest");
|
|
84
|
+
let manifest;
|
|
85
|
+
try {
|
|
86
|
+
manifest = JSON.parse(manifestBytes.toString("utf8"));
|
|
87
|
+
} catch {
|
|
88
|
+
throw invalid("invalid-packaged-manifest");
|
|
89
|
+
}
|
|
90
|
+
const keys = [
|
|
91
|
+
"schema",
|
|
92
|
+
"platform",
|
|
93
|
+
"arch",
|
|
94
|
+
"sourceDigest",
|
|
95
|
+
"imageDigest",
|
|
96
|
+
"bytes",
|
|
97
|
+
"linkage",
|
|
98
|
+
"commit",
|
|
99
|
+
];
|
|
100
|
+
if (
|
|
101
|
+
!manifest ||
|
|
102
|
+
typeof manifest !== "object" ||
|
|
103
|
+
Array.isArray(manifest) ||
|
|
104
|
+
Object.keys(manifest).length !== keys.length ||
|
|
105
|
+
Object.keys(manifest).some((key) => !keys.includes(key)) ||
|
|
106
|
+
manifest.schema !== "chainlesschain.linux-subreaper-artifact/v1" ||
|
|
107
|
+
manifest.platform !== "linux" ||
|
|
108
|
+
!LINUX_SUBREAPER_ARCHITECTURES.includes(arch) ||
|
|
109
|
+
manifest.arch !== arch ||
|
|
110
|
+
manifest.sourceDigest !== LINUX_SUBREAPER_SOURCE_DIGEST ||
|
|
111
|
+
manifest.linkage !== "static" ||
|
|
112
|
+
!/^[a-f0-9]{40}$/.test(manifest.commit) ||
|
|
113
|
+
(commit !== undefined && manifest.commit !== commit) ||
|
|
114
|
+
manifest.bytes !== imageBytes.length ||
|
|
115
|
+
manifest.imageDigest !== subreaperDigest(imageBytes)
|
|
116
|
+
)
|
|
117
|
+
throw invalid("packaged-identity-mismatch");
|
|
118
|
+
validateLinuxSubreaperElf(imageBytes, { arch, staticOnly: true });
|
|
119
|
+
return Object.freeze({ ...manifest });
|
|
120
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import {
|
|
7
|
+
LINUX_SUBREAPER_SOURCE_DIGEST,
|
|
8
|
+
MAX_SUBREAPER_IMAGE_BYTES,
|
|
9
|
+
MAX_SUBREAPER_MANIFEST_BYTES,
|
|
10
|
+
validateLinuxSubreaperArtifact,
|
|
11
|
+
validateLinuxSubreaperElf,
|
|
12
|
+
} from "./linux-subreaper-artifact.js";
|
|
13
|
+
export { LINUX_SUBREAPER_SOURCE_DIGEST } from "./linux-subreaper-artifact.js";
|
|
14
|
+
|
|
15
|
+
const MAX_SOURCE_BYTES = 128 * 1024;
|
|
16
|
+
const MAX_IMAGE_BYTES = MAX_SUBREAPER_IMAGE_BYTES;
|
|
17
|
+
const images = new Map();
|
|
18
|
+
const leases = new WeakMap();
|
|
19
|
+
const sha = (bytes) =>
|
|
20
|
+
"sha256:" + crypto.createHash("sha256").update(bytes).digest("hex");
|
|
21
|
+
|
|
22
|
+
function failure(reason) {
|
|
23
|
+
const error = new Error(
|
|
24
|
+
`Linux process supervision helper unavailable: ${reason}`,
|
|
25
|
+
);
|
|
26
|
+
error.code = "EXTERNAL_AGENT_HELPER_UNAVAILABLE";
|
|
27
|
+
return error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readDescriptor(fd, maximum) {
|
|
31
|
+
const before = fs.fstatSync(fd, { bigint: true });
|
|
32
|
+
if (!before.isFile() || before.size < 1n || before.size > BigInt(maximum))
|
|
33
|
+
throw failure("invalid-file");
|
|
34
|
+
const bytes = Buffer.alloc(Number(before.size));
|
|
35
|
+
let offset = 0;
|
|
36
|
+
while (offset < bytes.length) {
|
|
37
|
+
const count = fs.readSync(fd, bytes, offset, bytes.length - offset, offset);
|
|
38
|
+
if (count <= 0) throw failure("short-file-read");
|
|
39
|
+
offset += count;
|
|
40
|
+
}
|
|
41
|
+
const after = fs.fstatSync(fd, { bigint: true });
|
|
42
|
+
if (
|
|
43
|
+
before.dev !== after.dev ||
|
|
44
|
+
before.ino !== after.ino ||
|
|
45
|
+
before.size !== after.size ||
|
|
46
|
+
before.mtimeNs !== after.mtimeNs ||
|
|
47
|
+
before.ctimeNs !== after.ctimeNs
|
|
48
|
+
)
|
|
49
|
+
throw failure("file-changed");
|
|
50
|
+
return { bytes, stat: after };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function compilerPath() {
|
|
54
|
+
// Only the system toolchain is eligible. Do not consult PATH, CC, loader,
|
|
55
|
+
// include-path, or plugin-provided compiler settings.
|
|
56
|
+
const compiler = fs.realpathSync("/usr/bin/cc");
|
|
57
|
+
let current = compiler;
|
|
58
|
+
for (;;) {
|
|
59
|
+
const info = fs.lstatSync(current);
|
|
60
|
+
if (info.isSymbolicLink() || info.uid !== 0 || (info.mode & 0o022) !== 0)
|
|
61
|
+
throw failure("untrusted-system-compiler");
|
|
62
|
+
if (current === compiler && (!info.isFile() || (info.mode & 0o111) === 0))
|
|
63
|
+
throw failure("invalid-system-compiler");
|
|
64
|
+
if (current === "/") break;
|
|
65
|
+
current = path.dirname(current);
|
|
66
|
+
}
|
|
67
|
+
return compiler;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function packagedImage() {
|
|
71
|
+
const root = fileURLToPath(
|
|
72
|
+
new URL("../../assets/linux-subreaper/", import.meta.url),
|
|
73
|
+
);
|
|
74
|
+
// Only source checkouts with no packaged directory may use the development
|
|
75
|
+
// compiler path. A partial/corrupt installed payload must never fall back.
|
|
76
|
+
try {
|
|
77
|
+
if (!fs.lstatSync(root).isDirectory())
|
|
78
|
+
throw failure("invalid-packaged-directory");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.code === "ENOENT") return null;
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
let rootFd = null;
|
|
84
|
+
let dirFd = null;
|
|
85
|
+
const read = (name, maximum) => {
|
|
86
|
+
const fd = fs.openSync(
|
|
87
|
+
`/proc/self/fd/${dirFd}/${name}`,
|
|
88
|
+
fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW,
|
|
89
|
+
);
|
|
90
|
+
try {
|
|
91
|
+
const snapshot = readDescriptor(fd, maximum);
|
|
92
|
+
if (snapshot.stat.nlink !== 1n) throw failure("invalid-packaged-file");
|
|
93
|
+
return snapshot.bytes;
|
|
94
|
+
} finally {
|
|
95
|
+
fs.closeSync(fd);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
rootFd = fs.openSync(
|
|
100
|
+
root,
|
|
101
|
+
fs.constants.O_RDONLY |
|
|
102
|
+
fs.constants.O_DIRECTORY |
|
|
103
|
+
fs.constants.O_NOFOLLOW,
|
|
104
|
+
);
|
|
105
|
+
dirFd = fs.openSync(
|
|
106
|
+
`/proc/self/fd/${rootFd}/linux-${process.arch}`,
|
|
107
|
+
fs.constants.O_RDONLY |
|
|
108
|
+
fs.constants.O_DIRECTORY |
|
|
109
|
+
fs.constants.O_NOFOLLOW,
|
|
110
|
+
);
|
|
111
|
+
const manifest = read("manifest.json", MAX_SUBREAPER_MANIFEST_BYTES);
|
|
112
|
+
const bytes = read("supervisor", MAX_IMAGE_BYTES);
|
|
113
|
+
const identity = validateLinuxSubreaperArtifact(manifest, bytes, {
|
|
114
|
+
arch: process.arch,
|
|
115
|
+
});
|
|
116
|
+
return { bytes, identity };
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error.code === "EXTERNAL_AGENT_HELPER_UNAVAILABLE") throw error;
|
|
119
|
+
throw failure("packaged-helper-unavailable");
|
|
120
|
+
} finally {
|
|
121
|
+
if (dirFd !== null) fs.closeSync(dirFd);
|
|
122
|
+
if (rootFd !== null) fs.closeSync(rootFd);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function compileImage(spawnSync) {
|
|
127
|
+
let sourceFd = null;
|
|
128
|
+
let directoryFd = null;
|
|
129
|
+
let imageFd = null;
|
|
130
|
+
let temporaryRoot = null;
|
|
131
|
+
let imagePath = null;
|
|
132
|
+
try {
|
|
133
|
+
// Vite may import the Broker through an http: module URL on non-Linux
|
|
134
|
+
// hosts. Resolve this Linux-only asset only when native supervision runs.
|
|
135
|
+
const source = fileURLToPath(
|
|
136
|
+
new URL("./linux-subreaper-supervisor.c", import.meta.url),
|
|
137
|
+
);
|
|
138
|
+
sourceFd = fs.openSync(
|
|
139
|
+
source,
|
|
140
|
+
fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW,
|
|
141
|
+
);
|
|
142
|
+
const snapshot = readDescriptor(sourceFd, MAX_SOURCE_BYTES);
|
|
143
|
+
// npm/git may transport text with CRLF. Compile the exact canonical bytes
|
|
144
|
+
// that the source digest commits to, passed on stdin rather than reopened.
|
|
145
|
+
const sourceBytes = Buffer.from(
|
|
146
|
+
snapshot.bytes.toString("utf8").replace(/\r\n/g, "\n"),
|
|
147
|
+
);
|
|
148
|
+
if (
|
|
149
|
+
snapshot.stat.nlink !== 1n ||
|
|
150
|
+
sha(sourceBytes) !== LINUX_SUBREAPER_SOURCE_DIGEST
|
|
151
|
+
)
|
|
152
|
+
throw failure("source-digest-mismatch");
|
|
153
|
+
fs.closeSync(sourceFd);
|
|
154
|
+
sourceFd = null;
|
|
155
|
+
const packaged = packagedImage();
|
|
156
|
+
const compiler = packaged ? null : compilerPath();
|
|
157
|
+
temporaryRoot = fs.mkdtempSync(
|
|
158
|
+
path.join(os.tmpdir(), "cc-linux-subreaper-build-"),
|
|
159
|
+
);
|
|
160
|
+
fs.chmodSync(temporaryRoot, 0o700);
|
|
161
|
+
directoryFd = fs.openSync(
|
|
162
|
+
temporaryRoot,
|
|
163
|
+
fs.constants.O_RDONLY |
|
|
164
|
+
fs.constants.O_DIRECTORY |
|
|
165
|
+
fs.constants.O_NOFOLLOW,
|
|
166
|
+
);
|
|
167
|
+
const directory = fs.fstatSync(directoryFd);
|
|
168
|
+
if (
|
|
169
|
+
!directory.isDirectory() ||
|
|
170
|
+
directory.uid !== process.getuid() ||
|
|
171
|
+
(directory.mode & 0o777) !== 0o700
|
|
172
|
+
)
|
|
173
|
+
throw failure("untrusted-build-directory");
|
|
174
|
+
imagePath = path.join(temporaryRoot, "supervisor");
|
|
175
|
+
// The compiler writes through its inherited directory handle. An ancestor
|
|
176
|
+
// rename cannot redirect the build to a caller-supplied executable path.
|
|
177
|
+
if (packaged) {
|
|
178
|
+
fs.writeFileSync(
|
|
179
|
+
`/proc/self/fd/${directoryFd}/supervisor`,
|
|
180
|
+
packaged.bytes,
|
|
181
|
+
{ flag: "wx", mode: 0o500 },
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
const built = spawnSync(
|
|
185
|
+
compiler,
|
|
186
|
+
[
|
|
187
|
+
"-std=c11",
|
|
188
|
+
"-Wall",
|
|
189
|
+
"-Wextra",
|
|
190
|
+
"-Werror",
|
|
191
|
+
"-O2",
|
|
192
|
+
"-fstack-protector-strong",
|
|
193
|
+
"-D_FORTIFY_SOURCE=2",
|
|
194
|
+
"-x",
|
|
195
|
+
"c",
|
|
196
|
+
"-",
|
|
197
|
+
"-o",
|
|
198
|
+
"/proc/self/fd/3/supervisor",
|
|
199
|
+
],
|
|
200
|
+
{
|
|
201
|
+
cwd: temporaryRoot,
|
|
202
|
+
env: { PATH: "/usr/bin:/bin", LC_ALL: "C" },
|
|
203
|
+
shell: false,
|
|
204
|
+
input: sourceBytes,
|
|
205
|
+
timeout: 30000,
|
|
206
|
+
maxBuffer: 1024 * 1024,
|
|
207
|
+
stdio: ["pipe", "pipe", "pipe", directoryFd],
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
if (built.error || built.status !== 0 || built.signal)
|
|
211
|
+
throw failure("native-build-failed");
|
|
212
|
+
}
|
|
213
|
+
imageFd = fs.openSync(
|
|
214
|
+
`/proc/self/fd/${directoryFd}/supervisor`,
|
|
215
|
+
fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW,
|
|
216
|
+
);
|
|
217
|
+
fs.fchmodSync(imageFd, 0o500);
|
|
218
|
+
const image = readDescriptor(imageFd, MAX_IMAGE_BYTES);
|
|
219
|
+
if (
|
|
220
|
+
image.stat.uid !== BigInt(process.getuid()) ||
|
|
221
|
+
image.stat.nlink !== 1n ||
|
|
222
|
+
(image.stat.mode & 0o777n) !== 0o500n
|
|
223
|
+
)
|
|
224
|
+
throw failure("untrusted-helper-image");
|
|
225
|
+
validateLinuxSubreaperElf(image.bytes, {
|
|
226
|
+
arch: process.arch,
|
|
227
|
+
staticOnly: Boolean(packaged),
|
|
228
|
+
});
|
|
229
|
+
if (packaged && sha(image.bytes) !== packaged.identity.imageDigest)
|
|
230
|
+
throw failure("packaged-copy-changed");
|
|
231
|
+
// Remove the name before granting a launch lease. The executable can only
|
|
232
|
+
// be reached through the held descriptor; a later pathname swap is inert.
|
|
233
|
+
fs.unlinkSync(`/proc/self/fd/${directoryFd}/supervisor`);
|
|
234
|
+
imagePath = null;
|
|
235
|
+
const held = fs.fstatSync(imageFd, { bigint: true });
|
|
236
|
+
if (
|
|
237
|
+
held.nlink !== 0n ||
|
|
238
|
+
held.dev !== image.stat.dev ||
|
|
239
|
+
held.ino !== image.stat.ino
|
|
240
|
+
)
|
|
241
|
+
throw failure("helper-unlink-unconfirmed");
|
|
242
|
+
const entry = {
|
|
243
|
+
fd: imageFd,
|
|
244
|
+
dev: held.dev,
|
|
245
|
+
ino: held.ino,
|
|
246
|
+
digest: sha(image.bytes),
|
|
247
|
+
distribution: packaged ? "packaged-static" : "local-build",
|
|
248
|
+
};
|
|
249
|
+
imageFd = null;
|
|
250
|
+
return entry;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error.code === "EXTERNAL_AGENT_HELPER_UNAVAILABLE") throw error;
|
|
253
|
+
// Do not expose compiler stdout/stderr, paths, environment, or native argv.
|
|
254
|
+
throw failure(
|
|
255
|
+
error.code === "ENOENT"
|
|
256
|
+
? "system-compiler-or-source-missing"
|
|
257
|
+
: "native-build-setup-failed",
|
|
258
|
+
);
|
|
259
|
+
} finally {
|
|
260
|
+
if (sourceFd !== null) fs.closeSync(sourceFd);
|
|
261
|
+
if (imageFd !== null) fs.closeSync(imageFd);
|
|
262
|
+
if (imagePath && directoryFd !== null) {
|
|
263
|
+
try {
|
|
264
|
+
fs.unlinkSync(`/proc/self/fd/${directoryFd}/supervisor`);
|
|
265
|
+
} catch {
|
|
266
|
+
/* Preserve the admission failure. */
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (directoryFd !== null) fs.closeSync(directoryFd);
|
|
270
|
+
if (temporaryRoot) {
|
|
271
|
+
try {
|
|
272
|
+
fs.rmdirSync(temporaryRoot);
|
|
273
|
+
} catch {
|
|
274
|
+
/* Never recursively remove an unexpected build entry. */
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Acquire a one-use descriptor lease from trusted packaged source. */
|
|
281
|
+
export function acquireLinuxSubreaperHelper({ spawnSync }) {
|
|
282
|
+
if (process.platform !== "linux" || typeof spawnSync !== "function")
|
|
283
|
+
throw failure("unsupported-host");
|
|
284
|
+
let entry = images.get(spawnSync);
|
|
285
|
+
if (!entry) {
|
|
286
|
+
entry = compileImage(spawnSync);
|
|
287
|
+
images.set(spawnSync, entry);
|
|
288
|
+
// Outstanding leases hold their own duplicated descriptor. Bound cached
|
|
289
|
+
// native-seam variants without invalidating already-started children.
|
|
290
|
+
if (images.size > 4) {
|
|
291
|
+
const oldest = images.keys().next().value;
|
|
292
|
+
fs.closeSync(images.get(oldest).fd);
|
|
293
|
+
images.delete(oldest);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const held = readDescriptor(entry.fd, MAX_IMAGE_BYTES);
|
|
297
|
+
if (
|
|
298
|
+
held.stat.dev !== entry.dev ||
|
|
299
|
+
held.stat.ino !== entry.ino ||
|
|
300
|
+
held.stat.nlink !== 0n ||
|
|
301
|
+
sha(held.bytes) !== entry.digest
|
|
302
|
+
)
|
|
303
|
+
throw failure("cached-helper-changed");
|
|
304
|
+
const descriptor = fs.openSync(
|
|
305
|
+
`/proc/self/fd/${entry.fd}`,
|
|
306
|
+
fs.constants.O_RDONLY,
|
|
307
|
+
);
|
|
308
|
+
try {
|
|
309
|
+
const borrowed = fs.fstatSync(descriptor, { bigint: true });
|
|
310
|
+
if (
|
|
311
|
+
borrowed.dev !== entry.dev ||
|
|
312
|
+
borrowed.ino !== entry.ino ||
|
|
313
|
+
borrowed.nlink !== 0n
|
|
314
|
+
)
|
|
315
|
+
throw failure("helper-lease-changed");
|
|
316
|
+
} catch (error) {
|
|
317
|
+
fs.closeSync(descriptor);
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
const lease = Object.freeze({ kind: "linux-subreaper-helper-lease/v1" });
|
|
321
|
+
leases.set(lease, {
|
|
322
|
+
descriptor,
|
|
323
|
+
sourceDigest: LINUX_SUBREAPER_SOURCE_DIGEST,
|
|
324
|
+
imageDigest: entry.digest,
|
|
325
|
+
distribution: entry.distribution,
|
|
326
|
+
});
|
|
327
|
+
return lease;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function consumeLinuxSubreaperHelper(lease) {
|
|
331
|
+
const entry = leases.get(lease);
|
|
332
|
+
if (!entry) throw failure("invalid-or-consumed-helper-lease");
|
|
333
|
+
leases.delete(lease);
|
|
334
|
+
let released = false;
|
|
335
|
+
return Object.freeze({
|
|
336
|
+
...entry,
|
|
337
|
+
release() {
|
|
338
|
+
if (!released) {
|
|
339
|
+
released = true;
|
|
340
|
+
fs.closeSync(entry.descriptor);
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
}
|