chainlesschain 0.162.183 → 0.162.185
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/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BapS7lz4.js → AIOps-Co4ipAKx.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CXYdXEUI.js → ActionButton-BpsoDrDq.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CKR5Lexn.js → Analytics-DMUqeBik.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DATVUOXB.js → AppLayout-C6fD_dXr.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-vb_0ZlV1.js → Artifacts-DW0YlQlV.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BI1l9GWt.js → Audit-DXtHiiFK.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BFXbaUEf.js → BackgroundAgents-BgmF62U-.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DEYBJJ6H.js → Backup-D8vhVLQC.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-COdzeO0M.js → BaseInput-D7nCtY2X.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-oSN6sfM5.js → Chat-CBZLEV2G.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CI_jT0ru.js → ChatBubbleRenderer-CtDb-2Qu.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BezEa4tm.js → Checkbox-DJLkeD4M.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CNEL0Ife.js → Codegen-Cs6xO8Fd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Bf2Y2Bn1.js → Col-Dtx9wxfT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BfUlu4Hc.js → Community-DiigJg4n.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN4xWP9e.js → Compact-B67oMDDL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Bt6BKmEP.js → Compliance-BBDl-Jos.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DKLp8YxO.js → Cowork-Ba98hSsa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-o7Hhw6ky.js → Cron-DBWQZIz7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DaNXWJIw.js → Crosschain-OcrKzxAt.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B-Qa2mha.js → DID-mW2eSbjM.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CMX0Qnxw.js → Dashboard-BmsDPmKJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLuLyRu_.js → Dropdown-CVzmCyRx.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CFBAeMij.js → EmailListRenderer-vVgqKzhh.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DWzXQ67V.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DB1QSLgr.js → Federation-ZGomhjxr.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C-OKCToP.js → FormItemContext-DyaknbLr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DMJFtFZf.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DsGSujOV.js → Git-DQOJIYkG.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B0iS9NEW.js → Governance-CMiY-EEp.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CH004H7c.js → Inference-D6IXuucf.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Q9MTQ83c.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B_fyjZK9.js → Logs-N0jD-349.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BL1IVXm-.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BIStF9d1.js → Marketplace-WhYuhBxm.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-pGfal768.js → McpTools-DTeO28xy.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-xbNqpIqB.js → Memory-MSHzPSE-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-B5EQGrIK.js → MobileBridge-Cyqs3J3W.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-7IN2hWHg.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-a_rmfxJH.js → Mtc-MWBnezNM.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BmKuofUr.js → MtcAudit-DQu35Nu-.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BQbJ-CAN.js → Multisig-CQS_M-DP.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-JtPJKX25.js → NLProgramming-WD16vQvY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-ZFKznTSG.js → Notes-DqsiSwOq.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-B6aHRBBR.js → NotificationSettings-D3LoxO-P.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DdcsOqEz.js → OrderTableRenderer-C5jPoTd6.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BFDTAUgS.js → Organization-48aMBJso.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-0FoTfbWH.js → Overflow-j-a2XkhR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CWd90etw.js → P2P-BYpBNYnh.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DtCGC-W-.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CvQ7nr7T.js → Permissions-ZRXGXDFb.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-rdNPcR-Z.js → PersonalDataHub-6OWUPHyL.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-CmrfiNOs.js → Pipeline-FcjlDK9E.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D7ep1h_t.js → Privacy-vD_6Tldp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C0xKINHS.js → ProjectInit-B_CHOLXp.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-AOmZFp-F.js → ProjectSettings-rEiqQ2da.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BWHSkXZP.js → Projects-Bg8obRAM.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cb7JCTPL.js → Providers-Dgtt97B4.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BDMehcXC.js → QrScannerModal-DaGVylZO.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BF6_1nvx.js → QuickAsk-DP2_2x3u.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BHfT-A28.js → Recommend-D29yj4ki.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-Ue2wnBLp.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CsWHLEcj.js → Reputation-CrxIHqeC.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D6srnWhm.js → Row-BgSzJPCn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Xhr-KyVd.js → RssFeed-AwhYOrP9.js} +3 -3
- package/src/assets/web-panel/assets/{Search-vIbIJvwL.js → Search-D2shpEcm.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Bzi5Aiko.js → Security-BtKzhTbT.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BHtVX52P.js → Services-BY0DsUSm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C0R8B2jf.js → Skeleton-B3Sb0UI9.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DtT6rWOX.js → Skills-Ne5w0bDs.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-0B37mxVU.js → Sla-C8VkaP4T.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-0V2jE1Bs.js → SpeechSettings-BQdIEYP9.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYedRzaj.js → SyncSettings-CwFafrUg.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-yJuKoDlR.js +1 -0
- package/src/assets/web-panel/assets/{Templates-BKoHztGT.js → Templates-DYn9qCmx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant--17nwVNZ.js → Tenant-DkKReHvn.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-r6bd-owt.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-C5jx4AE3.js → TimelineRenderer-Mq2juUC3.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DYJKYa9t.js → Tokens-BnMw7MRi.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DChpWiQ9.js → Trigger-CTzKp4o9.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfWG52EA.js → Trust-BBrSZm9U.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CMugYcyA.js → UkeySign-D9fDvEMq.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BOSmrHAd.js → VideoEditing-DUi5ve-0.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D5g3k3Sa.js → Wallet-C8Fi1P8_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-D-7u4Mkc.js → WebAuthn-s1LppYi2.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CFzZMSUJ.js → WorkflowEditor-DJL2C4lj.js} +1 -1
- package/src/assets/web-panel/assets/{chat-VbR6l6mc.js → chat-D3F2ndOB.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BSZn8EDS.js → colors-B7739-e3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-y74obMEq.js → compact-item-CcJbFTF4.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C326bjex.js → createContext-CuP_DEFS.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Cj_36GHq.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D_INJQT_.js → hasIn-6YEXfIik.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuIkfnTF.js → index-4h3lpWJ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBkzrpiL.js → index-BIID4XcP.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7SrC-On.js → index-BOHUo3aM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnSfnSMP.js → index-BYYSLEjn.js} +1 -1
- package/src/assets/web-panel/assets/{index-KF9Zmqln.js → index-Bh4hcNWW.js} +1 -1
- package/src/assets/web-panel/assets/{index-C327pLbw.js → index-BhIKnQAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-E_v1ZCTP.js → index-Bru_kpZl.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bfw2hf2s.js → index-BsdSnHQE.js} +1 -1
- package/src/assets/web-panel/assets/{index-jYaogFE8.js → index-BxSqVMGh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dlxh9D8D.js → index-C3ZA_h1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdyjrCAu.js → index-C3pctdCb.js} +1 -1
- package/src/assets/web-panel/assets/{index-abmn_8Yz.js → index-CRuoRapN.js} +1 -1
- package/src/assets/web-panel/assets/{index-qMA4GYdQ.js → index-CY0zd_jc.js} +1 -1
- package/src/assets/web-panel/assets/index-CezFrAmD.js +1 -0
- package/src/assets/web-panel/assets/index-Cxgguez6.js +1 -0
- package/src/assets/web-panel/assets/{index-Cr3uuz0C.js → index-CxradkS6.js} +19 -19
- package/src/assets/web-panel/assets/{index-DwHSZLXg.js → index-D0K_rFC1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMHqs3Jb.js → index-D0zdPyTD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bnua8IX3.js → index-D3haorUt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BY8-nCgs.js → index-D6Ij_2uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-FQBsuExX.js → index-DDRlBaDt.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-ZCaUJc.js → index-DGsE7MQz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjcWKg1S.js → index-DHvxXXNA.js} +1 -1
- package/src/assets/web-panel/assets/{index-GA80ir_u.js → index-DV11jpeO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsovTpx9.js → index-DXdnfDVK.js} +1 -1
- package/src/assets/web-panel/assets/{index-nSn2-C4N.js → index-D_75jYWk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJi3UqBt.js → index-DjI9MkId.js} +1 -1
- package/src/assets/web-panel/assets/{index-gINyI1rh.js → index-DlSvpNMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfLJ42kv.js → index-Dox44j29.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2NyllnW.js → index-DpEKmjoa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Iw0FfqUz.js → index-Dsy7_x4s.js} +1 -1
- package/src/assets/web-panel/assets/{index-4eyH-eC2.js → index-Gxw09e9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-4PIeTM--.js → index-LfRlSmqe.js} +1 -1
- package/src/assets/web-panel/assets/{index-IZAWt0H8.js → index-Nz-MUHD1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUU2606o.js → index-b9cuvFEv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzE53HiB.js → index-eR6H6NR7.js} +1 -1
- package/src/assets/web-panel/assets/{index-9PIVuXqo.js → index-fWgUQZ9d.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkH1JvEt.js → index-mfWQRSLn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNMxIsDg.js → index-yOZYSNmG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DI0Zaf8-.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BZDKSy3Z.js → motion-rYJN_vzf.js} +1 -1
- package/src/assets/web-panel/assets/{move-DsIW01Vp.js → move-DnDK2gTP.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CohkDhsf.js → mtc-parser-CKnXy7ES.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DpiN5rzg.js → omit-3nRc3hWT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qbuqni3y.js → pickAttrs-Ddjj3sLu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-mBky6714.js → placementArrow-CbsbphQY.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Bas3eQ2K.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CBx_SKoK.js → slide-D4pvdFj0.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BlYguJSV.js → statusUtils-C2W-vY-S.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BCYQmRGv.js → styleChecker-Cbq5150d.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Dp5dybLM.js → useFlexGapSupport-BA2B7R82.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DHAMBgA2.js → useFs-CLrwg3xg.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-W6jsSw76.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BkI6aayc.js → vnode-8BDGy-YU.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C_IMGueq.js → zoom-B2SzFprl.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +74 -1
- package/src/commands/background-session.js +11 -1
- package/src/commands/batch.js +160 -5
- package/src/commands/plugin.js +90 -27
- package/src/commands/session.js +31 -1
- package/src/commands/team.js +212 -6
- package/src/data/changelog.json +26 -0
- package/src/gateways/terminal/PtyManager.js +45 -15
- package/src/gateways/ws/ws-agent-handler.js +15 -1
- package/src/gateways/ws/ws-session-gateway.js +88 -0
- package/src/harness/background-task-command-runner.js +234 -0
- package/src/harness/background-task-manager.js +150 -20
- package/src/harness/background-task-worker.js +17 -11
- package/src/harness/jsonl-session-store.js +57 -6
- package/src/harness/mcp-client.js +65 -43
- package/src/lib/agent-batch.js +4 -1
- package/src/lib/agent-team/team-worktree.js +3 -0
- package/src/lib/async-hook-supervisor.cjs +82 -10
- package/src/lib/async-hook-supervisor.js +50 -4
- package/src/lib/background-agent-supervisor.js +46 -0
- package/src/lib/collaboration-run-store.js +362 -0
- package/src/lib/event-runtime-host.js +12 -10
- package/src/lib/hook-runner.cjs +105 -18
- package/src/lib/hook-runner.js +79 -6
- package/src/lib/hook-shell-command.cjs +148 -0
- package/src/lib/hooks-v2-runtime.js +280 -81
- package/src/lib/hooks-v2-workspace-context.js +258 -0
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +15 -0
- package/src/lib/lsp/lsp-client.js +12 -1
- package/src/lib/plugin-monitor-supervisor.js +53 -16
- package/src/lib/plugin-runtime/bin.js +21 -5
- package/src/lib/plugin-runtime/install.js +249 -57
- package/src/lib/plugin-runtime/mcp.js +21 -2
- package/src/lib/plugin-runtime/monitors.js +19 -3
- package/src/lib/plugin-runtime/sandbox-policy.js +100 -4
- package/src/lib/process-execution-broker/index.js +1151 -44
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1557 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +1378 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +586 -58
- package/src/lib/session-usage.js +202 -2
- package/src/lib/stream-retry.js +29 -0
- package/src/repl/agent-repl.js +40 -3
- package/src/runtime/agent-core.js +304 -42
- package/src/runtime/headless-runner.js +48 -1
- package/src/runtime/headless-stream.js +42 -2
- package/src/workers/background-agent-worker.js +5 -11
- package/src/assets/web-panel/assets/MarkdownRenderer-C0o4FtpD.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +0 -5
- package/src/assets/web-panel/assets/Tasks-D-R1x8vm.js +0 -1
- package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +0 -3
- package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +0 -1
- package/src/assets/web-panel/assets/index-5iAFwYx1.js +0 -1
- package/src/assets/web-panel/assets/index-BfnB7LPx.js +0 -1
|
@@ -27,6 +27,7 @@ import crypto from "node:crypto";
|
|
|
27
27
|
import os from "node:os";
|
|
28
28
|
import path from "node:path";
|
|
29
29
|
import * as fs from "node:fs";
|
|
30
|
+
import * as tty from "node:tty";
|
|
30
31
|
import { createRequire } from "node:module";
|
|
31
32
|
|
|
32
33
|
const require = createRequire(import.meta.url);
|
|
@@ -38,7 +39,13 @@ import {
|
|
|
38
39
|
postSpawnSandbox as _postSpawnSandbox,
|
|
39
40
|
SANDBOX_BOUNDARIES,
|
|
40
41
|
} from "./platform-sandbox.js";
|
|
42
|
+
import {
|
|
43
|
+
admitLinuxGenericSandboxExecutionContract,
|
|
44
|
+
issueLinuxGenericSandboxExecutionContract,
|
|
45
|
+
LINUX_GENERIC_CONTRACT_KIND,
|
|
46
|
+
} from "./linux-generic-bwrap.js";
|
|
41
47
|
import { consumeIssuedPluginSandboxExecutionContract } from "../plugin-runtime/bin.js";
|
|
48
|
+
import runtimeProvenanceLedger from "../runtime-provenance-ledger.js";
|
|
42
49
|
import { credentialAgent } from "./credential-agent.js";
|
|
43
50
|
|
|
44
51
|
const SUPPORTED_SANDBOX_BOUNDARIES = new Set(Object.values(SANDBOX_BOUNDARIES));
|
|
@@ -50,8 +57,6 @@ const SUPPORTED_SANDBOX_PROFILES = new Set([
|
|
|
50
57
|
|
|
51
58
|
// 延迟导入避免循环依赖
|
|
52
59
|
let _traceCtx = null;
|
|
53
|
-
let _rpl = null;
|
|
54
|
-
let _hooksV2 = null;
|
|
55
60
|
const _ipcBus = null;
|
|
56
61
|
|
|
57
62
|
function getTraceCtx() {
|
|
@@ -66,25 +71,431 @@ function getTraceCtx() {
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
function getRpl() {
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
_rpl = require("../runtime-provenance-ledger.js");
|
|
72
|
-
} catch {
|
|
73
|
-
_rpl = null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return _rpl;
|
|
74
|
+
return runtimeProvenanceLedger;
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
function
|
|
80
|
-
|
|
77
|
+
function createNativePtyAdapter() {
|
|
78
|
+
const MAX_PENDING_WRITE_BYTES = 1024 * 1024;
|
|
79
|
+
const terminalStates = new WeakMap();
|
|
80
|
+
const blockingSlaveOwners = new Map();
|
|
81
|
+
const closeStream = (stream) => {
|
|
81
82
|
try {
|
|
82
|
-
|
|
83
|
+
stream?.destroy?.();
|
|
83
84
|
} catch {
|
|
84
|
-
|
|
85
|
+
// PTY cleanup is best-effort after the owning process has terminated.
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const terminalState = (terminal) => {
|
|
89
|
+
const state = terminalStates.get(terminal);
|
|
90
|
+
if (!state) {
|
|
91
|
+
throw new Error("pty_terminal_state_unavailable");
|
|
92
|
+
}
|
|
93
|
+
return state;
|
|
94
|
+
};
|
|
95
|
+
const invalidateMasterState = (state) => {
|
|
96
|
+
state.disposed = true;
|
|
97
|
+
state.fd = null;
|
|
98
|
+
state.writeQueue.length = 0;
|
|
99
|
+
state.pendingWriteBytes = 0;
|
|
100
|
+
if (state.writeImmediate) {
|
|
101
|
+
clearImmediate(state.writeImmediate);
|
|
102
|
+
state.writeImmediate = null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const hasLiveMaster = (state) =>
|
|
106
|
+
!state.disposed &&
|
|
107
|
+
Number.isInteger(state.fd) &&
|
|
108
|
+
state.master?.destroyed !== true &&
|
|
109
|
+
state.master?.closed !== true;
|
|
110
|
+
const processWriteQueue = (state) => {
|
|
111
|
+
if (!hasLiveMaster(state)) {
|
|
112
|
+
invalidateMasterState(state);
|
|
113
|
+
return;
|
|
85
114
|
}
|
|
86
|
-
|
|
87
|
-
|
|
115
|
+
if (state.writeQueue.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const task = state.writeQueue[0];
|
|
119
|
+
let written = 0;
|
|
120
|
+
try {
|
|
121
|
+
// The node-pty master is non-blocking. Keep the syscall synchronous so
|
|
122
|
+
// releaseTerminal() can never close and recycle this numeric fd while a
|
|
123
|
+
// libuv fs.write request still refers to it. Bound each turn for event
|
|
124
|
+
// loop fairness; EAGAIN is retried from the next turn.
|
|
125
|
+
written = fs.writeSync(
|
|
126
|
+
state.fd,
|
|
127
|
+
task.buffer,
|
|
128
|
+
task.offset,
|
|
129
|
+
Math.min(task.buffer.byteLength - task.offset, 64 * 1024),
|
|
130
|
+
null,
|
|
131
|
+
);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error.code !== "EAGAIN" && error.code !== "EWOULDBLOCK") {
|
|
134
|
+
invalidateMasterState(state);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (written > 0) {
|
|
138
|
+
task.offset += written;
|
|
139
|
+
state.pendingWriteBytes -= written;
|
|
140
|
+
if (task.offset >= task.buffer.byteLength) {
|
|
141
|
+
state.writeQueue.shift();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
!state.disposed &&
|
|
146
|
+
state.writeQueue.length > 0 &&
|
|
147
|
+
!state.writeImmediate
|
|
148
|
+
) {
|
|
149
|
+
state.writeImmediate = setImmediate(() => {
|
|
150
|
+
state.writeImmediate = null;
|
|
151
|
+
processWriteQueue(state);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
allocate(ptyModule, options) {
|
|
157
|
+
if (
|
|
158
|
+
typeof ptyModule?.native?.open !== "function" ||
|
|
159
|
+
typeof ptyModule.native.resize !== "function"
|
|
160
|
+
) {
|
|
161
|
+
throw new TypeError("pty_module_native_open_unavailable");
|
|
162
|
+
}
|
|
163
|
+
const cols = options?.cols || 80;
|
|
164
|
+
const rows = options?.rows || 24;
|
|
165
|
+
if (
|
|
166
|
+
!Number.isInteger(cols) ||
|
|
167
|
+
cols <= 0 ||
|
|
168
|
+
cols > 65_535 ||
|
|
169
|
+
!Number.isInteger(rows) ||
|
|
170
|
+
rows <= 0 ||
|
|
171
|
+
rows > 65_535
|
|
172
|
+
) {
|
|
173
|
+
throw new TypeError("pty_dimensions_invalid");
|
|
174
|
+
}
|
|
175
|
+
let opened;
|
|
176
|
+
let master = null;
|
|
177
|
+
try {
|
|
178
|
+
opened = ptyModule.native.open(cols, rows);
|
|
179
|
+
if (
|
|
180
|
+
!Number.isInteger(opened?.master) ||
|
|
181
|
+
opened.master < 0 ||
|
|
182
|
+
!Number.isInteger(opened?.slave) ||
|
|
183
|
+
opened.slave < 0 ||
|
|
184
|
+
opened.master === opened.slave ||
|
|
185
|
+
typeof opened?.pty !== "string" ||
|
|
186
|
+
!/^\/dev\/pts\/\d+$/.test(opened.pty)
|
|
187
|
+
) {
|
|
188
|
+
throw new Error("pty_native_identity_unavailable");
|
|
189
|
+
}
|
|
190
|
+
const masterStat = fs.fstatSync(opened.master);
|
|
191
|
+
const slaveStat = fs.fstatSync(opened.slave);
|
|
192
|
+
const ptmxStat = fs.statSync("/dev/ptmx");
|
|
193
|
+
const pathStat = fs.statSync(opened.pty);
|
|
194
|
+
if (
|
|
195
|
+
!masterStat.isCharacterDevice() ||
|
|
196
|
+
!slaveStat.isCharacterDevice() ||
|
|
197
|
+
!ptmxStat.isCharacterDevice() ||
|
|
198
|
+
!pathStat.isCharacterDevice() ||
|
|
199
|
+
String(masterStat.dev) !== String(ptmxStat.dev) ||
|
|
200
|
+
String(masterStat.rdev) !== String(ptmxStat.rdev) ||
|
|
201
|
+
String(pathStat.dev) !== String(slaveStat.dev) ||
|
|
202
|
+
String(pathStat.rdev) !== String(slaveStat.rdev)
|
|
203
|
+
) {
|
|
204
|
+
throw new Error("pty_native_identity_invalid");
|
|
205
|
+
}
|
|
206
|
+
master = new tty.ReadStream(opened.master);
|
|
207
|
+
const encoding =
|
|
208
|
+
options?.encoding === null
|
|
209
|
+
? null
|
|
210
|
+
: typeof options?.encoding === "string"
|
|
211
|
+
? options.encoding
|
|
212
|
+
: "utf8";
|
|
213
|
+
if (encoding !== null) {
|
|
214
|
+
master.setEncoding(encoding);
|
|
215
|
+
}
|
|
216
|
+
const terminal = {
|
|
217
|
+
master,
|
|
218
|
+
ptsName: opened.pty,
|
|
219
|
+
};
|
|
220
|
+
const state = {
|
|
221
|
+
fd: opened.master,
|
|
222
|
+
rawSlaveFd: opened.slave,
|
|
223
|
+
blockingSlaveFd: null,
|
|
224
|
+
native: ptyModule.native,
|
|
225
|
+
cols,
|
|
226
|
+
rows,
|
|
227
|
+
encoding: encoding || "utf8",
|
|
228
|
+
writeQueue: [],
|
|
229
|
+
pendingWriteBytes: 0,
|
|
230
|
+
writeImmediate: null,
|
|
231
|
+
disposed: false,
|
|
232
|
+
readError: null,
|
|
233
|
+
master,
|
|
234
|
+
};
|
|
235
|
+
terminalStates.set(terminal, state);
|
|
236
|
+
master.on("error", (error) => {
|
|
237
|
+
state.readError = error;
|
|
238
|
+
invalidateMasterState(state);
|
|
239
|
+
});
|
|
240
|
+
master.once("close", () => invalidateMasterState(state));
|
|
241
|
+
return terminal;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (master) {
|
|
244
|
+
closeStream(master);
|
|
245
|
+
} else if (Number.isInteger(opened?.master)) {
|
|
246
|
+
try {
|
|
247
|
+
fs.closeSync(opened.master);
|
|
248
|
+
} catch {
|
|
249
|
+
// Preserve the allocation failure.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (Number.isInteger(opened?.slave)) {
|
|
253
|
+
try {
|
|
254
|
+
fs.closeSync(opened.slave);
|
|
255
|
+
} catch {
|
|
256
|
+
// Preserve the allocation failure.
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
openBlockingSlave(terminal) {
|
|
263
|
+
const ptsName = terminal?.ptsName;
|
|
264
|
+
const state = terminalState(terminal);
|
|
265
|
+
const originalFd = state.rawSlaveFd;
|
|
266
|
+
if (
|
|
267
|
+
typeof ptsName !== "string" ||
|
|
268
|
+
!/^\/dev\/pts\/\d+$/.test(ptsName) ||
|
|
269
|
+
!Number.isInteger(originalFd)
|
|
270
|
+
) {
|
|
271
|
+
throw new Error("pty_slave_identity_unavailable");
|
|
272
|
+
}
|
|
273
|
+
const originalStat = fs.fstatSync(originalFd);
|
|
274
|
+
if (!originalStat.isCharacterDevice()) {
|
|
275
|
+
throw new Error("pty_slave_not_character_device");
|
|
276
|
+
}
|
|
277
|
+
const flags =
|
|
278
|
+
Number(fs.constants.O_RDWR) |
|
|
279
|
+
Number(fs.constants.O_NOCTTY || 0) |
|
|
280
|
+
Number(fs.constants.O_CLOEXEC || 0);
|
|
281
|
+
const fd = fs.openSync(ptsName, flags);
|
|
282
|
+
try {
|
|
283
|
+
const openedStat = fs.fstatSync(fd);
|
|
284
|
+
const pathStat = fs.statSync(ptsName);
|
|
285
|
+
if (
|
|
286
|
+
!openedStat.isCharacterDevice() ||
|
|
287
|
+
!pathStat.isCharacterDevice() ||
|
|
288
|
+
String(openedStat.dev) !== String(originalStat.dev) ||
|
|
289
|
+
String(openedStat.rdev) !== String(originalStat.rdev) ||
|
|
290
|
+
String(pathStat.dev) !== String(originalStat.dev) ||
|
|
291
|
+
String(pathStat.rdev) !== String(originalStat.rdev)
|
|
292
|
+
) {
|
|
293
|
+
throw new Error("pty_slave_identity_changed");
|
|
294
|
+
}
|
|
295
|
+
fs.closeSync(originalFd);
|
|
296
|
+
state.rawSlaveFd = null;
|
|
297
|
+
state.blockingSlaveFd = fd;
|
|
298
|
+
blockingSlaveOwners.set(fd, state);
|
|
299
|
+
return fd;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
fs.closeSync(fd);
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
closeFd(fd) {
|
|
306
|
+
fs.closeSync(fd);
|
|
307
|
+
const state = blockingSlaveOwners.get(fd);
|
|
308
|
+
if (state) {
|
|
309
|
+
state.blockingSlaveFd = null;
|
|
310
|
+
blockingSlaveOwners.delete(fd);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
getCols(terminal) {
|
|
314
|
+
return terminalState(terminal).cols;
|
|
315
|
+
},
|
|
316
|
+
getRows(terminal) {
|
|
317
|
+
return terminalState(terminal).rows;
|
|
318
|
+
},
|
|
319
|
+
onData(terminal, listener) {
|
|
320
|
+
const master = terminal?.master;
|
|
321
|
+
if (typeof master?.on !== "function") {
|
|
322
|
+
throw new Error("pty_master_stream_unavailable");
|
|
323
|
+
}
|
|
324
|
+
master.on("data", listener);
|
|
325
|
+
return {
|
|
326
|
+
dispose() {
|
|
327
|
+
if (typeof master.off === "function") {
|
|
328
|
+
master.off("data", listener);
|
|
329
|
+
} else {
|
|
330
|
+
master.removeListener?.("data", listener);
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
write(terminal, data) {
|
|
336
|
+
const state = terminalState(terminal);
|
|
337
|
+
if (!hasLiveMaster(state)) {
|
|
338
|
+
invalidateMasterState(state);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const buffer =
|
|
342
|
+
typeof data === "string"
|
|
343
|
+
? Buffer.from(data, state.encoding)
|
|
344
|
+
: Buffer.from(data);
|
|
345
|
+
if (buffer.byteLength === 0) return;
|
|
346
|
+
if (
|
|
347
|
+
buffer.byteLength > MAX_PENDING_WRITE_BYTES ||
|
|
348
|
+
state.pendingWriteBytes > MAX_PENDING_WRITE_BYTES - buffer.byteLength
|
|
349
|
+
) {
|
|
350
|
+
const error = new Error("pty_write_queue_limit_exceeded");
|
|
351
|
+
error.code = "ERR_PTY_WRITE_BACKPRESSURE";
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
state.writeQueue.push({ buffer, offset: 0 });
|
|
355
|
+
state.pendingWriteBytes += buffer.byteLength;
|
|
356
|
+
processWriteQueue(state);
|
|
357
|
+
},
|
|
358
|
+
resize(terminal, cols, rows) {
|
|
359
|
+
const state = terminalState(terminal);
|
|
360
|
+
if (!hasLiveMaster(state)) {
|
|
361
|
+
invalidateMasterState(state);
|
|
362
|
+
const error = new Error("pty_terminal_closed");
|
|
363
|
+
error.code = "ERR_PTY_TERMINAL_CLOSED";
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
if (
|
|
367
|
+
!Number.isInteger(cols) ||
|
|
368
|
+
cols <= 0 ||
|
|
369
|
+
cols > 65_535 ||
|
|
370
|
+
!Number.isInteger(rows) ||
|
|
371
|
+
rows <= 0 ||
|
|
372
|
+
rows > 65_535
|
|
373
|
+
) {
|
|
374
|
+
throw new TypeError("pty_dimensions_invalid");
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
state.native.resize(state.fd, cols, rows);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
invalidateMasterState(state);
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
state.cols = cols;
|
|
383
|
+
state.rows = rows;
|
|
384
|
+
},
|
|
385
|
+
clear() {},
|
|
386
|
+
pause(terminal) {
|
|
387
|
+
return terminal?.master?.pause?.();
|
|
388
|
+
},
|
|
389
|
+
resume(terminal) {
|
|
390
|
+
return terminal?.master?.resume?.();
|
|
391
|
+
},
|
|
392
|
+
setEncoding(terminal, encoding) {
|
|
393
|
+
const master = terminal?.master;
|
|
394
|
+
if (master?._decoder) {
|
|
395
|
+
delete master._decoder;
|
|
396
|
+
}
|
|
397
|
+
if (encoding) {
|
|
398
|
+
master?.setEncoding?.(encoding);
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
releaseTerminal(terminal) {
|
|
402
|
+
const state = terminalStates.get(terminal);
|
|
403
|
+
if (state) {
|
|
404
|
+
invalidateMasterState(state);
|
|
405
|
+
if (Number.isInteger(state.rawSlaveFd)) {
|
|
406
|
+
try {
|
|
407
|
+
fs.closeSync(state.rawSlaveFd);
|
|
408
|
+
} catch {
|
|
409
|
+
// Closing the master below remains authoritative.
|
|
410
|
+
}
|
|
411
|
+
state.rawSlaveFd = null;
|
|
412
|
+
}
|
|
413
|
+
if (Number.isInteger(state.blockingSlaveFd)) {
|
|
414
|
+
try {
|
|
415
|
+
fs.closeSync(state.blockingSlaveFd);
|
|
416
|
+
} catch {
|
|
417
|
+
// The descriptor may already have been closed by the launch path.
|
|
418
|
+
}
|
|
419
|
+
blockingSlaveOwners.delete(state.blockingSlaveFd);
|
|
420
|
+
state.blockingSlaveFd = null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
closeStream(terminal?.master);
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function wrapSandboxedPty(terminal, child, command, ptyAdapter) {
|
|
429
|
+
const exitEmitter = new EventEmitter();
|
|
430
|
+
let exitState = null;
|
|
431
|
+
let closed = false;
|
|
432
|
+
const finish = (exitCode, signal) => {
|
|
433
|
+
if (closed) return;
|
|
434
|
+
closed = true;
|
|
435
|
+
exitState = {
|
|
436
|
+
exitCode: Number.isInteger(exitCode) ? exitCode : 1,
|
|
437
|
+
signal: signal ?? null,
|
|
438
|
+
};
|
|
439
|
+
exitEmitter.emit("exit", exitState);
|
|
440
|
+
};
|
|
441
|
+
child.once("exit", finish);
|
|
442
|
+
child.once("error", () => finish(1, null));
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
pid: child.pid,
|
|
446
|
+
process: command,
|
|
447
|
+
childProcess: child,
|
|
448
|
+
get cols() {
|
|
449
|
+
return ptyAdapter.getCols(terminal);
|
|
450
|
+
},
|
|
451
|
+
get rows() {
|
|
452
|
+
return ptyAdapter.getRows(terminal);
|
|
453
|
+
},
|
|
454
|
+
onData(listener) {
|
|
455
|
+
return ptyAdapter.onData(terminal, listener);
|
|
456
|
+
},
|
|
457
|
+
onExit(listener) {
|
|
458
|
+
if (exitState) {
|
|
459
|
+
queueMicrotask(() => listener(exitState));
|
|
460
|
+
return { dispose() {} };
|
|
461
|
+
}
|
|
462
|
+
exitEmitter.once("exit", listener);
|
|
463
|
+
return {
|
|
464
|
+
dispose() {
|
|
465
|
+
exitEmitter.off("exit", listener);
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
write(data) {
|
|
470
|
+
return ptyAdapter.write(terminal, data);
|
|
471
|
+
},
|
|
472
|
+
resize(cols, rows) {
|
|
473
|
+
if (closed) {
|
|
474
|
+
const error = new Error("pty_terminal_closed");
|
|
475
|
+
error.code = "ERR_PTY_TERMINAL_CLOSED";
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
return ptyAdapter.resize(terminal, cols, rows);
|
|
479
|
+
},
|
|
480
|
+
clear() {
|
|
481
|
+
return ptyAdapter.clear(terminal);
|
|
482
|
+
},
|
|
483
|
+
pause() {
|
|
484
|
+
return ptyAdapter.pause(terminal);
|
|
485
|
+
},
|
|
486
|
+
resume() {
|
|
487
|
+
return ptyAdapter.resume(terminal);
|
|
488
|
+
},
|
|
489
|
+
setEncoding(encoding) {
|
|
490
|
+
return ptyAdapter.setEncoding(terminal, encoding);
|
|
491
|
+
},
|
|
492
|
+
kill(signal = "SIGHUP") {
|
|
493
|
+
return child.kill(signal);
|
|
494
|
+
},
|
|
495
|
+
destroy() {
|
|
496
|
+
return child.kill("SIGHUP");
|
|
497
|
+
},
|
|
498
|
+
};
|
|
88
499
|
}
|
|
89
500
|
|
|
90
501
|
class ProcessExecutionBroker extends EventEmitter {
|
|
@@ -124,6 +535,8 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
124
535
|
applySandbox: _applySandbox,
|
|
125
536
|
postSpawnSandbox: _postSpawnSandbox,
|
|
126
537
|
};
|
|
538
|
+
this._ptyAdapter = createNativePtyAdapter();
|
|
539
|
+
this._hooksEventSink = null;
|
|
127
540
|
|
|
128
541
|
this._ensureLogDir();
|
|
129
542
|
this._loadPermissions();
|
|
@@ -271,6 +684,25 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
271
684
|
},
|
|
272
685
|
);
|
|
273
686
|
};
|
|
687
|
+
if (raw?.kind === LINUX_GENERIC_CONTRACT_KIND) {
|
|
688
|
+
const admitted = admitLinuxGenericSandboxExecutionContract(raw, {
|
|
689
|
+
origin: options.origin || "unknown",
|
|
690
|
+
command: launch?.command,
|
|
691
|
+
args: launch?.args,
|
|
692
|
+
cwd: options.cwd || process.cwd(),
|
|
693
|
+
shell: options.shell,
|
|
694
|
+
sync: launch?.sync === true,
|
|
695
|
+
pty: launch?.pty === true,
|
|
696
|
+
stdio: options.stdio,
|
|
697
|
+
requiredBoundaries,
|
|
698
|
+
});
|
|
699
|
+
if (!admitted) {
|
|
700
|
+
return invalid(
|
|
701
|
+
"sandboxExecutionContract was not issued for this workspace launch provenance",
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
return admitted;
|
|
705
|
+
}
|
|
274
706
|
if (
|
|
275
707
|
!consumeIssuedPluginSandboxExecutionContract(raw, {
|
|
276
708
|
origin: options.origin,
|
|
@@ -315,6 +747,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
315
747
|
![
|
|
316
748
|
"strict-plugin-node-bin",
|
|
317
749
|
"strict-plugin-native-static-elf-bin",
|
|
750
|
+
"strict-plugin-native-elf-bin",
|
|
318
751
|
].includes(raw.kind)
|
|
319
752
|
) {
|
|
320
753
|
return invalid("unsupported sandboxExecutionContract kind or version");
|
|
@@ -407,6 +840,58 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
407
840
|
});
|
|
408
841
|
}
|
|
409
842
|
|
|
843
|
+
/**
|
|
844
|
+
* Issue a private one-launch Linux workspace authority for trusted host
|
|
845
|
+
* call-sites. The writable root is a positional host value, never read from
|
|
846
|
+
* a manifest/spawn option. Unsupported platforms and mixed boundary sets
|
|
847
|
+
* return null so the normal fail-closed adapter path remains authoritative.
|
|
848
|
+
*/
|
|
849
|
+
issueLinuxWorkspaceSandboxExecutionContract(
|
|
850
|
+
command,
|
|
851
|
+
args = [],
|
|
852
|
+
options = {},
|
|
853
|
+
trustedWorkspaceRoot = process.cwd(),
|
|
854
|
+
{ sync = false, pty = false } = {},
|
|
855
|
+
) {
|
|
856
|
+
const requiredBoundaries = [
|
|
857
|
+
...new Set([
|
|
858
|
+
...(options.requiredBoundaries || []),
|
|
859
|
+
...(options.sandboxPolicy?.requiredBoundaries || []),
|
|
860
|
+
]),
|
|
861
|
+
];
|
|
862
|
+
if (
|
|
863
|
+
process.platform !== "linux" ||
|
|
864
|
+
requiredBoundaries.length === 0 ||
|
|
865
|
+
(options.shell !== undefined &&
|
|
866
|
+
options.shell !== null &&
|
|
867
|
+
options.shell !== false) ||
|
|
868
|
+
requiredBoundaries.some(
|
|
869
|
+
(boundary) =>
|
|
870
|
+
boundary !== SANDBOX_BOUNDARIES.FILESYSTEM &&
|
|
871
|
+
boundary !== SANDBOX_BOUNDARIES.NETWORK,
|
|
872
|
+
)
|
|
873
|
+
) {
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
return issueLinuxGenericSandboxExecutionContract({
|
|
877
|
+
origin: options.origin || "unknown",
|
|
878
|
+
command,
|
|
879
|
+
args,
|
|
880
|
+
cwd: options.cwd || trustedWorkspaceRoot,
|
|
881
|
+
workspaceRoot: trustedWorkspaceRoot,
|
|
882
|
+
shell: options.shell,
|
|
883
|
+
sync,
|
|
884
|
+
pty,
|
|
885
|
+
stdio: options.stdio,
|
|
886
|
+
requiredBoundaries,
|
|
887
|
+
detached: options.detached,
|
|
888
|
+
uid: options.uid,
|
|
889
|
+
gid: options.gid,
|
|
890
|
+
argv0: options.argv0,
|
|
891
|
+
serialization: options.serialization,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
410
895
|
_stripPluginControlOptions(options) {
|
|
411
896
|
delete options.pluginId;
|
|
412
897
|
delete options.pluginVersion;
|
|
@@ -636,6 +1121,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
636
1121
|
);
|
|
637
1122
|
}
|
|
638
1123
|
let runtimeProbe = null;
|
|
1124
|
+
let genericWorkspaceProbe = false;
|
|
1125
|
+
let sanitizedFilesystemPolicy = null;
|
|
1126
|
+
let sanitizedNetworkPolicy = null;
|
|
1127
|
+
let sanitizedPtyPolicy = null;
|
|
639
1128
|
if (plan.runtimeProbe !== null && plan.runtimeProbe !== undefined) {
|
|
640
1129
|
if (
|
|
641
1130
|
typeof plan.runtimeProbe !== "object" ||
|
|
@@ -659,6 +1148,9 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
659
1148
|
"pluginTreeContentSnapshotScope",
|
|
660
1149
|
"pluginTreeContentSnapshotMechanism",
|
|
661
1150
|
"pluginTreeSnapshotConsistency",
|
|
1151
|
+
"initialDynamicLoadClosureScope",
|
|
1152
|
+
"initialDynamicLoadClosureMechanism",
|
|
1153
|
+
"initialDynamicInterpreter",
|
|
662
1154
|
]) {
|
|
663
1155
|
if (
|
|
664
1156
|
plan.runtimeProbe[field] !== undefined &&
|
|
@@ -677,6 +1169,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
677
1169
|
"pluginTreeContentSnapshot",
|
|
678
1170
|
"pluginTreeSnapshotContractBound",
|
|
679
1171
|
"pluginTreeSnapshotAtomic",
|
|
1172
|
+
"initialDynamicLoadClosureDescriptorBound",
|
|
680
1173
|
]) {
|
|
681
1174
|
if (
|
|
682
1175
|
plan.runtimeProbe[field] !== undefined &&
|
|
@@ -691,6 +1184,8 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
691
1184
|
for (const field of [
|
|
692
1185
|
"pluginTreeContentSnapshotFiles",
|
|
693
1186
|
"pluginTreeContentSnapshotBytes",
|
|
1187
|
+
"initialDynamicDependencyCount",
|
|
1188
|
+
"initialDynamicRuntimeFileCount",
|
|
694
1189
|
]) {
|
|
695
1190
|
if (
|
|
696
1191
|
plan.runtimeProbe[field] !== undefined &&
|
|
@@ -716,6 +1211,19 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
716
1211
|
"Sandbox runtime probe pluginTreeContentSnapshotDigest must be a lowercase SHA-256 value",
|
|
717
1212
|
);
|
|
718
1213
|
}
|
|
1214
|
+
if (
|
|
1215
|
+
plan.runtimeProbe.initialDynamicLoadClosureDigest !== undefined &&
|
|
1216
|
+
(typeof plan.runtimeProbe.initialDynamicLoadClosureDigest !==
|
|
1217
|
+
"string" ||
|
|
1218
|
+
!/^[a-f0-9]{64}$/.test(
|
|
1219
|
+
plan.runtimeProbe.initialDynamicLoadClosureDigest,
|
|
1220
|
+
))
|
|
1221
|
+
) {
|
|
1222
|
+
throw this._sandboxError(
|
|
1223
|
+
"invalid_sandbox_plan",
|
|
1224
|
+
"Sandbox runtime probe initialDynamicLoadClosureDigest must be a lowercase SHA-256 value",
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
719
1227
|
const pluginTreeSnapshot =
|
|
720
1228
|
plan.runtimeProbe.pluginTreeContentSnapshot === true;
|
|
721
1229
|
const pluginTreeEvidenceFields = [
|
|
@@ -899,6 +1407,329 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
899
1407
|
);
|
|
900
1408
|
}
|
|
901
1409
|
}
|
|
1410
|
+
const initialDynamicLoadClosureBound =
|
|
1411
|
+
plan.runtimeProbe.initialDynamicLoadClosureDescriptorBound === true;
|
|
1412
|
+
const initialDynamicEvidenceFields = [
|
|
1413
|
+
"initialDynamicLoadClosureScope",
|
|
1414
|
+
"initialDynamicLoadClosureMechanism",
|
|
1415
|
+
"initialDynamicInterpreter",
|
|
1416
|
+
"initialDynamicDependencyCount",
|
|
1417
|
+
"initialDynamicRuntimeFileCount",
|
|
1418
|
+
"initialDynamicLoadClosureDigest",
|
|
1419
|
+
];
|
|
1420
|
+
const initialDynamicInterpreter =
|
|
1421
|
+
plan.runtimeProbe.initialDynamicInterpreter;
|
|
1422
|
+
const initialDynamicDependencyCount =
|
|
1423
|
+
plan.runtimeProbe.initialDynamicDependencyCount;
|
|
1424
|
+
const initialDynamicRuntimeFileCount =
|
|
1425
|
+
plan.runtimeProbe.initialDynamicRuntimeFileCount;
|
|
1426
|
+
const successfulDynamicProbe =
|
|
1427
|
+
plan.applied === true &&
|
|
1428
|
+
plan.runtimeProbe.kind ===
|
|
1429
|
+
"linux-bwrap-plugin-native-dynamic-elf-policy-v1" &&
|
|
1430
|
+
plan.runtimeProbe.runnable === true;
|
|
1431
|
+
if (
|
|
1432
|
+
initialDynamicLoadClosureBound &&
|
|
1433
|
+
(plan.applied !== true ||
|
|
1434
|
+
plan.platform !== "linux" ||
|
|
1435
|
+
plan.enforcement !== "linux-bwrap" ||
|
|
1436
|
+
backend !== "linux-bwrap" ||
|
|
1437
|
+
candidateBackend !== null ||
|
|
1438
|
+
policyAttested !== true ||
|
|
1439
|
+
policyDigest === null ||
|
|
1440
|
+
!guarantees.includes(SANDBOX_BOUNDARIES.FILESYSTEM) ||
|
|
1441
|
+
!guarantees.includes(SANDBOX_BOUNDARIES.NETWORK) ||
|
|
1442
|
+
plan.runtimeProbe.attempted !== true ||
|
|
1443
|
+
plan.runtimeProbe.runnable !== true ||
|
|
1444
|
+
plan.runtimeProbe.reason !== null ||
|
|
1445
|
+
plan.runtimeProbe.kind !==
|
|
1446
|
+
"linux-bwrap-plugin-native-dynamic-elf-policy-v1" ||
|
|
1447
|
+
plan.runtimeProbe.probeRuntime !== "node" ||
|
|
1448
|
+
plan.runtimeProbe.targetRuntime !== "native-dynamic-elf" ||
|
|
1449
|
+
plan.runtimeProbe.contentSnapshot !== true ||
|
|
1450
|
+
plan.runtimeProbe.contentSnapshotScope !==
|
|
1451
|
+
"plugin-entry-executable" ||
|
|
1452
|
+
plan.runtimeProbe.contentSnapshotMechanism !==
|
|
1453
|
+
"verified-o_tmpfile-copy-bwrap-ro-bind-data-v1" ||
|
|
1454
|
+
plan.runtimeProbe.handleAtomic !== false ||
|
|
1455
|
+
!supervisorBound ||
|
|
1456
|
+
plan.runtimeProbe.initialDynamicLoadClosureScope !==
|
|
1457
|
+
"initial-pt_interp-and-direct-dt_needed-attested-system-set" ||
|
|
1458
|
+
plan.runtimeProbe.initialDynamicLoadClosureMechanism !==
|
|
1459
|
+
"parsed-elf-direct-system-set-to-attested-node-runtime-fds-v1" ||
|
|
1460
|
+
typeof initialDynamicInterpreter !== "string" ||
|
|
1461
|
+
!path.posix.isAbsolute(initialDynamicInterpreter) ||
|
|
1462
|
+
path.posix.normalize(initialDynamicInterpreter) !==
|
|
1463
|
+
initialDynamicInterpreter ||
|
|
1464
|
+
!["/lib/", "/lib64/", "/usr/lib/", "/usr/lib64/"].some((prefix) =>
|
|
1465
|
+
initialDynamicInterpreter.startsWith(prefix),
|
|
1466
|
+
) ||
|
|
1467
|
+
!Number.isSafeInteger(initialDynamicDependencyCount) ||
|
|
1468
|
+
initialDynamicDependencyCount < 0 ||
|
|
1469
|
+
initialDynamicDependencyCount > 128 ||
|
|
1470
|
+
!Number.isSafeInteger(initialDynamicRuntimeFileCount) ||
|
|
1471
|
+
initialDynamicRuntimeFileCount < 1 ||
|
|
1472
|
+
initialDynamicRuntimeFileCount > 129 ||
|
|
1473
|
+
initialDynamicRuntimeFileCount <
|
|
1474
|
+
Math.max(1, initialDynamicDependencyCount) ||
|
|
1475
|
+
initialDynamicRuntimeFileCount > initialDynamicDependencyCount + 1 ||
|
|
1476
|
+
!/^[a-f0-9]{64}$/.test(
|
|
1477
|
+
plan.runtimeProbe.initialDynamicLoadClosureDigest || "",
|
|
1478
|
+
))
|
|
1479
|
+
) {
|
|
1480
|
+
throw this._sandboxError(
|
|
1481
|
+
"invalid_sandbox_plan",
|
|
1482
|
+
"Sandbox runtime probe initial dynamic direct system set evidence must use the typed descriptor-bound contract",
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
if (
|
|
1486
|
+
!initialDynamicLoadClosureBound &&
|
|
1487
|
+
initialDynamicEvidenceFields.some(
|
|
1488
|
+
(field) => plan.runtimeProbe[field] !== undefined,
|
|
1489
|
+
)
|
|
1490
|
+
) {
|
|
1491
|
+
throw this._sandboxError(
|
|
1492
|
+
"invalid_sandbox_plan",
|
|
1493
|
+
"Sandbox runtime probe initial dynamic direct system set evidence requires initialDynamicLoadClosureDescriptorBound",
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
if (successfulDynamicProbe && !initialDynamicLoadClosureBound) {
|
|
1497
|
+
throw this._sandboxError(
|
|
1498
|
+
"invalid_sandbox_plan",
|
|
1499
|
+
"Sandbox runtime probe successful dynamic ELF evidence requires a descriptor-bound initial direct system set",
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
const genericWorkspaceEvidenceFields = [
|
|
1503
|
+
"contractDigest",
|
|
1504
|
+
"policyDigest",
|
|
1505
|
+
"mountTopologyDigest",
|
|
1506
|
+
"sourceMountSetDigest",
|
|
1507
|
+
"emptyRoot",
|
|
1508
|
+
"undeclaredRootReadOnly",
|
|
1509
|
+
"workspaceReadWrite",
|
|
1510
|
+
"workspaceMountTopologyAttested",
|
|
1511
|
+
"workspaceRootAliasAttested",
|
|
1512
|
+
"anonymousDevWritable",
|
|
1513
|
+
"systemReadOnly",
|
|
1514
|
+
"hostHomeHidden",
|
|
1515
|
+
"outsideMarkerHidden",
|
|
1516
|
+
"networkNamespace",
|
|
1517
|
+
"networkNamespaceChanged",
|
|
1518
|
+
"socketCreationDenied",
|
|
1519
|
+
"descriptorMounts",
|
|
1520
|
+
"mountTopologyAtomic",
|
|
1521
|
+
"sourceMountPropagationPrivateAtAttestation",
|
|
1522
|
+
];
|
|
1523
|
+
for (const field of [
|
|
1524
|
+
"contractDigest",
|
|
1525
|
+
"policyDigest",
|
|
1526
|
+
"mountTopologyDigest",
|
|
1527
|
+
"sourceMountSetDigest",
|
|
1528
|
+
]) {
|
|
1529
|
+
if (
|
|
1530
|
+
plan.runtimeProbe[field] !== undefined &&
|
|
1531
|
+
(typeof plan.runtimeProbe[field] !== "string" ||
|
|
1532
|
+
!/^[a-f0-9]{64}$/.test(plan.runtimeProbe[field]))
|
|
1533
|
+
) {
|
|
1534
|
+
throw this._sandboxError(
|
|
1535
|
+
"invalid_sandbox_plan",
|
|
1536
|
+
`Sandbox runtime probe ${field} must be a lowercase SHA-256 value`,
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
for (const field of genericWorkspaceEvidenceFields.slice(4)) {
|
|
1541
|
+
if (
|
|
1542
|
+
plan.runtimeProbe[field] !== undefined &&
|
|
1543
|
+
typeof plan.runtimeProbe[field] !== "boolean"
|
|
1544
|
+
) {
|
|
1545
|
+
throw this._sandboxError(
|
|
1546
|
+
"invalid_sandbox_plan",
|
|
1547
|
+
`Sandbox runtime probe ${field} must be boolean`,
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const genericWorkspaceKind =
|
|
1552
|
+
plan.runtimeProbe.kind === "linux-bwrap-generic-workspace-policy-v1";
|
|
1553
|
+
if (
|
|
1554
|
+
!genericWorkspaceKind &&
|
|
1555
|
+
genericWorkspaceEvidenceFields.some(
|
|
1556
|
+
(field) => plan.runtimeProbe[field] !== undefined,
|
|
1557
|
+
)
|
|
1558
|
+
) {
|
|
1559
|
+
throw this._sandboxError(
|
|
1560
|
+
"invalid_sandbox_plan",
|
|
1561
|
+
"Linux generic workspace evidence requires its typed runtime probe kind",
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
if (genericWorkspaceKind) {
|
|
1565
|
+
const filesystemPolicy = plan.filesystemPolicy;
|
|
1566
|
+
const networkPolicy = plan.networkPolicy;
|
|
1567
|
+
const ptyPolicy = plan.ptyPolicy ?? null;
|
|
1568
|
+
const ptyPlan =
|
|
1569
|
+
ptyPolicy !== null &&
|
|
1570
|
+
typeof ptyPolicy === "object" &&
|
|
1571
|
+
!Array.isArray(ptyPolicy);
|
|
1572
|
+
const ptyPlanValid = ptyPlan
|
|
1573
|
+
? ptyPolicy.mode === "dedicated-controlling-terminal" &&
|
|
1574
|
+
ptyPolicy.launcherPath === "/usr/bin/setsid" &&
|
|
1575
|
+
typeof ptyPolicy.launcherSha256 === "string" &&
|
|
1576
|
+
/^[a-f0-9]{64}$/.test(ptyPolicy.launcherSha256) &&
|
|
1577
|
+
Number.isSafeInteger(ptyPolicy.launcherBytes) &&
|
|
1578
|
+
ptyPolicy.launcherBytes > 0 &&
|
|
1579
|
+
ptyPolicy.launcherBytes <= 256 * 1024 * 1024 &&
|
|
1580
|
+
ptyPolicy.launcherDescriptorBound === true &&
|
|
1581
|
+
ptyPolicy.launcherExecutablePinned === true &&
|
|
1582
|
+
ptyPolicy.launcherDescriptorConsumedBeforeTarget === true &&
|
|
1583
|
+
ptyPolicy.launcherStagingPathHidden === true &&
|
|
1584
|
+
ptyPolicy.bwrapNewSession === false &&
|
|
1585
|
+
plan.args[0] === "--ctty" &&
|
|
1586
|
+
/^\/proc\/self\/fd\/\d+$/.test(plan.args[1] || "") &&
|
|
1587
|
+
!plan.args.includes("--new-session")
|
|
1588
|
+
: ptyPolicy === null &&
|
|
1589
|
+
plan.args[0] !== "--ctty" &&
|
|
1590
|
+
plan.args.includes("--new-session");
|
|
1591
|
+
const workspaceRoot = filesystemPolicy?.workspaceRoot;
|
|
1592
|
+
const workingDirectory = filesystemPolicy?.workingDirectory;
|
|
1593
|
+
const workspaceRelative =
|
|
1594
|
+
typeof workspaceRoot === "string" &&
|
|
1595
|
+
typeof workingDirectory === "string"
|
|
1596
|
+
? path.posix.relative(workspaceRoot, workingDirectory)
|
|
1597
|
+
: "..";
|
|
1598
|
+
genericWorkspaceProbe =
|
|
1599
|
+
plan.applied === true &&
|
|
1600
|
+
plan.platform === "linux" &&
|
|
1601
|
+
plan.enforcement === "linux-bwrap-workspace" &&
|
|
1602
|
+
backend === "linux-bwrap-workspace" &&
|
|
1603
|
+
candidateBackend === null &&
|
|
1604
|
+
policyAttested === true &&
|
|
1605
|
+
policyDigest !== null &&
|
|
1606
|
+
plan.runtimeProbe.policyDigest === policyDigest &&
|
|
1607
|
+
/^[a-f0-9]{64}$/.test(plan.runtimeProbe.contractDigest || "") &&
|
|
1608
|
+
guarantees.includes(SANDBOX_BOUNDARIES.FILESYSTEM) &&
|
|
1609
|
+
guarantees.includes(SANDBOX_BOUNDARIES.NETWORK) &&
|
|
1610
|
+
plan.runtimeProbe.attempted === true &&
|
|
1611
|
+
plan.runtimeProbe.runnable === true &&
|
|
1612
|
+
plan.runtimeProbe.reason === null &&
|
|
1613
|
+
plan.runtimeProbe.probeRuntime === "posix-sh" &&
|
|
1614
|
+
plan.runtimeProbe.targetRuntime === "generic-command" &&
|
|
1615
|
+
plan.runtimeProbe.contentSnapshot === false &&
|
|
1616
|
+
plan.runtimeProbe.handleAtomic === false &&
|
|
1617
|
+
plan.runtimeProbe.mountTopologyAtomic === false &&
|
|
1618
|
+
plan.runtimeProbe.mountTopologyDigest ===
|
|
1619
|
+
filesystemPolicy?.mountTopologyDigest &&
|
|
1620
|
+
plan.runtimeProbe.sourceMountSetDigest ===
|
|
1621
|
+
filesystemPolicy?.sourceMountSetDigest &&
|
|
1622
|
+
plan.runtimeProbe.sourceMountPropagationPrivateAtAttestation ===
|
|
1623
|
+
true &&
|
|
1624
|
+
[
|
|
1625
|
+
"emptyRoot",
|
|
1626
|
+
"undeclaredRootReadOnly",
|
|
1627
|
+
"workspaceReadWrite",
|
|
1628
|
+
"workspaceMountTopologyAttested",
|
|
1629
|
+
"workspaceRootAliasAttested",
|
|
1630
|
+
"anonymousDevWritable",
|
|
1631
|
+
"systemReadOnly",
|
|
1632
|
+
"hostHomeHidden",
|
|
1633
|
+
"outsideMarkerHidden",
|
|
1634
|
+
"networkNamespace",
|
|
1635
|
+
"networkNamespaceChanged",
|
|
1636
|
+
"socketCreationDenied",
|
|
1637
|
+
"descriptorMounts",
|
|
1638
|
+
].every((field) => plan.runtimeProbe[field] === true) &&
|
|
1639
|
+
typeof workspaceRoot === "string" &&
|
|
1640
|
+
path.posix.isAbsolute(workspaceRoot) &&
|
|
1641
|
+
workspaceRoot !== "/" &&
|
|
1642
|
+
typeof workingDirectory === "string" &&
|
|
1643
|
+
path.posix.isAbsolute(workingDirectory) &&
|
|
1644
|
+
workspaceRelative !== ".." &&
|
|
1645
|
+
!workspaceRelative.startsWith("../") &&
|
|
1646
|
+
!path.posix.isAbsolute(workspaceRelative) &&
|
|
1647
|
+
filesystemPolicy.workspaceAccess === "read-write" &&
|
|
1648
|
+
filesystemPolicy.systemAccess === "read-only" &&
|
|
1649
|
+
filesystemPolicy.undeclaredRootAccess === "read-only" &&
|
|
1650
|
+
filesystemPolicy.hostRootMapped === false &&
|
|
1651
|
+
filesystemPolicy.hostHomeMapped === false &&
|
|
1652
|
+
filesystemPolicy.workspaceDescriptorBound === true &&
|
|
1653
|
+
filesystemPolicy.systemDescriptorBound === true &&
|
|
1654
|
+
filesystemPolicy.exactEtcFileDescriptors === true &&
|
|
1655
|
+
filesystemPolicy.workspaceRecursiveBind === true &&
|
|
1656
|
+
filesystemPolicy.workspaceMountTopology ===
|
|
1657
|
+
"no-strict-descendants-or-forbidden-root-aliases-at-attestation" &&
|
|
1658
|
+
filesystemPolicy.mountTopologySource === "proc-self-mountinfo" &&
|
|
1659
|
+
/^[a-f0-9]{64}$/.test(filesystemPolicy.mountTopologyDigest || "") &&
|
|
1660
|
+
/^[a-f0-9]{64}$/.test(filesystemPolicy.sourceMountSetDigest || "") &&
|
|
1661
|
+
filesystemPolicy.sourceMountPropagationPrivateAtAttestation ===
|
|
1662
|
+
true &&
|
|
1663
|
+
filesystemPolicy.mountTopologyAtomic === false &&
|
|
1664
|
+
Array.isArray(filesystemPolicy.anonymousWritablePaths) &&
|
|
1665
|
+
filesystemPolicy.anonymousWritablePaths.length === 5 &&
|
|
1666
|
+
["/home/sandbox", "/dev", "/run", "/tmp", "/var/tmp"].every(
|
|
1667
|
+
(value, index) =>
|
|
1668
|
+
filesystemPolicy.anonymousWritablePaths[index] === value,
|
|
1669
|
+
) &&
|
|
1670
|
+
networkPolicy?.namespace === "new" &&
|
|
1671
|
+
networkPolicy?.namespaceIdentityChanged === true &&
|
|
1672
|
+
networkPolicy?.seccomp === "deny-network-creation" &&
|
|
1673
|
+
plan.command.startsWith("/proc/self/fd/") &&
|
|
1674
|
+
plan.options?.cwd === "/" &&
|
|
1675
|
+
plan.options?.shell === false &&
|
|
1676
|
+
Array.isArray(plan.options?.stdio) &&
|
|
1677
|
+
plan.args.includes("--bind-fd") &&
|
|
1678
|
+
plan.args.includes("--ro-bind-fd") &&
|
|
1679
|
+
plan.args.includes("--remount-ro") &&
|
|
1680
|
+
plan.args.includes("--unshare-net") &&
|
|
1681
|
+
plan.args.includes("--seccomp") &&
|
|
1682
|
+
!plan.args.includes("--ro-bind") &&
|
|
1683
|
+
ptyPlanValid;
|
|
1684
|
+
if (!genericWorkspaceProbe) {
|
|
1685
|
+
throw this._sandboxError(
|
|
1686
|
+
"invalid_sandbox_plan",
|
|
1687
|
+
"Linux generic workspace evidence must use the typed descriptor-bound empty-root contract",
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
sanitizedFilesystemPolicy = Object.freeze({
|
|
1691
|
+
workspaceRoot,
|
|
1692
|
+
workingDirectory,
|
|
1693
|
+
workspaceAccess: filesystemPolicy.workspaceAccess,
|
|
1694
|
+
systemAccess: filesystemPolicy.systemAccess,
|
|
1695
|
+
undeclaredRootAccess: filesystemPolicy.undeclaredRootAccess,
|
|
1696
|
+
anonymousWritablePaths: Object.freeze([
|
|
1697
|
+
...filesystemPolicy.anonymousWritablePaths,
|
|
1698
|
+
]),
|
|
1699
|
+
hostRootMapped: filesystemPolicy.hostRootMapped,
|
|
1700
|
+
hostHomeMapped: filesystemPolicy.hostHomeMapped,
|
|
1701
|
+
workspaceDescriptorBound: filesystemPolicy.workspaceDescriptorBound,
|
|
1702
|
+
systemDescriptorBound: filesystemPolicy.systemDescriptorBound,
|
|
1703
|
+
exactEtcFileDescriptors: filesystemPolicy.exactEtcFileDescriptors,
|
|
1704
|
+
workspaceRecursiveBind: filesystemPolicy.workspaceRecursiveBind,
|
|
1705
|
+
workspaceMountTopology: filesystemPolicy.workspaceMountTopology,
|
|
1706
|
+
mountTopologySource: filesystemPolicy.mountTopologySource,
|
|
1707
|
+
mountTopologyDigest: filesystemPolicy.mountTopologyDigest,
|
|
1708
|
+
sourceMountSetDigest: filesystemPolicy.sourceMountSetDigest,
|
|
1709
|
+
sourceMountPropagationPrivateAtAttestation:
|
|
1710
|
+
filesystemPolicy.sourceMountPropagationPrivateAtAttestation,
|
|
1711
|
+
mountTopologyAtomic: filesystemPolicy.mountTopologyAtomic,
|
|
1712
|
+
});
|
|
1713
|
+
sanitizedNetworkPolicy = Object.freeze({
|
|
1714
|
+
namespace: networkPolicy.namespace,
|
|
1715
|
+
namespaceIdentityChanged: networkPolicy.namespaceIdentityChanged,
|
|
1716
|
+
seccomp: networkPolicy.seccomp,
|
|
1717
|
+
});
|
|
1718
|
+
sanitizedPtyPolicy = ptyPlan
|
|
1719
|
+
? Object.freeze({
|
|
1720
|
+
mode: ptyPolicy.mode,
|
|
1721
|
+
launcherPath: ptyPolicy.launcherPath,
|
|
1722
|
+
launcherSha256: ptyPolicy.launcherSha256,
|
|
1723
|
+
launcherBytes: ptyPolicy.launcherBytes,
|
|
1724
|
+
launcherDescriptorBound: ptyPolicy.launcherDescriptorBound,
|
|
1725
|
+
launcherExecutablePinned: ptyPolicy.launcherExecutablePinned,
|
|
1726
|
+
launcherDescriptorConsumedBeforeTarget:
|
|
1727
|
+
ptyPolicy.launcherDescriptorConsumedBeforeTarget,
|
|
1728
|
+
launcherStagingPathHidden: ptyPolicy.launcherStagingPathHidden,
|
|
1729
|
+
bwrapNewSession: ptyPolicy.bwrapNewSession,
|
|
1730
|
+
})
|
|
1731
|
+
: null;
|
|
1732
|
+
}
|
|
902
1733
|
runtimeProbe = {
|
|
903
1734
|
kind: plan.runtimeProbe.kind,
|
|
904
1735
|
attempted: plan.runtimeProbe.attempted,
|
|
@@ -944,6 +1775,19 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
944
1775
|
runtimeProbe[field] = plan.runtimeProbe[field];
|
|
945
1776
|
}
|
|
946
1777
|
}
|
|
1778
|
+
for (const field of [
|
|
1779
|
+
"initialDynamicLoadClosureDescriptorBound",
|
|
1780
|
+
...initialDynamicEvidenceFields,
|
|
1781
|
+
]) {
|
|
1782
|
+
if (plan.runtimeProbe[field] !== undefined) {
|
|
1783
|
+
runtimeProbe[field] = plan.runtimeProbe[field];
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
for (const field of genericWorkspaceEvidenceFields) {
|
|
1787
|
+
if (plan.runtimeProbe[field] !== undefined) {
|
|
1788
|
+
runtimeProbe[field] = plan.runtimeProbe[field];
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
947
1791
|
if (supervisorIdentity !== undefined) {
|
|
948
1792
|
runtimeProbe.supervisorExecutableIdentity = {
|
|
949
1793
|
path: supervisorIdentity.path,
|
|
@@ -960,6 +1804,12 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
960
1804
|
};
|
|
961
1805
|
}
|
|
962
1806
|
}
|
|
1807
|
+
if (backend === "linux-bwrap-workspace" && !genericWorkspaceProbe) {
|
|
1808
|
+
throw this._sandboxError(
|
|
1809
|
+
"invalid_sandbox_plan",
|
|
1810
|
+
"Applied Linux generic workspace plans require typed runtime evidence",
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
963
1813
|
const postSpawn = plan.postSpawn || { required: false, mode: "none" };
|
|
964
1814
|
if (
|
|
965
1815
|
postSpawn.required &&
|
|
@@ -981,6 +1831,13 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
981
1831
|
policyAttested,
|
|
982
1832
|
policyDigest,
|
|
983
1833
|
runtimeProbe,
|
|
1834
|
+
...(sanitizedFilesystemPolicy
|
|
1835
|
+
? {
|
|
1836
|
+
filesystemPolicy: sanitizedFilesystemPolicy,
|
|
1837
|
+
networkPolicy: sanitizedNetworkPolicy,
|
|
1838
|
+
ptyPolicy: sanitizedPtyPolicy,
|
|
1839
|
+
}
|
|
1840
|
+
: {}),
|
|
984
1841
|
postSpawn: { ...postSpawn },
|
|
985
1842
|
};
|
|
986
1843
|
}
|
|
@@ -992,9 +1849,18 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
992
1849
|
(boundary) => !guaranteed.has(boundary),
|
|
993
1850
|
);
|
|
994
1851
|
if (missingBoundaries.length > 0) {
|
|
1852
|
+
const candidateReason =
|
|
1853
|
+
typeof plan.reason === "string" && plan.reason
|
|
1854
|
+
? `; reason=${plan.reason}${
|
|
1855
|
+
typeof plan.runtimeProbe?.reason === "string" &&
|
|
1856
|
+
plan.runtimeProbe.reason
|
|
1857
|
+
? `:${plan.runtimeProbe.reason}`
|
|
1858
|
+
: ""
|
|
1859
|
+
}`
|
|
1860
|
+
: "";
|
|
995
1861
|
throw this._sandboxBoundaryError(
|
|
996
1862
|
"required_boundaries_unsatisfied",
|
|
997
|
-
`Sandbox backend ${plan.backend || plan.candidateBackend || "unavailable"} cannot satisfy required boundaries: ${missingBoundaries.join(", ")}`,
|
|
1863
|
+
`Sandbox backend ${plan.backend || plan.candidateBackend || "unavailable"} cannot satisfy required boundaries: ${missingBoundaries.join(", ")}${candidateReason}`,
|
|
998
1864
|
{
|
|
999
1865
|
requiredBoundaries,
|
|
1000
1866
|
actualGuarantees,
|
|
@@ -1018,7 +1884,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1018
1884
|
command,
|
|
1019
1885
|
args,
|
|
1020
1886
|
options,
|
|
1021
|
-
{ sync = false, sandboxPolicy = {} } = {},
|
|
1887
|
+
{ sync = false, pty = false, sandboxPolicy = {} } = {},
|
|
1022
1888
|
) {
|
|
1023
1889
|
const strict = this._sandboxStrictEnabled();
|
|
1024
1890
|
const requiredBoundaries = sandboxPolicy.requiredBoundaries || [];
|
|
@@ -1057,6 +1923,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1057
1923
|
profile,
|
|
1058
1924
|
requiredBoundaries: Object.freeze([...requiredBoundaries]),
|
|
1059
1925
|
sync,
|
|
1926
|
+
...(pty ? { pty: true } : {}),
|
|
1060
1927
|
...(sandboxPolicy.executionContract
|
|
1061
1928
|
? { executionContract: sandboxPolicy.executionContract }
|
|
1062
1929
|
: {}),
|
|
@@ -1135,6 +2002,57 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1135
2002
|
auditEntry.sandboxPolicyAttested =
|
|
1136
2003
|
typeof plan?.policyAttested === "boolean" ? plan.policyAttested : null;
|
|
1137
2004
|
auditEntry.sandboxPolicyDigest = plan?.policyDigest || null;
|
|
2005
|
+
const genericPolicy =
|
|
2006
|
+
applied === true && plan?.backend === "linux-bwrap-workspace";
|
|
2007
|
+
auditEntry.sandboxFilesystemPolicy = genericPolicy
|
|
2008
|
+
? {
|
|
2009
|
+
workspaceRoot: plan.filesystemPolicy.workspaceRoot,
|
|
2010
|
+
workingDirectory: plan.filesystemPolicy.workingDirectory,
|
|
2011
|
+
workspaceAccess: plan.filesystemPolicy.workspaceAccess,
|
|
2012
|
+
systemAccess: plan.filesystemPolicy.systemAccess,
|
|
2013
|
+
undeclaredRootAccess: plan.filesystemPolicy.undeclaredRootAccess,
|
|
2014
|
+
anonymousWritablePaths: [
|
|
2015
|
+
...plan.filesystemPolicy.anonymousWritablePaths,
|
|
2016
|
+
],
|
|
2017
|
+
hostRootMapped: plan.filesystemPolicy.hostRootMapped,
|
|
2018
|
+
hostHomeMapped: plan.filesystemPolicy.hostHomeMapped,
|
|
2019
|
+
workspaceDescriptorBound:
|
|
2020
|
+
plan.filesystemPolicy.workspaceDescriptorBound,
|
|
2021
|
+
systemDescriptorBound: plan.filesystemPolicy.systemDescriptorBound,
|
|
2022
|
+
exactEtcFileDescriptors:
|
|
2023
|
+
plan.filesystemPolicy.exactEtcFileDescriptors,
|
|
2024
|
+
workspaceRecursiveBind: plan.filesystemPolicy.workspaceRecursiveBind,
|
|
2025
|
+
workspaceMountTopology: plan.filesystemPolicy.workspaceMountTopology,
|
|
2026
|
+
mountTopologySource: plan.filesystemPolicy.mountTopologySource,
|
|
2027
|
+
mountTopologyDigest: plan.filesystemPolicy.mountTopologyDigest,
|
|
2028
|
+
sourceMountSetDigest: plan.filesystemPolicy.sourceMountSetDigest,
|
|
2029
|
+
sourceMountPropagationPrivateAtAttestation:
|
|
2030
|
+
plan.filesystemPolicy.sourceMountPropagationPrivateAtAttestation,
|
|
2031
|
+
mountTopologyAtomic: plan.filesystemPolicy.mountTopologyAtomic,
|
|
2032
|
+
}
|
|
2033
|
+
: null;
|
|
2034
|
+
auditEntry.sandboxNetworkPolicy = genericPolicy
|
|
2035
|
+
? {
|
|
2036
|
+
namespace: plan.networkPolicy.namespace,
|
|
2037
|
+
namespaceIdentityChanged: plan.networkPolicy.namespaceIdentityChanged,
|
|
2038
|
+
seccomp: plan.networkPolicy.seccomp,
|
|
2039
|
+
}
|
|
2040
|
+
: null;
|
|
2041
|
+
auditEntry.sandboxPtyPolicy =
|
|
2042
|
+
genericPolicy && plan.ptyPolicy
|
|
2043
|
+
? {
|
|
2044
|
+
mode: plan.ptyPolicy.mode,
|
|
2045
|
+
launcherPath: plan.ptyPolicy.launcherPath,
|
|
2046
|
+
launcherSha256: plan.ptyPolicy.launcherSha256,
|
|
2047
|
+
launcherBytes: plan.ptyPolicy.launcherBytes,
|
|
2048
|
+
launcherDescriptorBound: plan.ptyPolicy.launcherDescriptorBound,
|
|
2049
|
+
launcherExecutablePinned: plan.ptyPolicy.launcherExecutablePinned,
|
|
2050
|
+
launcherDescriptorConsumedBeforeTarget:
|
|
2051
|
+
plan.ptyPolicy.launcherDescriptorConsumedBeforeTarget,
|
|
2052
|
+
launcherStagingPathHidden: plan.ptyPolicy.launcherStagingPathHidden,
|
|
2053
|
+
bwrapNewSession: plan.ptyPolicy.bwrapNewSession,
|
|
2054
|
+
}
|
|
2055
|
+
: null;
|
|
1138
2056
|
auditEntry.sandboxCandidateReason = plan?.candidateBackend
|
|
1139
2057
|
? plan?.reason || null
|
|
1140
2058
|
: null;
|
|
@@ -1396,12 +2314,13 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1396
2314
|
|
|
1397
2315
|
_writeRplEntry(auditEntry, status = "started", error = null) {
|
|
1398
2316
|
const rpl = getRpl();
|
|
1399
|
-
if (!rpl
|
|
2317
|
+
if (!rpl) return;
|
|
1400
2318
|
try {
|
|
1401
2319
|
const traceCtx = this._getTraceContext();
|
|
1402
|
-
rpl.
|
|
2320
|
+
if (typeof rpl.record !== "function") return;
|
|
2321
|
+
rpl.record(
|
|
2322
|
+
"process.execution",
|
|
1403
2323
|
{
|
|
1404
|
-
kind: "process.execution",
|
|
1405
2324
|
component: auditEntry.origin,
|
|
1406
2325
|
action: "spawn",
|
|
1407
2326
|
artifactId: auditEntry.executionId,
|
|
@@ -1422,6 +2341,15 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1422
2341
|
policy: auditEntry.policy,
|
|
1423
2342
|
scope: auditEntry.scope,
|
|
1424
2343
|
},
|
|
2344
|
+
sandbox: {
|
|
2345
|
+
applied: auditEntry.sandboxed === true,
|
|
2346
|
+
backend: auditEntry.sandboxBackend || null,
|
|
2347
|
+
guarantees: [...(auditEntry.sandboxGuarantees || [])],
|
|
2348
|
+
policyAttested: auditEntry.sandboxPolicyAttested === true,
|
|
2349
|
+
policyDigest: auditEntry.sandboxPolicyDigest || null,
|
|
2350
|
+
filesystemPolicy: auditEntry.sandboxFilesystemPolicy || null,
|
|
2351
|
+
networkPolicy: auditEntry.sandboxNetworkPolicy || null,
|
|
2352
|
+
},
|
|
1425
2353
|
traceId:
|
|
1426
2354
|
traceCtx?.traceId || auditEntry.traceId || `trace-${Date.now()}`,
|
|
1427
2355
|
parentSpanId: traceCtx?.spanId || null,
|
|
@@ -1436,15 +2364,22 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1436
2364
|
}
|
|
1437
2365
|
|
|
1438
2366
|
_emitHooksEvent(event, data) {
|
|
1439
|
-
const hooks =
|
|
1440
|
-
if (!hooks
|
|
2367
|
+
const hooks = this._hooksEventSink;
|
|
2368
|
+
if (!hooks) return;
|
|
1441
2369
|
try {
|
|
1442
|
-
hooks.
|
|
2370
|
+
hooks.emit(event, data);
|
|
1443
2371
|
} catch {
|
|
1444
2372
|
// Hook reporting must not hide the process execution result.
|
|
1445
2373
|
}
|
|
1446
2374
|
}
|
|
1447
2375
|
|
|
2376
|
+
_setHooksEventSink(hooks) {
|
|
2377
|
+
if (hooks !== null && typeof hooks?.emit !== "function") {
|
|
2378
|
+
throw new TypeError("hooks event sink must expose emit()");
|
|
2379
|
+
}
|
|
2380
|
+
this._hooksEventSink = hooks;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
1448
2383
|
spawn(command, args, options = {}) {
|
|
1449
2384
|
const executionId = crypto.randomUUID();
|
|
1450
2385
|
const startTime = Date.now();
|
|
@@ -1607,6 +2542,20 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1607
2542
|
proc,
|
|
1608
2543
|
sandboxPlan.cleanup,
|
|
1609
2544
|
);
|
|
2545
|
+
if (
|
|
2546
|
+
sandboxPlan.applied === true &&
|
|
2547
|
+
(sandboxPlan.backend === "linux-bwrap-workspace" ||
|
|
2548
|
+
sandboxPlan.backend === "linux-bwrap") &&
|
|
2549
|
+
sandboxPlan.postSpawn.required === false
|
|
2550
|
+
) {
|
|
2551
|
+
// child_process.spawn() has synchronously duplicated every stdio entry
|
|
2552
|
+
// into the launched bwrap process before returning. Both the generic
|
|
2553
|
+
// workspace backend and direct Plugin-bin bwrap backend are complete
|
|
2554
|
+
// pre-spawn plans with no parent-side enforcement handle. Retaining
|
|
2555
|
+
// their pinned mount/seccomp/supervisor descriptors until a long-lived
|
|
2556
|
+
// process exits would turn each child into an avoidable FD leak.
|
|
2557
|
+
cleanupSandbox();
|
|
2558
|
+
}
|
|
1610
2559
|
try {
|
|
1611
2560
|
this._runPostSpawnSandbox(proc, sandboxPlan, auditEntry);
|
|
1612
2561
|
auditEntry.pid = proc.pid;
|
|
@@ -1830,9 +2779,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1830
2779
|
|
|
1831
2780
|
/**
|
|
1832
2781
|
* Route a node-pty session through the same provenance and credential
|
|
1833
|
-
* boundary as child_process execution.
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
2782
|
+
* boundary as child_process execution. Policy-free sessions retain native
|
|
2783
|
+
* node-pty semantics. A policy-bearing Linux session uses node-pty only to
|
|
2784
|
+
* allocate a dedicated terminal; child_process then duplicates its slave
|
|
2785
|
+
* together with the descriptor-pinned generic bwrap plan.
|
|
1836
2786
|
*/
|
|
1837
2787
|
spawnPty(ptyModule, command, args = [], options = {}) {
|
|
1838
2788
|
if (!ptyModule || typeof ptyModule.spawn !== "function") {
|
|
@@ -1874,6 +2824,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1874
2824
|
command,
|
|
1875
2825
|
args,
|
|
1876
2826
|
sync: false,
|
|
2827
|
+
pty: true,
|
|
1877
2828
|
});
|
|
1878
2829
|
auditEntry.sandboxRequired = [...sandboxPolicy.requiredBoundaries];
|
|
1879
2830
|
} catch (sandboxPolicyError) {
|
|
@@ -1895,27 +2846,19 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1895
2846
|
error.auditEntry = auditEntry;
|
|
1896
2847
|
throw error;
|
|
1897
2848
|
}
|
|
1898
|
-
if (sandboxPolicy.requiredBoundaries.length > 0) {
|
|
1899
|
-
const sandboxError = this._sandboxBoundaryError(
|
|
1900
|
-
"required_boundaries_unsatisfied",
|
|
1901
|
-
`Native PTY host cannot satisfy required boundaries: ${sandboxPolicy.requiredBoundaries.join(", ")}`,
|
|
1902
|
-
{
|
|
1903
|
-
requiredBoundaries: sandboxPolicy.requiredBoundaries,
|
|
1904
|
-
missingBoundaries: sandboxPolicy.requiredBoundaries,
|
|
1905
|
-
},
|
|
1906
|
-
);
|
|
1907
|
-
this._recordSandboxDenial(auditEntry, sandboxError, startTime);
|
|
1908
|
-
sandboxError.auditEntry = auditEntry;
|
|
1909
|
-
throw sandboxError;
|
|
1910
|
-
}
|
|
1911
2849
|
|
|
1912
2850
|
const spawnOptions = { ...options, args: [...auditEntry.args] };
|
|
1913
2851
|
this._stripSandboxControlOptions(spawnOptions);
|
|
1914
2852
|
this._stripPluginControlOptions(spawnOptions);
|
|
1915
|
-
delete spawnOptions.origin;
|
|
1916
|
-
delete spawnOptions.policy;
|
|
1917
|
-
delete spawnOptions.scope;
|
|
1918
2853
|
try {
|
|
2854
|
+
const terminalName =
|
|
2855
|
+
typeof options.name === "string" && options.name
|
|
2856
|
+
? options.name
|
|
2857
|
+
: "xterm";
|
|
2858
|
+
spawnOptions.env = {
|
|
2859
|
+
...(spawnOptions.env || process.env),
|
|
2860
|
+
TERM: terminalName,
|
|
2861
|
+
};
|
|
1919
2862
|
if (this._credentialBoundaryEnabled()) {
|
|
1920
2863
|
spawnOptions.credentialContext = this._createCredentialContext(
|
|
1921
2864
|
command,
|
|
@@ -1937,7 +2880,170 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1937
2880
|
}
|
|
1938
2881
|
const filteredArgs = spawnOptions.args || [];
|
|
1939
2882
|
delete spawnOptions.args;
|
|
1940
|
-
|
|
2883
|
+
|
|
2884
|
+
if (sandboxPolicy.requiredBoundaries.length === 0) {
|
|
2885
|
+
delete spawnOptions.origin;
|
|
2886
|
+
delete spawnOptions.policy;
|
|
2887
|
+
delete spawnOptions.scope;
|
|
2888
|
+
const proc = ptyModule.spawn(command, filteredArgs, spawnOptions);
|
|
2889
|
+
auditEntry.pid = proc?.pid ?? null;
|
|
2890
|
+
auditEntry.endTime = Date.now();
|
|
2891
|
+
auditEntry.durationMs = auditEntry.endTime - startTime;
|
|
2892
|
+
this._recordAudit(auditEntry);
|
|
2893
|
+
this._writeRplEntry(auditEntry, "started");
|
|
2894
|
+
return proc;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
let sandboxPlan;
|
|
2898
|
+
try {
|
|
2899
|
+
sandboxPlan = this._prepareSandboxPlan(
|
|
2900
|
+
command,
|
|
2901
|
+
filteredArgs,
|
|
2902
|
+
spawnOptions,
|
|
2903
|
+
{
|
|
2904
|
+
pty: true,
|
|
2905
|
+
sandboxPolicy,
|
|
2906
|
+
},
|
|
2907
|
+
);
|
|
2908
|
+
if (
|
|
2909
|
+
sandboxPlan.applied !== true ||
|
|
2910
|
+
sandboxPlan.backend !== "linux-bwrap-workspace" ||
|
|
2911
|
+
sandboxPlan.ptyPolicy?.mode !== "dedicated-controlling-terminal"
|
|
2912
|
+
) {
|
|
2913
|
+
throw this._sandboxBoundaryError(
|
|
2914
|
+
"required_boundaries_unsatisfied",
|
|
2915
|
+
`PTY backend ${sandboxPlan.backend || sandboxPlan.candidateBackend || "unavailable"} cannot satisfy the dedicated terminal contract`,
|
|
2916
|
+
{
|
|
2917
|
+
requiredBoundaries: sandboxPolicy.requiredBoundaries,
|
|
2918
|
+
actualGuarantees: sandboxPlan.guarantees,
|
|
2919
|
+
missingBoundaries: sandboxPolicy.requiredBoundaries.filter(
|
|
2920
|
+
(boundary) => !sandboxPlan.guarantees.includes(boundary),
|
|
2921
|
+
),
|
|
2922
|
+
sandboxBackend: sandboxPlan.backend,
|
|
2923
|
+
sandboxCandidateBackend: sandboxPlan.candidateBackend,
|
|
2924
|
+
sandboxRuntimeProbe: sandboxPlan.runtimeProbe,
|
|
2925
|
+
sandboxPolicyAttested: sandboxPlan.policyAttested,
|
|
2926
|
+
sandboxPolicyDigest: sandboxPlan.policyDigest,
|
|
2927
|
+
},
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
} catch (sandboxError) {
|
|
2931
|
+
sandboxPlan?.cleanup?.();
|
|
2932
|
+
this._recordSandboxDenial(auditEntry, sandboxError, startTime);
|
|
2933
|
+
sandboxError.auditEntry = auditEntry;
|
|
2934
|
+
throw sandboxError;
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
let terminal;
|
|
2938
|
+
let slaveFd = null;
|
|
2939
|
+
let child;
|
|
2940
|
+
let proc;
|
|
2941
|
+
let terminalReleased = false;
|
|
2942
|
+
const releaseTerminal = () => {
|
|
2943
|
+
if (terminalReleased) return;
|
|
2944
|
+
terminalReleased = true;
|
|
2945
|
+
try {
|
|
2946
|
+
this._ptyAdapter.releaseTerminal(terminal);
|
|
2947
|
+
} catch {
|
|
2948
|
+
// The sandbox process exit remains authoritative.
|
|
2949
|
+
}
|
|
2950
|
+
};
|
|
2951
|
+
try {
|
|
2952
|
+
terminal = this._ptyAdapter.allocate(ptyModule, {
|
|
2953
|
+
cols: options.cols,
|
|
2954
|
+
rows: options.rows,
|
|
2955
|
+
encoding: options.encoding,
|
|
2956
|
+
});
|
|
2957
|
+
slaveFd = this._ptyAdapter.openBlockingSlave(terminal);
|
|
2958
|
+
const nativeSpawnFn = this._native?.spawn || nativeSpawn;
|
|
2959
|
+
child = nativeSpawnFn(sandboxPlan.command, sandboxPlan.args, {
|
|
2960
|
+
...sandboxPlan.options,
|
|
2961
|
+
stdio: [
|
|
2962
|
+
slaveFd,
|
|
2963
|
+
slaveFd,
|
|
2964
|
+
slaveFd,
|
|
2965
|
+
...sandboxPlan.options.stdio.slice(3),
|
|
2966
|
+
],
|
|
2967
|
+
});
|
|
2968
|
+
proc = wrapSandboxedPty(terminal, child, command, this._ptyAdapter);
|
|
2969
|
+
} catch (ptyError) {
|
|
2970
|
+
if (child && typeof child.kill === "function") {
|
|
2971
|
+
try {
|
|
2972
|
+
child.kill();
|
|
2973
|
+
} catch {
|
|
2974
|
+
// Continue fail-closed cleanup after a partial child launch.
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
if (Number.isInteger(slaveFd)) {
|
|
2978
|
+
try {
|
|
2979
|
+
this._ptyAdapter.closeFd(slaveFd);
|
|
2980
|
+
} catch {
|
|
2981
|
+
// Preserve the allocation/spawn failure.
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
sandboxPlan.cleanup?.();
|
|
2985
|
+
releaseTerminal();
|
|
2986
|
+
const sandboxError = this._sandboxBoundaryError(
|
|
2987
|
+
"pty_allocation_or_spawn_failed",
|
|
2988
|
+
`Strong Linux PTY launch failed: ${ptyError.message}`,
|
|
2989
|
+
{
|
|
2990
|
+
requiredBoundaries: sandboxPolicy.requiredBoundaries,
|
|
2991
|
+
actualGuarantees: [],
|
|
2992
|
+
missingBoundaries: sandboxPolicy.requiredBoundaries,
|
|
2993
|
+
sandboxBackend: sandboxPlan.backend,
|
|
2994
|
+
sandboxRuntimeProbe: sandboxPlan.runtimeProbe,
|
|
2995
|
+
sandboxPolicyAttested: sandboxPlan.policyAttested,
|
|
2996
|
+
sandboxPolicyDigest: sandboxPlan.policyDigest,
|
|
2997
|
+
},
|
|
2998
|
+
);
|
|
2999
|
+
this._recordSandboxDenial(auditEntry, sandboxError, startTime);
|
|
3000
|
+
sandboxError.auditEntry = auditEntry;
|
|
3001
|
+
throw sandboxError;
|
|
3002
|
+
}
|
|
3003
|
+
try {
|
|
3004
|
+
this._ptyAdapter.closeFd(slaveFd);
|
|
3005
|
+
} catch {
|
|
3006
|
+
try {
|
|
3007
|
+
child.kill();
|
|
3008
|
+
} catch {
|
|
3009
|
+
// Continue fail-closed cleanup even if the partial child already died.
|
|
3010
|
+
}
|
|
3011
|
+
sandboxPlan.cleanup?.();
|
|
3012
|
+
releaseTerminal();
|
|
3013
|
+
const sandboxError = this._sandboxBoundaryError(
|
|
3014
|
+
"pty_parent_slave_cleanup_failed",
|
|
3015
|
+
"Strong Linux PTY could not close its parent-side slave descriptor",
|
|
3016
|
+
{
|
|
3017
|
+
requiredBoundaries: sandboxPolicy.requiredBoundaries,
|
|
3018
|
+
missingBoundaries: sandboxPolicy.requiredBoundaries,
|
|
3019
|
+
sandboxBackend: sandboxPlan.backend,
|
|
3020
|
+
},
|
|
3021
|
+
);
|
|
3022
|
+
this._recordSandboxDenial(auditEntry, sandboxError, startTime);
|
|
3023
|
+
sandboxError.auditEntry = auditEntry;
|
|
3024
|
+
throw sandboxError;
|
|
3025
|
+
}
|
|
3026
|
+
sandboxPlan.cleanup?.();
|
|
3027
|
+
this._applySandboxAudit(auditEntry, sandboxPlan, true);
|
|
3028
|
+
this._stats.sandboxed++;
|
|
3029
|
+
|
|
3030
|
+
child.once("exit", (code, signal) => {
|
|
3031
|
+
releaseTerminal();
|
|
3032
|
+
auditEntry.exitCode = code;
|
|
3033
|
+
auditEntry.signal = signal;
|
|
3034
|
+
auditEntry.endTime = Date.now();
|
|
3035
|
+
auditEntry.durationMs = auditEntry.endTime - startTime;
|
|
3036
|
+
this._writeRplEntry(auditEntry, "completed");
|
|
3037
|
+
this.emit("exit", auditEntry);
|
|
3038
|
+
});
|
|
3039
|
+
child.once("error", (error) => {
|
|
3040
|
+
releaseTerminal();
|
|
3041
|
+
auditEntry.error = error.message;
|
|
3042
|
+
auditEntry.endTime = Date.now();
|
|
3043
|
+
auditEntry.durationMs = auditEntry.endTime - startTime;
|
|
3044
|
+
this._writeRplEntry(auditEntry, "error", error);
|
|
3045
|
+
if (this.listenerCount("error") > 0) this.emit("error", auditEntry);
|
|
3046
|
+
});
|
|
1941
3047
|
auditEntry.pid = proc?.pid ?? null;
|
|
1942
3048
|
auditEntry.endTime = Date.now();
|
|
1943
3049
|
auditEntry.durationMs = auditEntry.endTime - startTime;
|
|
@@ -1945,6 +3051,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1945
3051
|
this._writeRplEntry(auditEntry, "started");
|
|
1946
3052
|
return proc;
|
|
1947
3053
|
} catch (error) {
|
|
3054
|
+
if (error.auditEntry === auditEntry) throw error;
|
|
1948
3055
|
auditEntry.error = error.message;
|
|
1949
3056
|
auditEntry.endTime = Date.now();
|
|
1950
3057
|
auditEntry.durationMs = auditEntry.endTime - startTime;
|