chainlesschain 0.162.184 → 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-DzAl5fGx.js → AIOps-Co4ipAKx.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DAHtMGeC.js → ActionButton-BpsoDrDq.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BHQaexv5.js → Analytics-DMUqeBik.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Phz_pD0n.js → AppLayout-C6fD_dXr.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DHt47kCA.js → Artifacts-DW0YlQlV.js} +1 -1
- package/src/assets/web-panel/assets/{Audit--Y_8FULl.js → Audit-DXtHiiFK.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-N1zchH7D.js → BackgroundAgents-BgmF62U-.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B_BoyN30.js → Backup-D8vhVLQC.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Gl4uQHcT.js → BaseInput-D7nCtY2X.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Cg-eIyAZ.js → Chat-CBZLEV2G.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CtDb-2Qu.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-D0MGR2wA.js → Checkbox-DJLkeD4M.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BdObJEFn.js → Codegen-Cs6xO8Fd.js} +1 -1
- package/src/assets/web-panel/assets/{Col--ghxXT3z.js → Col-Dtx9wxfT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BUFeXOqz.js → Community-DiigJg4n.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DhQO2_Mr.js → Compact-B67oMDDL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-3HoTqKYd.js → Compliance-BBDl-Jos.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-B3zmO4VW.js → Cowork-Ba98hSsa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CJDrZOcc.js → Cron-DBWQZIz7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-SMl15obd.js → Crosschain-OcrKzxAt.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CgTrTWrl.js → DID-mW2eSbjM.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-dXFKaKol.js → Dashboard-BmsDPmKJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-qQHEhCvI.js → Dropdown-CVzmCyRx.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C_ej0MWH.js → EmailListRenderer-vVgqKzhh.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D6ajoE7I.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Cio2y49h.js → Federation-ZGomhjxr.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-ClJz1cYp.js → FormItemContext-DyaknbLr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Dr_8Maca.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CevLwp5u.js → Git-DQOJIYkG.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CW9YyQUY.js → Governance-CMiY-EEp.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CQqeAyER.js → Inference-D6IXuucf.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-cuGVxHrw.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DIuHri5d.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-CY1VhFGx.js → Marketplace-WhYuhBxm.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BBAbi1vt.js → McpTools-DTeO28xy.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BWYGHxJA.js → Memory-MSHzPSE-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CPIFkW7A.js → MobileBridge-Cyqs3J3W.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DQyWxCho.js → MobileProjects-7IN2hWHg.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Bo3GnQbx.js → Mtc-MWBnezNM.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-C8-c4rSW.js → MtcAudit-DQu35Nu-.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-ChqMTtoq.js → Multisig-CQS_M-DP.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C48TyhvI.js → NLProgramming-WD16vQvY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-zogUFWxp.js → Notes-DqsiSwOq.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-v-K9XpSB.js → NotificationSettings-D3LoxO-P.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-C5jPoTd6.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Boi1dQfR.js → Organization-48aMBJso.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DM5YWwgA.js → Overflow-j-a2XkhR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-C57LKpKK.js → P2P-BYpBNYnh.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BFkPqu6Q.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D6fB9YSp.js → Permissions-ZRXGXDFb.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Bp7EefEo.js → PersonalDataHub-6OWUPHyL.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-BVln3hZf.js → Pipeline-FcjlDK9E.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bf8P3nF6.js → Privacy-vD_6Tldp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DM6wA2E2.js → ProjectInit-B_CHOLXp.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CGbc5Iho.js → ProjectSettings-rEiqQ2da.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-D0p9EOxm.js → Projects-Bg8obRAM.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B2kJcxS6.js → Providers-Dgtt97B4.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cxu7dXYy.js → QrScannerModal-DaGVylZO.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DTlrTPms.js → QuickAsk-DP2_2x3u.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cm3X-cCj.js → Recommend-D29yj4ki.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DNb2kjvt.js → RemoteSession-Ue2wnBLp.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CZUFFjc1.js → Reputation-CrxIHqeC.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DAPgloxA.js → Row-BgSzJPCn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-dsx9S793.js → RssFeed-AwhYOrP9.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DnnC8kz-.js → Search-D2shpEcm.js} +1 -1
- package/src/assets/web-panel/assets/{Security-VuZknRMK.js → Security-BtKzhTbT.js} +4 -4
- package/src/assets/web-panel/assets/{Services-B3XNX8Vx.js → Services-BY0DsUSm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Dl1nVtCj.js → Skeleton-B3Sb0UI9.js} +1 -1
- package/src/assets/web-panel/assets/Skills-Ne5w0bDs.js +1 -0
- package/src/assets/web-panel/assets/{Sla-HzU56n1z.js → Sla-C8VkaP4T.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CRle7Jl2.js → SpeechSettings-BQdIEYP9.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DWadz7Sl.js → SyncSettings-CwFafrUg.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-yJuKoDlR.js +1 -0
- package/src/assets/web-panel/assets/{Templates-BBAgvrlM.js → Templates-DYn9qCmx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BsbbvGN8.js → Tenant-DkKReHvn.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-uscpZI4q.js → Terminal-r6bd-owt.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-mTyjNE6r.js → TimelineRenderer-Mq2juUC3.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bz97oBgs.js → Tokens-BnMw7MRi.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BTRKC140.js → Trigger-CTzKp4o9.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B4A_KuyX.js → Trust-BBrSZm9U.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DES9TmLU.js → UkeySign-D9fDvEMq.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-ClXs7Qxp.js → VideoEditing-DUi5ve-0.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-IzZqFz-7.js → Wallet-C8Fi1P8_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BtOP04g_.js → WebAuthn-s1LppYi2.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-_0cqnopY.js → WorkflowEditor-DJL2C4lj.js} +1 -1
- package/src/assets/web-panel/assets/{chat-ax7193uU.js → chat-D3F2ndOB.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Bu4WGJah.js → colors-B7739-e3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BX1dlTW0.js → compact-item-CcJbFTF4.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CktA_Dj7.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-D5wd817l.js → hasIn-6YEXfIik.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3HmzCLt.js → index-4h3lpWJ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjEJjl5C.js → index-BIID4XcP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQtoRyTO.js → index-BOHUo3aM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP8ELJBc.js → index-BYYSLEjn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfzxR61U.js → index-Bh4hcNWW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmPJ6T6F.js → index-BhIKnQAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8DkWyNr.js → index-Bru_kpZl.js} +1 -1
- package/src/assets/web-panel/assets/{index-iHcPtQ2e.js → index-BsdSnHQE.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9op8poE.js → index-BxSqVMGh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGdL3sG.js → index-C3ZA_h1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmVUeWUk.js → index-C3pctdCb.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cv7bVWJ5.js → index-CRuoRapN.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-o4Rwjk.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-Ci3y1ZAv.js → index-CxradkS6.js} +19 -19
- package/src/assets/web-panel/assets/{index-CUForzsG.js → index-D0K_rFC1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLLsUNDc.js → index-D0zdPyTD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeVZaJSC.js → index-D3haorUt.js} +1 -1
- package/src/assets/web-panel/assets/{index-o14NNnxw.js → index-D6Ij_2uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db3CpWTr.js → index-DDRlBaDt.js} +1 -1
- package/src/assets/web-panel/assets/{index-h3KJZr95.js → index-DGsE7MQz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAtBNfkr.js → index-DHvxXXNA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DF1TysIF.js → index-DV11jpeO.js} +1 -1
- package/src/assets/web-panel/assets/{index-5PYYkr8S.js → index-DXdnfDVK.js} +1 -1
- package/src/assets/web-panel/assets/{index-tYPSfGbd.js → index-D_75jYWk.js} +1 -1
- package/src/assets/web-panel/assets/{index-jzlzm4VK.js → index-DjI9MkId.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_cycOET.js → index-DlSvpNMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dzdr0zDf.js → index-Dox44j29.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYRE0NJA.js → index-DpEKmjoa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad2CYeH.js → index-Dsy7_x4s.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8tLR2TG.js → index-Gxw09e9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-ytp2H5Zx.js → index-LfRlSmqe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkD1K-fF.js → index-Nz-MUHD1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRfXoRuh.js → index-b9cuvFEv.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYo9S5OT.js → index-eR6H6NR7.js} +1 -1
- package/src/assets/web-panel/assets/{index-A4uBPQCz.js → index-fWgUQZ9d.js} +1 -1
- package/src/assets/web-panel/assets/{index-PBLzwYnu.js → index-mfWQRSLn.js} +1 -1
- package/src/assets/web-panel/assets/{index-bFCaM7Eq.js → index-yOZYSNmG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D2Iut83B.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BuaJVdK9.js → motion-rYJN_vzf.js} +1 -1
- package/src/assets/web-panel/assets/{move-CaEuM92y.js → move-DnDK2gTP.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-wuKNBwys.js → mtc-parser-CKnXy7ES.js} +1 -1
- package/src/assets/web-panel/assets/{omit-7a3eklaq.js → omit-3nRc3hWT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Dn4z7KvJ.js → pickAttrs-Ddjj3sLu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DVn_qT4-.js → placementArrow-CbsbphQY.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Dodz1OMQ.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CxoGskbp.js → slide-D4pvdFj0.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DBh_epQH.js → statusUtils-C2W-vY-S.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-RXQ4gG5P.js → styleChecker-Cbq5150d.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-1Wxm0HEG.js → useFlexGapSupport-BA2B7R82.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CI_ySNxJ.js → useFs-CLrwg3xg.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C6973rZP.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CalynwKr.js → vnode-8BDGy-YU.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CM1cd-Q0.js → zoom-B2SzFprl.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +13 -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/lib/background-agent-supervisor.js +46 -0
- package/src/lib/event-runtime-host.js +12 -10
- package/src/lib/hook-runner.cjs +7 -1
- package/src/lib/hook-runner.js +2 -0
- package/src/lib/hooks-v2-runtime.js +94 -6
- package/src/lib/hooks-v2-workspace-context.js +258 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +1 -4
- package/src/lib/plugin-runtime/bin.js +20 -4
- package/src/lib/plugin-runtime/sandbox-policy.js +1 -4
- package/src/lib/process-execution-broker/index.js +706 -40
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +212 -2
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +124 -6
- package/src/lib/process-execution-broker/platform-sandbox.js +6 -2
- package/src/repl/agent-repl.js +8 -0
- package/src/runtime/agent-core.js +272 -38
- package/src/runtime/headless-runner.js +11 -1
- package/src/runtime/headless-stream.js +8 -0
- package/src/workers/background-agent-worker.js +5 -11
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-R91Qw5JA.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +0 -1
- package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-x877iKKS.js +0 -1
- package/src/assets/web-panel/assets/index-D7pfPouB.js +0 -1
- package/src/assets/web-panel/assets/index-e4i-VLBd.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);
|
|
@@ -56,7 +57,6 @@ const SUPPORTED_SANDBOX_PROFILES = new Set([
|
|
|
56
57
|
|
|
57
58
|
// 延迟导入避免循环依赖
|
|
58
59
|
let _traceCtx = null;
|
|
59
|
-
let _hooksV2 = null;
|
|
60
60
|
const _ipcBus = null;
|
|
61
61
|
|
|
62
62
|
function getTraceCtx() {
|
|
@@ -74,15 +74,428 @@ function getRpl() {
|
|
|
74
74
|
return runtimeProvenanceLedger;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function
|
|
78
|
-
|
|
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) => {
|
|
79
82
|
try {
|
|
80
|
-
|
|
83
|
+
stream?.destroy?.();
|
|
81
84
|
} catch {
|
|
82
|
-
|
|
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;
|
|
83
114
|
}
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
};
|
|
86
499
|
}
|
|
87
500
|
|
|
88
501
|
class ProcessExecutionBroker extends EventEmitter {
|
|
@@ -122,6 +535,8 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
122
535
|
applySandbox: _applySandbox,
|
|
123
536
|
postSpawnSandbox: _postSpawnSandbox,
|
|
124
537
|
};
|
|
538
|
+
this._ptyAdapter = createNativePtyAdapter();
|
|
539
|
+
this._hooksEventSink = null;
|
|
125
540
|
|
|
126
541
|
this._ensureLogDir();
|
|
127
542
|
this._loadPermissions();
|
|
@@ -277,6 +692,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
277
692
|
cwd: options.cwd || process.cwd(),
|
|
278
693
|
shell: options.shell,
|
|
279
694
|
sync: launch?.sync === true,
|
|
695
|
+
pty: launch?.pty === true,
|
|
280
696
|
stdio: options.stdio,
|
|
281
697
|
requiredBoundaries,
|
|
282
698
|
});
|
|
@@ -435,7 +851,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
435
851
|
args = [],
|
|
436
852
|
options = {},
|
|
437
853
|
trustedWorkspaceRoot = process.cwd(),
|
|
438
|
-
{ sync = false } = {},
|
|
854
|
+
{ sync = false, pty = false } = {},
|
|
439
855
|
) {
|
|
440
856
|
const requiredBoundaries = [
|
|
441
857
|
...new Set([
|
|
@@ -465,6 +881,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
465
881
|
workspaceRoot: trustedWorkspaceRoot,
|
|
466
882
|
shell: options.shell,
|
|
467
883
|
sync,
|
|
884
|
+
pty,
|
|
468
885
|
stdio: options.stdio,
|
|
469
886
|
requiredBoundaries,
|
|
470
887
|
detached: options.detached,
|
|
@@ -707,6 +1124,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
707
1124
|
let genericWorkspaceProbe = false;
|
|
708
1125
|
let sanitizedFilesystemPolicy = null;
|
|
709
1126
|
let sanitizedNetworkPolicy = null;
|
|
1127
|
+
let sanitizedPtyPolicy = null;
|
|
710
1128
|
if (plan.runtimeProbe !== null && plan.runtimeProbe !== undefined) {
|
|
711
1129
|
if (
|
|
712
1130
|
typeof plan.runtimeProbe !== "object" ||
|
|
@@ -1085,6 +1503,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1085
1503
|
"contractDigest",
|
|
1086
1504
|
"policyDigest",
|
|
1087
1505
|
"mountTopologyDigest",
|
|
1506
|
+
"sourceMountSetDigest",
|
|
1088
1507
|
"emptyRoot",
|
|
1089
1508
|
"undeclaredRootReadOnly",
|
|
1090
1509
|
"workspaceReadWrite",
|
|
@@ -1099,11 +1518,13 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1099
1518
|
"socketCreationDenied",
|
|
1100
1519
|
"descriptorMounts",
|
|
1101
1520
|
"mountTopologyAtomic",
|
|
1521
|
+
"sourceMountPropagationPrivateAtAttestation",
|
|
1102
1522
|
];
|
|
1103
1523
|
for (const field of [
|
|
1104
1524
|
"contractDigest",
|
|
1105
1525
|
"policyDigest",
|
|
1106
1526
|
"mountTopologyDigest",
|
|
1527
|
+
"sourceMountSetDigest",
|
|
1107
1528
|
]) {
|
|
1108
1529
|
if (
|
|
1109
1530
|
plan.runtimeProbe[field] !== undefined &&
|
|
@@ -1116,7 +1537,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1116
1537
|
);
|
|
1117
1538
|
}
|
|
1118
1539
|
}
|
|
1119
|
-
for (const field of genericWorkspaceEvidenceFields.slice(
|
|
1540
|
+
for (const field of genericWorkspaceEvidenceFields.slice(4)) {
|
|
1120
1541
|
if (
|
|
1121
1542
|
plan.runtimeProbe[field] !== undefined &&
|
|
1122
1543
|
typeof plan.runtimeProbe[field] !== "boolean"
|
|
@@ -1143,6 +1564,30 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1143
1564
|
if (genericWorkspaceKind) {
|
|
1144
1565
|
const filesystemPolicy = plan.filesystemPolicy;
|
|
1145
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");
|
|
1146
1591
|
const workspaceRoot = filesystemPolicy?.workspaceRoot;
|
|
1147
1592
|
const workingDirectory = filesystemPolicy?.workingDirectory;
|
|
1148
1593
|
const workspaceRelative =
|
|
@@ -1172,6 +1617,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1172
1617
|
plan.runtimeProbe.mountTopologyAtomic === false &&
|
|
1173
1618
|
plan.runtimeProbe.mountTopologyDigest ===
|
|
1174
1619
|
filesystemPolicy?.mountTopologyDigest &&
|
|
1620
|
+
plan.runtimeProbe.sourceMountSetDigest ===
|
|
1621
|
+
filesystemPolicy?.sourceMountSetDigest &&
|
|
1622
|
+
plan.runtimeProbe.sourceMountPropagationPrivateAtAttestation ===
|
|
1623
|
+
true &&
|
|
1175
1624
|
[
|
|
1176
1625
|
"emptyRoot",
|
|
1177
1626
|
"undeclaredRootReadOnly",
|
|
@@ -1208,6 +1657,9 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1208
1657
|
"no-strict-descendants-or-forbidden-root-aliases-at-attestation" &&
|
|
1209
1658
|
filesystemPolicy.mountTopologySource === "proc-self-mountinfo" &&
|
|
1210
1659
|
/^[a-f0-9]{64}$/.test(filesystemPolicy.mountTopologyDigest || "") &&
|
|
1660
|
+
/^[a-f0-9]{64}$/.test(filesystemPolicy.sourceMountSetDigest || "") &&
|
|
1661
|
+
filesystemPolicy.sourceMountPropagationPrivateAtAttestation ===
|
|
1662
|
+
true &&
|
|
1211
1663
|
filesystemPolicy.mountTopologyAtomic === false &&
|
|
1212
1664
|
Array.isArray(filesystemPolicy.anonymousWritablePaths) &&
|
|
1213
1665
|
filesystemPolicy.anonymousWritablePaths.length === 5 &&
|
|
@@ -1227,7 +1679,8 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1227
1679
|
plan.args.includes("--remount-ro") &&
|
|
1228
1680
|
plan.args.includes("--unshare-net") &&
|
|
1229
1681
|
plan.args.includes("--seccomp") &&
|
|
1230
|
-
!plan.args.includes("--ro-bind")
|
|
1682
|
+
!plan.args.includes("--ro-bind") &&
|
|
1683
|
+
ptyPlanValid;
|
|
1231
1684
|
if (!genericWorkspaceProbe) {
|
|
1232
1685
|
throw this._sandboxError(
|
|
1233
1686
|
"invalid_sandbox_plan",
|
|
@@ -1252,6 +1705,9 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1252
1705
|
workspaceMountTopology: filesystemPolicy.workspaceMountTopology,
|
|
1253
1706
|
mountTopologySource: filesystemPolicy.mountTopologySource,
|
|
1254
1707
|
mountTopologyDigest: filesystemPolicy.mountTopologyDigest,
|
|
1708
|
+
sourceMountSetDigest: filesystemPolicy.sourceMountSetDigest,
|
|
1709
|
+
sourceMountPropagationPrivateAtAttestation:
|
|
1710
|
+
filesystemPolicy.sourceMountPropagationPrivateAtAttestation,
|
|
1255
1711
|
mountTopologyAtomic: filesystemPolicy.mountTopologyAtomic,
|
|
1256
1712
|
});
|
|
1257
1713
|
sanitizedNetworkPolicy = Object.freeze({
|
|
@@ -1259,6 +1715,20 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1259
1715
|
namespaceIdentityChanged: networkPolicy.namespaceIdentityChanged,
|
|
1260
1716
|
seccomp: networkPolicy.seccomp,
|
|
1261
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;
|
|
1262
1732
|
}
|
|
1263
1733
|
runtimeProbe = {
|
|
1264
1734
|
kind: plan.runtimeProbe.kind,
|
|
@@ -1365,6 +1835,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1365
1835
|
? {
|
|
1366
1836
|
filesystemPolicy: sanitizedFilesystemPolicy,
|
|
1367
1837
|
networkPolicy: sanitizedNetworkPolicy,
|
|
1838
|
+
ptyPolicy: sanitizedPtyPolicy,
|
|
1368
1839
|
}
|
|
1369
1840
|
: {}),
|
|
1370
1841
|
postSpawn: { ...postSpawn },
|
|
@@ -1378,9 +1849,18 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1378
1849
|
(boundary) => !guaranteed.has(boundary),
|
|
1379
1850
|
);
|
|
1380
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
|
+
: "";
|
|
1381
1861
|
throw this._sandboxBoundaryError(
|
|
1382
1862
|
"required_boundaries_unsatisfied",
|
|
1383
|
-
`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}`,
|
|
1384
1864
|
{
|
|
1385
1865
|
requiredBoundaries,
|
|
1386
1866
|
actualGuarantees,
|
|
@@ -1404,7 +1884,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1404
1884
|
command,
|
|
1405
1885
|
args,
|
|
1406
1886
|
options,
|
|
1407
|
-
{ sync = false, sandboxPolicy = {} } = {},
|
|
1887
|
+
{ sync = false, pty = false, sandboxPolicy = {} } = {},
|
|
1408
1888
|
) {
|
|
1409
1889
|
const strict = this._sandboxStrictEnabled();
|
|
1410
1890
|
const requiredBoundaries = sandboxPolicy.requiredBoundaries || [];
|
|
@@ -1443,6 +1923,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1443
1923
|
profile,
|
|
1444
1924
|
requiredBoundaries: Object.freeze([...requiredBoundaries]),
|
|
1445
1925
|
sync,
|
|
1926
|
+
...(pty ? { pty: true } : {}),
|
|
1446
1927
|
...(sandboxPolicy.executionContract
|
|
1447
1928
|
? { executionContract: sandboxPolicy.executionContract }
|
|
1448
1929
|
: {}),
|
|
@@ -1544,6 +2025,9 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1544
2025
|
workspaceMountTopology: plan.filesystemPolicy.workspaceMountTopology,
|
|
1545
2026
|
mountTopologySource: plan.filesystemPolicy.mountTopologySource,
|
|
1546
2027
|
mountTopologyDigest: plan.filesystemPolicy.mountTopologyDigest,
|
|
2028
|
+
sourceMountSetDigest: plan.filesystemPolicy.sourceMountSetDigest,
|
|
2029
|
+
sourceMountPropagationPrivateAtAttestation:
|
|
2030
|
+
plan.filesystemPolicy.sourceMountPropagationPrivateAtAttestation,
|
|
1547
2031
|
mountTopologyAtomic: plan.filesystemPolicy.mountTopologyAtomic,
|
|
1548
2032
|
}
|
|
1549
2033
|
: null;
|
|
@@ -1554,6 +2038,21 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1554
2038
|
seccomp: plan.networkPolicy.seccomp,
|
|
1555
2039
|
}
|
|
1556
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;
|
|
1557
2056
|
auditEntry.sandboxCandidateReason = plan?.candidateBackend
|
|
1558
2057
|
? plan?.reason || null
|
|
1559
2058
|
: null;
|
|
@@ -1865,15 +2364,22 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1865
2364
|
}
|
|
1866
2365
|
|
|
1867
2366
|
_emitHooksEvent(event, data) {
|
|
1868
|
-
const hooks =
|
|
1869
|
-
if (!hooks
|
|
2367
|
+
const hooks = this._hooksEventSink;
|
|
2368
|
+
if (!hooks) return;
|
|
1870
2369
|
try {
|
|
1871
|
-
hooks.
|
|
2370
|
+
hooks.emit(event, data);
|
|
1872
2371
|
} catch {
|
|
1873
2372
|
// Hook reporting must not hide the process execution result.
|
|
1874
2373
|
}
|
|
1875
2374
|
}
|
|
1876
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
|
+
|
|
1877
2383
|
spawn(command, args, options = {}) {
|
|
1878
2384
|
const executionId = crypto.randomUUID();
|
|
1879
2385
|
const startTime = Date.now();
|
|
@@ -2038,14 +2544,16 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2038
2544
|
);
|
|
2039
2545
|
if (
|
|
2040
2546
|
sandboxPlan.applied === true &&
|
|
2041
|
-
sandboxPlan.backend === "linux-bwrap-workspace"
|
|
2547
|
+
(sandboxPlan.backend === "linux-bwrap-workspace" ||
|
|
2548
|
+
sandboxPlan.backend === "linux-bwrap") &&
|
|
2042
2549
|
sandboxPlan.postSpawn.required === false
|
|
2043
2550
|
) {
|
|
2044
2551
|
// child_process.spawn() has synchronously duplicated every stdio entry
|
|
2045
|
-
// into the launched bwrap process before returning.
|
|
2046
|
-
//
|
|
2047
|
-
//
|
|
2048
|
-
//
|
|
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.
|
|
2049
2557
|
cleanupSandbox();
|
|
2050
2558
|
}
|
|
2051
2559
|
try {
|
|
@@ -2271,9 +2779,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2271
2779
|
|
|
2272
2780
|
/**
|
|
2273
2781
|
* Route a node-pty session through the same provenance and credential
|
|
2274
|
-
* boundary as child_process execution.
|
|
2275
|
-
*
|
|
2276
|
-
*
|
|
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.
|
|
2277
2786
|
*/
|
|
2278
2787
|
spawnPty(ptyModule, command, args = [], options = {}) {
|
|
2279
2788
|
if (!ptyModule || typeof ptyModule.spawn !== "function") {
|
|
@@ -2315,6 +2824,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2315
2824
|
command,
|
|
2316
2825
|
args,
|
|
2317
2826
|
sync: false,
|
|
2827
|
+
pty: true,
|
|
2318
2828
|
});
|
|
2319
2829
|
auditEntry.sandboxRequired = [...sandboxPolicy.requiredBoundaries];
|
|
2320
2830
|
} catch (sandboxPolicyError) {
|
|
@@ -2336,27 +2846,19 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2336
2846
|
error.auditEntry = auditEntry;
|
|
2337
2847
|
throw error;
|
|
2338
2848
|
}
|
|
2339
|
-
if (sandboxPolicy.requiredBoundaries.length > 0) {
|
|
2340
|
-
const sandboxError = this._sandboxBoundaryError(
|
|
2341
|
-
"required_boundaries_unsatisfied",
|
|
2342
|
-
`Native PTY host cannot satisfy required boundaries: ${sandboxPolicy.requiredBoundaries.join(", ")}`,
|
|
2343
|
-
{
|
|
2344
|
-
requiredBoundaries: sandboxPolicy.requiredBoundaries,
|
|
2345
|
-
missingBoundaries: sandboxPolicy.requiredBoundaries,
|
|
2346
|
-
},
|
|
2347
|
-
);
|
|
2348
|
-
this._recordSandboxDenial(auditEntry, sandboxError, startTime);
|
|
2349
|
-
sandboxError.auditEntry = auditEntry;
|
|
2350
|
-
throw sandboxError;
|
|
2351
|
-
}
|
|
2352
2849
|
|
|
2353
2850
|
const spawnOptions = { ...options, args: [...auditEntry.args] };
|
|
2354
2851
|
this._stripSandboxControlOptions(spawnOptions);
|
|
2355
2852
|
this._stripPluginControlOptions(spawnOptions);
|
|
2356
|
-
delete spawnOptions.origin;
|
|
2357
|
-
delete spawnOptions.policy;
|
|
2358
|
-
delete spawnOptions.scope;
|
|
2359
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
|
+
};
|
|
2360
2862
|
if (this._credentialBoundaryEnabled()) {
|
|
2361
2863
|
spawnOptions.credentialContext = this._createCredentialContext(
|
|
2362
2864
|
command,
|
|
@@ -2378,7 +2880,170 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2378
2880
|
}
|
|
2379
2881
|
const filteredArgs = spawnOptions.args || [];
|
|
2380
2882
|
delete spawnOptions.args;
|
|
2381
|
-
|
|
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
|
+
});
|
|
2382
3047
|
auditEntry.pid = proc?.pid ?? null;
|
|
2383
3048
|
auditEntry.endTime = Date.now();
|
|
2384
3049
|
auditEntry.durationMs = auditEntry.endTime - startTime;
|
|
@@ -2386,6 +3051,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2386
3051
|
this._writeRplEntry(auditEntry, "started");
|
|
2387
3052
|
return proc;
|
|
2388
3053
|
} catch (error) {
|
|
3054
|
+
if (error.auditEntry === auditEntry) throw error;
|
|
2389
3055
|
auditEntry.error = error.message;
|
|
2390
3056
|
auditEntry.endTime = Date.now();
|
|
2391
3057
|
auditEntry.durationMs = auditEntry.endTime - startTime;
|