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
|
@@ -14,6 +14,7 @@ import { spawnSync as nativeSpawnSync } from "node:child_process";
|
|
|
14
14
|
import {
|
|
15
15
|
LINUX_GENERIC_BWRAP_BACKEND,
|
|
16
16
|
LINUX_GENERIC_CONTRACT_KIND,
|
|
17
|
+
LINUX_GENERIC_PTY_LAUNCHER_PATH,
|
|
17
18
|
planLinuxGenericBubblewrap,
|
|
18
19
|
} from "./linux-generic-bwrap.js";
|
|
19
20
|
|
|
@@ -345,8 +346,12 @@ function parseMountInfo(runtime) {
|
|
|
345
346
|
}
|
|
346
347
|
const root = decodeMountInfoPath(fields[3]);
|
|
347
348
|
const mountPoint = decodeMountInfoPath(fields[4]);
|
|
349
|
+
const optionalFields = fields.slice(6, separator);
|
|
348
350
|
const fsType = fields[separator + 1];
|
|
349
351
|
if (
|
|
352
|
+
!fields[5] ||
|
|
353
|
+
fields[5].includes("\0") ||
|
|
354
|
+
optionalFields.some((field) => !field || field.includes("\0")) ||
|
|
350
355
|
!fsType ||
|
|
351
356
|
fsType.includes("\0") ||
|
|
352
357
|
fields.slice(separator + 1).some((field) => !field)
|
|
@@ -361,6 +366,7 @@ function parseMountInfo(runtime) {
|
|
|
361
366
|
root,
|
|
362
367
|
mountPoint,
|
|
363
368
|
fsType,
|
|
369
|
+
optionalFields: Object.freeze(optionalFields),
|
|
364
370
|
lineDigest: sha256(line),
|
|
365
371
|
}),
|
|
366
372
|
);
|
|
@@ -399,7 +405,75 @@ function sameDirectoryIdentity(left, right) {
|
|
|
399
405
|
);
|
|
400
406
|
}
|
|
401
407
|
|
|
402
|
-
function
|
|
408
|
+
function bindSourceMountTopology(entries, bindSources) {
|
|
409
|
+
const normalizedSources = bindSources
|
|
410
|
+
.map((source) => ({
|
|
411
|
+
kind: source.kind,
|
|
412
|
+
source: source.source,
|
|
413
|
+
recursive: source.recursive === true,
|
|
414
|
+
}))
|
|
415
|
+
.sort(
|
|
416
|
+
(left, right) =>
|
|
417
|
+
left.source.localeCompare(right.source) ||
|
|
418
|
+
left.kind.localeCompare(right.kind),
|
|
419
|
+
);
|
|
420
|
+
const sourceKeys = new Set();
|
|
421
|
+
for (const source of normalizedSources) {
|
|
422
|
+
if (
|
|
423
|
+
typeof source.kind !== "string" ||
|
|
424
|
+
!source.kind ||
|
|
425
|
+
typeof source.source !== "string" ||
|
|
426
|
+
!path.posix.isAbsolute(source.source) ||
|
|
427
|
+
path.posix.normalize(source.source) !== source.source
|
|
428
|
+
) {
|
|
429
|
+
throw new Error("bind_source_mount_topology_invalid");
|
|
430
|
+
}
|
|
431
|
+
const key = `${source.kind}\0${source.source}`;
|
|
432
|
+
if (sourceKeys.has(key)) {
|
|
433
|
+
throw new Error("bind_source_mount_topology_duplicate");
|
|
434
|
+
}
|
|
435
|
+
sourceKeys.add(key);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
let importedMountEntryCount = 0;
|
|
439
|
+
const sourceMountBindings = normalizedSources.map((source) => {
|
|
440
|
+
const mountedBy = containingMount(entries, source.source);
|
|
441
|
+
const importedMounts = new Map([[mountedBy.mountId, mountedBy]]);
|
|
442
|
+
if (source.recursive) {
|
|
443
|
+
for (const entry of entries) {
|
|
444
|
+
if (isWithin(source.source, entry.mountPoint)) {
|
|
445
|
+
importedMounts.set(entry.mountId, entry);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const imported = [...importedMounts.values()];
|
|
450
|
+
if (imported.some((entry) => entry.optionalFields.length !== 0)) {
|
|
451
|
+
throw new Error("bind_source_mount_propagation_not_private");
|
|
452
|
+
}
|
|
453
|
+
importedMountEntryCount += imported.length;
|
|
454
|
+
return {
|
|
455
|
+
...source,
|
|
456
|
+
containingMount: mountedBy.lineDigest,
|
|
457
|
+
importedMountEntryDigests: imported
|
|
458
|
+
.map((entry) => entry.lineDigest)
|
|
459
|
+
.sort(),
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
sourceMountSetDigest: sha256(stableJson(normalizedSources)),
|
|
464
|
+
sourceMountCount: normalizedSources.length,
|
|
465
|
+
importedMountEntryCount,
|
|
466
|
+
sourceMountBindings,
|
|
467
|
+
sourceMountPropagationPrivateAtAttestation: true,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function workspaceMountTopology(
|
|
472
|
+
runtime,
|
|
473
|
+
workspaceRoot,
|
|
474
|
+
workspaceIdentity,
|
|
475
|
+
bindSources,
|
|
476
|
+
) {
|
|
403
477
|
const entries = parseMountInfo(runtime);
|
|
404
478
|
const descendants = entries.filter(
|
|
405
479
|
(entry) =>
|
|
@@ -498,6 +572,7 @@ function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
|
|
|
498
572
|
const lineage = entries.filter((entry) =>
|
|
499
573
|
isWithin(entry.mountPoint, workspaceRoot),
|
|
500
574
|
);
|
|
575
|
+
const bindSourceTopology = bindSourceMountTopology(entries, bindSources);
|
|
501
576
|
const binding = {
|
|
502
577
|
version: MOUNT_TOPOLOGY_VERSION,
|
|
503
578
|
source: "proc-self-mountinfo",
|
|
@@ -512,6 +587,7 @@ function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
|
|
|
512
587
|
forbiddenIdentityDigests: forbiddenIdentities
|
|
513
588
|
.map((entry) => sha256(stableJson(entry)))
|
|
514
589
|
.sort(),
|
|
590
|
+
...bindSourceTopology,
|
|
515
591
|
strictDescendantMountsAtAttestation: 0,
|
|
516
592
|
recursiveBind: true,
|
|
517
593
|
mountTopologyAtomic: false,
|
|
@@ -525,6 +601,11 @@ function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
|
|
|
525
601
|
filesystemEntryCount: binding.filesystemEntryDigests.length,
|
|
526
602
|
aliasCount: binding.aliasDigests.length,
|
|
527
603
|
forbiddenIdentityCount: binding.forbiddenIdentityDigests.length,
|
|
604
|
+
sourceMountSetDigest: binding.sourceMountSetDigest,
|
|
605
|
+
sourceMountCount: binding.sourceMountCount,
|
|
606
|
+
importedMountEntryCount: binding.importedMountEntryCount,
|
|
607
|
+
sourceMountPropagationPrivateAtAttestation:
|
|
608
|
+
binding.sourceMountPropagationPrivateAtAttestation,
|
|
528
609
|
strictDescendantMountsAtAttestation:
|
|
529
610
|
binding.strictDescendantMountsAtAttestation,
|
|
530
611
|
rootAliasAttested: true,
|
|
@@ -697,10 +778,20 @@ function createTrustedResources(
|
|
|
697
778
|
const cwdPins = openPair(contract.workingDirectory, {
|
|
698
779
|
directory: true,
|
|
699
780
|
});
|
|
700
|
-
|
|
781
|
+
// Fail before executing any helper when the writable recursive source is
|
|
782
|
+
// already propagation-capable. The complete exact source set is attested
|
|
783
|
+
// after the actual system and /etc descriptors have been acquired.
|
|
784
|
+
workspaceMountTopology(
|
|
701
785
|
runtime,
|
|
702
786
|
contract.workspaceRoot,
|
|
703
787
|
workspace.identity,
|
|
788
|
+
[
|
|
789
|
+
{
|
|
790
|
+
kind: "workspace",
|
|
791
|
+
source: contract.workspaceRoot,
|
|
792
|
+
recursive: true,
|
|
793
|
+
},
|
|
794
|
+
],
|
|
704
795
|
);
|
|
705
796
|
|
|
706
797
|
const supervisor = openPair(BWRAP_PATH, {
|
|
@@ -771,6 +862,76 @@ function createTrustedResources(
|
|
|
771
862
|
throw error;
|
|
772
863
|
}
|
|
773
864
|
|
|
865
|
+
let ptyLauncher = null;
|
|
866
|
+
let ptyLauncherBytes = 0;
|
|
867
|
+
let ptyLauncherDigest = null;
|
|
868
|
+
if (sandboxOpts?.pty === true) {
|
|
869
|
+
ptyLauncher = openPair(LINUX_GENERIC_PTY_LAUNCHER_PATH, {
|
|
870
|
+
directory: false,
|
|
871
|
+
rootRequired: true,
|
|
872
|
+
executable: true,
|
|
873
|
+
});
|
|
874
|
+
ptyLauncherBytes = Number(runtime.fs.fstatSync(ptyLauncher.probeFd).size);
|
|
875
|
+
const probeDigest = hashOpenFile(
|
|
876
|
+
runtime,
|
|
877
|
+
ptyLauncher.probeFd,
|
|
878
|
+
ptyLauncherBytes,
|
|
879
|
+
);
|
|
880
|
+
ptyLauncherDigest = hashOpenFile(
|
|
881
|
+
runtime,
|
|
882
|
+
ptyLauncher.finalFd,
|
|
883
|
+
ptyLauncherBytes,
|
|
884
|
+
);
|
|
885
|
+
if (probeDigest !== ptyLauncherDigest) {
|
|
886
|
+
throw new Error("pty_launcher_digest_mismatch");
|
|
887
|
+
}
|
|
888
|
+
ptyLauncher.identity = Object.freeze({
|
|
889
|
+
...ptyLauncher.identity,
|
|
890
|
+
sha256: ptyLauncherDigest,
|
|
891
|
+
bytes: ptyLauncherBytes,
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
const launcherCapabilityFd = runtime.fs.openSync(
|
|
895
|
+
LINUX_GENERIC_PTY_LAUNCHER_PATH,
|
|
896
|
+
openFlags({ directory: false }),
|
|
897
|
+
);
|
|
898
|
+
ownedFds.add(launcherCapabilityFd);
|
|
899
|
+
const launcherCapabilityStat = runtime.fs.fstatSync(launcherCapabilityFd);
|
|
900
|
+
if (
|
|
901
|
+
!statMatchesIdentity(launcherCapabilityStat, ptyLauncher.identity, {
|
|
902
|
+
directory: false,
|
|
903
|
+
}) ||
|
|
904
|
+
hashOpenFile(runtime, launcherCapabilityFd, ptyLauncherBytes) !==
|
|
905
|
+
ptyLauncherDigest
|
|
906
|
+
) {
|
|
907
|
+
throw new Error("pty_launcher_capability_reader_unattested");
|
|
908
|
+
}
|
|
909
|
+
const launcherCapabilityResult = runtime.spawnSync(
|
|
910
|
+
"/proc/self/fd/3",
|
|
911
|
+
["--help"],
|
|
912
|
+
{
|
|
913
|
+
shell: false,
|
|
914
|
+
encoding: "utf8",
|
|
915
|
+
stdio: ["ignore", "pipe", "pipe", launcherCapabilityFd],
|
|
916
|
+
timeout: 10_000,
|
|
917
|
+
env: { PATH: "/usr/bin:/bin", LANG: "C", LC_ALL: "C" },
|
|
918
|
+
},
|
|
919
|
+
);
|
|
920
|
+
closeFd(launcherCapabilityFd);
|
|
921
|
+
const launcherHelp =
|
|
922
|
+
`${String(launcherCapabilityResult?.stdout || "")}\n` +
|
|
923
|
+
String(launcherCapabilityResult?.stderr || "");
|
|
924
|
+
if (
|
|
925
|
+
launcherCapabilityResult?.error ||
|
|
926
|
+
launcherCapabilityResult?.status !== 0 ||
|
|
927
|
+
!launcherHelp.split(/\s+/).includes("--ctty")
|
|
928
|
+
) {
|
|
929
|
+
const error = new Error("pty_launcher_capability_probe_failed");
|
|
930
|
+
error.code = "LINUX_GENERIC_BWRAP_UNAVAILABLE";
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
774
935
|
const system = [];
|
|
775
936
|
const systemSymlinks = [];
|
|
776
937
|
for (const destination of SYSTEM_DESTINATIONS) {
|
|
@@ -824,6 +985,33 @@ function createTrustedResources(
|
|
|
824
985
|
identity: pair.identity,
|
|
825
986
|
});
|
|
826
987
|
}
|
|
988
|
+
const sourceMountSources = Object.freeze([
|
|
989
|
+
Object.freeze({
|
|
990
|
+
kind: "workspace",
|
|
991
|
+
source: contract.workspaceRoot,
|
|
992
|
+
recursive: true,
|
|
993
|
+
}),
|
|
994
|
+
...system.map((entry) =>
|
|
995
|
+
Object.freeze({
|
|
996
|
+
kind: "system",
|
|
997
|
+
source: entry.destination,
|
|
998
|
+
recursive: true,
|
|
999
|
+
}),
|
|
1000
|
+
),
|
|
1001
|
+
...etc.map((entry) =>
|
|
1002
|
+
Object.freeze({
|
|
1003
|
+
kind: "etc",
|
|
1004
|
+
source: entry.destination,
|
|
1005
|
+
recursive: false,
|
|
1006
|
+
}),
|
|
1007
|
+
),
|
|
1008
|
+
]);
|
|
1009
|
+
const initialWorkspaceMountTopology = workspaceMountTopology(
|
|
1010
|
+
runtime,
|
|
1011
|
+
contract.workspaceRoot,
|
|
1012
|
+
workspace.identity,
|
|
1013
|
+
sourceMountSources,
|
|
1014
|
+
);
|
|
827
1015
|
|
|
828
1016
|
const filter = buildNetworkSeccompFilter(runtime.arch);
|
|
829
1017
|
const openSeccomp = (phase) => {
|
|
@@ -969,6 +1157,7 @@ function createTrustedResources(
|
|
|
969
1157
|
issued.origin === (spawnOpts?.origin || "unknown") &&
|
|
970
1158
|
issued.command === command &&
|
|
971
1159
|
issued.sync === (sandboxOpts?.sync === true) &&
|
|
1160
|
+
issued.pty === (sandboxOpts?.pty === true) &&
|
|
972
1161
|
issued.shell === false &&
|
|
973
1162
|
Array.isArray(stdio) &&
|
|
974
1163
|
issued.stdio.length === stdio.length &&
|
|
@@ -986,6 +1175,7 @@ function createTrustedResources(
|
|
|
986
1175
|
runtime,
|
|
987
1176
|
contract.workspaceRoot,
|
|
988
1177
|
workspace.identity,
|
|
1178
|
+
sourceMountSources,
|
|
989
1179
|
);
|
|
990
1180
|
} catch {
|
|
991
1181
|
return false;
|
|
@@ -1001,6 +1191,14 @@ function createTrustedResources(
|
|
|
1001
1191
|
!descriptorAttested(supervisor.finalFd, supervisor.identity, false) ||
|
|
1002
1192
|
hashOpenFile(runtime, supervisor.finalFd, supervisorBytes) !==
|
|
1003
1193
|
supervisorFinalDigest ||
|
|
1194
|
+
(sandboxOpts?.pty === true &&
|
|
1195
|
+
(!descriptorAttested(
|
|
1196
|
+
ptyLauncher?.finalFd,
|
|
1197
|
+
ptyLauncher?.identity,
|
|
1198
|
+
false,
|
|
1199
|
+
) ||
|
|
1200
|
+
hashOpenFile(runtime, ptyLauncher.finalFd, ptyLauncherBytes) !==
|
|
1201
|
+
ptyLauncherDigest)) ||
|
|
1004
1202
|
hashOpenFile(runtime, seccomp.finalFd, filter.buffer.length) !==
|
|
1005
1203
|
filter.sha256
|
|
1006
1204
|
) {
|
|
@@ -1249,6 +1447,17 @@ function createTrustedResources(
|
|
|
1249
1447
|
sha256: supervisorFinalDigest,
|
|
1250
1448
|
bytes: supervisorBytes,
|
|
1251
1449
|
},
|
|
1450
|
+
...(ptyLauncher
|
|
1451
|
+
? {
|
|
1452
|
+
ptyLauncher: {
|
|
1453
|
+
probeFd: ptyLauncher.probeFd,
|
|
1454
|
+
finalFd: ptyLauncher.finalFd,
|
|
1455
|
+
identity: ptyLauncher.identity,
|
|
1456
|
+
sha256: ptyLauncherDigest,
|
|
1457
|
+
bytes: ptyLauncherBytes,
|
|
1458
|
+
},
|
|
1459
|
+
}
|
|
1460
|
+
: {}),
|
|
1252
1461
|
workspace: {
|
|
1253
1462
|
probeFd: workspace.probeFd,
|
|
1254
1463
|
finalFd: workspace.finalFd,
|
|
@@ -1291,6 +1500,7 @@ export function applyLinuxGenericWorkspaceSandbox(
|
|
|
1291
1500
|
cwd: spawnOpts?.cwd || process.cwd(),
|
|
1292
1501
|
shell: spawnOpts?.shell,
|
|
1293
1502
|
sync: sandboxOpts?.sync === true,
|
|
1503
|
+
pty: sandboxOpts?.pty === true,
|
|
1294
1504
|
stdio: spawnOpts?.stdio,
|
|
1295
1505
|
requiredBoundaries,
|
|
1296
1506
|
};
|
|
@@ -24,6 +24,7 @@ import path from "node:path";
|
|
|
24
24
|
|
|
25
25
|
export const LINUX_GENERIC_BWRAP_BACKEND = "linux-bwrap-workspace";
|
|
26
26
|
export const LINUX_GENERIC_CONTRACT_KIND = "strict-workspace-command";
|
|
27
|
+
export const LINUX_GENERIC_PTY_LAUNCHER_PATH = "/usr/bin/setsid";
|
|
27
28
|
|
|
28
29
|
const SUPPORTED_BOUNDARIES = new Set(["filesystem", "network"]);
|
|
29
30
|
const SYSTEM_RUNTIME_DESTINATIONS = new Set([
|
|
@@ -97,6 +98,7 @@ const CONTRACT_VERSION = 1;
|
|
|
97
98
|
const PLAN_VERSION = 1;
|
|
98
99
|
const SANDBOX_HOME = "/home/sandbox";
|
|
99
100
|
const SUPERVISOR_STAGING_PATH = "/run/.chainless-bwrap-supervisor";
|
|
101
|
+
const PTY_LAUNCHER_STAGING_PATH = "/run/.chainless-pty-launcher";
|
|
100
102
|
const SCRATCH_MOUNTS = ["/tmp", "/run", "/var/tmp", SANDBOX_HOME];
|
|
101
103
|
|
|
102
104
|
const issuedContracts = new WeakMap();
|
|
@@ -194,6 +196,12 @@ function normalizeShell(shell) {
|
|
|
194
196
|
throw new TypeError("strong Linux workspace sandbox requires shell:false");
|
|
195
197
|
}
|
|
196
198
|
|
|
199
|
+
function normalizePty(pty) {
|
|
200
|
+
if (pty === undefined || pty === null || pty === false) return false;
|
|
201
|
+
if (pty === true) return true;
|
|
202
|
+
throw new TypeError("strong Linux workspace sandbox requires pty:boolean");
|
|
203
|
+
}
|
|
204
|
+
|
|
197
205
|
function normalizeStdio(stdio) {
|
|
198
206
|
let entries;
|
|
199
207
|
if (stdio === undefined || stdio === null || stdio === "pipe") {
|
|
@@ -324,6 +332,10 @@ export function issueLinuxGenericSandboxExecutionContract(
|
|
|
324
332
|
const args = normalizeArgs(spec?.args || []);
|
|
325
333
|
const shell = normalizeShell(spec?.shell);
|
|
326
334
|
const sync = spec?.sync === true;
|
|
335
|
+
const pty = normalizePty(spec?.pty);
|
|
336
|
+
if (pty && sync) {
|
|
337
|
+
throw new TypeError("strong Linux workspace PTY cannot be synchronous");
|
|
338
|
+
}
|
|
327
339
|
const stdio = normalizeStdio(spec?.stdio);
|
|
328
340
|
const requiredBoundaries = normalizeBoundaries(spec?.requiredBoundaries);
|
|
329
341
|
if (
|
|
@@ -355,6 +367,7 @@ export function issueLinuxGenericSandboxExecutionContract(
|
|
|
355
367
|
args,
|
|
356
368
|
shell,
|
|
357
369
|
sync,
|
|
370
|
+
pty,
|
|
358
371
|
stdio,
|
|
359
372
|
requiredBoundaries,
|
|
360
373
|
};
|
|
@@ -383,11 +396,13 @@ function issuedBindingMatches(issued, provenance = {}) {
|
|
|
383
396
|
let args;
|
|
384
397
|
let boundaries;
|
|
385
398
|
let shell;
|
|
399
|
+
let pty;
|
|
386
400
|
let stdio;
|
|
387
401
|
try {
|
|
388
402
|
args = normalizeArgs(provenance.args || []);
|
|
389
403
|
boundaries = normalizeBoundaries(provenance.requiredBoundaries);
|
|
390
404
|
shell = normalizeShell(provenance.shell);
|
|
405
|
+
pty = normalizePty(provenance.pty);
|
|
391
406
|
stdio = normalizeStdio(provenance.stdio);
|
|
392
407
|
} catch {
|
|
393
408
|
return false;
|
|
@@ -397,6 +412,7 @@ function issuedBindingMatches(issued, provenance = {}) {
|
|
|
397
412
|
provenance.command === issued.command &&
|
|
398
413
|
provenance.cwd === issued.workingDirectory &&
|
|
399
414
|
provenance.sync === issued.sync &&
|
|
415
|
+
pty === issued.pty &&
|
|
400
416
|
shell === issued.shell &&
|
|
401
417
|
args.length === issued.args.length &&
|
|
402
418
|
args.every((value, index) => value === issued.args[index]) &&
|
|
@@ -514,12 +530,23 @@ function validateWorkspaceMountTopology(value, issued) {
|
|
|
514
530
|
!/^[a-f0-9]{64}$/.test(value.digest || "") ||
|
|
515
531
|
!Number.isSafeInteger(value.lineageEntryCount) ||
|
|
516
532
|
value.lineageEntryCount < 1 ||
|
|
533
|
+
value.lineageEntryCount > 4096 ||
|
|
517
534
|
!Number.isSafeInteger(value.filesystemEntryCount) ||
|
|
518
535
|
value.filesystemEntryCount < 1 ||
|
|
536
|
+
value.filesystemEntryCount > 16384 ||
|
|
519
537
|
!Number.isSafeInteger(value.aliasCount) ||
|
|
520
|
-
value.aliasCount
|
|
538
|
+
value.aliasCount !== 1 ||
|
|
521
539
|
!Number.isSafeInteger(value.forbiddenIdentityCount) ||
|
|
522
540
|
value.forbiddenIdentityCount < 1 ||
|
|
541
|
+
value.forbiddenIdentityCount > 64 ||
|
|
542
|
+
!/^[a-f0-9]{64}$/.test(value.sourceMountSetDigest || "") ||
|
|
543
|
+
!Number.isSafeInteger(value.sourceMountCount) ||
|
|
544
|
+
value.sourceMountCount < 2 ||
|
|
545
|
+
value.sourceMountCount > 64 ||
|
|
546
|
+
!Number.isSafeInteger(value.importedMountEntryCount) ||
|
|
547
|
+
value.importedMountEntryCount < value.sourceMountCount ||
|
|
548
|
+
value.importedMountEntryCount > 65536 ||
|
|
549
|
+
value.sourceMountPropagationPrivateAtAttestation !== true ||
|
|
523
550
|
value.strictDescendantMountsAtAttestation !== 0 ||
|
|
524
551
|
value.rootAliasAttested !== true ||
|
|
525
552
|
value.recursiveBind !== true ||
|
|
@@ -538,6 +565,11 @@ function validateWorkspaceMountTopology(value, issued) {
|
|
|
538
565
|
filesystemEntryCount: value.filesystemEntryCount,
|
|
539
566
|
aliasCount: value.aliasCount,
|
|
540
567
|
forbiddenIdentityCount: value.forbiddenIdentityCount,
|
|
568
|
+
sourceMountSetDigest: value.sourceMountSetDigest,
|
|
569
|
+
sourceMountCount: value.sourceMountCount,
|
|
570
|
+
importedMountEntryCount: value.importedMountEntryCount,
|
|
571
|
+
sourceMountPropagationPrivateAtAttestation:
|
|
572
|
+
value.sourceMountPropagationPrivateAtAttestation,
|
|
541
573
|
strictDescendantMountsAtAttestation:
|
|
542
574
|
value.strictDescendantMountsAtAttestation,
|
|
543
575
|
rootAliasAttested: value.rootAliasAttested,
|
|
@@ -579,6 +611,30 @@ function validateResourceContract(resources, issued) {
|
|
|
579
611
|
) {
|
|
580
612
|
throw new Error("bubblewrap supervisor is not root-owned and pinned");
|
|
581
613
|
}
|
|
614
|
+
let ptyLauncher = null;
|
|
615
|
+
if (issued.pty) {
|
|
616
|
+
if (
|
|
617
|
+
resources.ptyLauncher?.identity?.realPath !==
|
|
618
|
+
LINUX_GENERIC_PTY_LAUNCHER_PATH ||
|
|
619
|
+
!validateRootOwnedIdentity(resources.ptyLauncher?.identity, {
|
|
620
|
+
directory: false,
|
|
621
|
+
}) ||
|
|
622
|
+
!/^[a-f0-9]{64}$/.test(resources.ptyLauncher?.sha256 || "") ||
|
|
623
|
+
!Number.isSafeInteger(resources.ptyLauncher?.bytes) ||
|
|
624
|
+
resources.ptyLauncher.bytes <= 0 ||
|
|
625
|
+
!Number.isInteger(resources.ptyLauncher?.probeFd) ||
|
|
626
|
+
!Number.isInteger(resources.ptyLauncher?.finalFd)
|
|
627
|
+
) {
|
|
628
|
+
throw new Error("PTY session launcher is not root-owned and pinned");
|
|
629
|
+
}
|
|
630
|
+
ptyLauncher = {
|
|
631
|
+
identity: resources.ptyLauncher.identity,
|
|
632
|
+
sha256: resources.ptyLauncher.sha256,
|
|
633
|
+
bytes: resources.ptyLauncher.bytes,
|
|
634
|
+
};
|
|
635
|
+
} else if (resources.ptyLauncher !== undefined) {
|
|
636
|
+
throw new Error("non-PTY launch supplied a PTY session launcher");
|
|
637
|
+
}
|
|
582
638
|
const system = Array.isArray(resources.system) ? resources.system : [];
|
|
583
639
|
if (!system.some((entry) => entry.destination === "/usr")) {
|
|
584
640
|
throw new Error("root-owned /usr runtime mount is required");
|
|
@@ -657,6 +713,7 @@ function validateResourceContract(resources, issued) {
|
|
|
657
713
|
sha256: resources.supervisor.sha256,
|
|
658
714
|
bytes: resources.supervisor.bytes,
|
|
659
715
|
},
|
|
716
|
+
ptyLauncher,
|
|
660
717
|
system,
|
|
661
718
|
symlinks,
|
|
662
719
|
etc,
|
|
@@ -749,6 +806,15 @@ function directoryArguments(directoryDestinations, fileDestinations) {
|
|
|
749
806
|
|
|
750
807
|
function descriptorLayout(stdio, resources, validated, workspaceRoot, phase) {
|
|
751
808
|
const descriptors = [
|
|
809
|
+
...(validated.ptyLauncher
|
|
810
|
+
? [
|
|
811
|
+
{
|
|
812
|
+
kind: "ptyLauncher",
|
|
813
|
+
fd: resources.ptyLauncher[`${phase}Fd`],
|
|
814
|
+
destination: PTY_LAUNCHER_STAGING_PATH,
|
|
815
|
+
},
|
|
816
|
+
]
|
|
817
|
+
: []),
|
|
752
818
|
{
|
|
753
819
|
kind: "supervisor",
|
|
754
820
|
fd: resources.supervisor[`${phase}Fd`],
|
|
@@ -791,6 +857,7 @@ function descriptorLayout(stdio, resources, validated, workspaceRoot, phase) {
|
|
|
791
857
|
function buildPolicyArgs({ issued, validated, descriptors, environment }) {
|
|
792
858
|
const byKind = (kind) =>
|
|
793
859
|
descriptors.filter((descriptor) => descriptor.kind === kind);
|
|
860
|
+
const ptyLauncher = byKind("ptyLauncher")[0] || null;
|
|
794
861
|
const supervisor = byKind("supervisor")[0];
|
|
795
862
|
const workspace = byKind("workspace")[0];
|
|
796
863
|
const seccomp = byKind("seccomp")[0];
|
|
@@ -805,7 +872,7 @@ function buildPolicyArgs({ issued, validated, descriptors, environment }) {
|
|
|
805
872
|
);
|
|
806
873
|
const args = [
|
|
807
874
|
"--die-with-parent",
|
|
808
|
-
"--new-session",
|
|
875
|
+
...(issued.pty ? [] : ["--new-session"]),
|
|
809
876
|
"--unshare-user",
|
|
810
877
|
"--disable-userns",
|
|
811
878
|
"--assert-userns-disabled",
|
|
@@ -824,6 +891,15 @@ function buildPolicyArgs({ issued, validated, descriptors, environment }) {
|
|
|
824
891
|
args.push("--dir", directory);
|
|
825
892
|
}
|
|
826
893
|
args.push(
|
|
894
|
+
...(ptyLauncher
|
|
895
|
+
? [
|
|
896
|
+
"--perms",
|
|
897
|
+
"0000",
|
|
898
|
+
"--file",
|
|
899
|
+
String(ptyLauncher.childFd),
|
|
900
|
+
PTY_LAUNCHER_STAGING_PATH,
|
|
901
|
+
]
|
|
902
|
+
: []),
|
|
827
903
|
"--perms",
|
|
828
904
|
"0000",
|
|
829
905
|
"--file",
|
|
@@ -951,6 +1027,15 @@ function policyBinding({
|
|
|
951
1027
|
? { identity: validated.target.identity }
|
|
952
1028
|
: {}),
|
|
953
1029
|
},
|
|
1030
|
+
pty: issued.pty
|
|
1031
|
+
? {
|
|
1032
|
+
mode: "dedicated-controlling-terminal",
|
|
1033
|
+
launcher: validated.ptyLauncher,
|
|
1034
|
+
launcherDescriptorBound: true,
|
|
1035
|
+
launcherDescriptorConsumedBeforeTarget: true,
|
|
1036
|
+
bwrapNewSession: false,
|
|
1037
|
+
}
|
|
1038
|
+
: { mode: "none" },
|
|
954
1039
|
requiredBoundaries: [...requiredBoundaries].sort(),
|
|
955
1040
|
environmentDigest: sha256(stableJson(environment)),
|
|
956
1041
|
policyArgs,
|
|
@@ -1067,6 +1152,7 @@ export function planLinuxGenericBubblewrap(
|
|
|
1067
1152
|
cwd: "/",
|
|
1068
1153
|
shell: false,
|
|
1069
1154
|
encoding: "utf8",
|
|
1155
|
+
timeout: 10_000,
|
|
1070
1156
|
stdio: [
|
|
1071
1157
|
...issued.stdio.map((_, index) =>
|
|
1072
1158
|
index === 1 || index === 2 ? "pipe" : "ignore",
|
|
@@ -1138,13 +1224,22 @@ export function planLinuxGenericBubblewrap(
|
|
|
1138
1224
|
const supervisor = finalDescriptors.find(
|
|
1139
1225
|
(entry) => entry.kind === "supervisor",
|
|
1140
1226
|
);
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1227
|
+
const ptyLauncher = finalDescriptors.find(
|
|
1228
|
+
(entry) => entry.kind === "ptyLauncher",
|
|
1229
|
+
);
|
|
1230
|
+
const supervisorCommand = `/proc/self/fd/${supervisor.childFd}`;
|
|
1231
|
+
const command = issued.pty
|
|
1232
|
+
? `/proc/self/fd/${ptyLauncher.childFd}`
|
|
1233
|
+
: supervisorCommand;
|
|
1234
|
+
const bwrapArgs = [
|
|
1143
1235
|
...policyArgs,
|
|
1144
1236
|
"--",
|
|
1145
1237
|
validated.target.resolvedCommand,
|
|
1146
1238
|
...validated.target.args,
|
|
1147
|
-
]
|
|
1239
|
+
];
|
|
1240
|
+
const args = Object.freeze(
|
|
1241
|
+
issued.pty ? ["--ctty", supervisorCommand, ...bwrapArgs] : bwrapArgs,
|
|
1242
|
+
);
|
|
1148
1243
|
const options = Object.freeze({
|
|
1149
1244
|
...passthroughSpawnOptions(spawnOptions),
|
|
1150
1245
|
cwd: "/",
|
|
@@ -1187,10 +1282,16 @@ export function planLinuxGenericBubblewrap(
|
|
|
1187
1282
|
targetRuntime: "generic-command",
|
|
1188
1283
|
contentSnapshot: false,
|
|
1189
1284
|
handleAtomic: false,
|
|
1285
|
+
// Every descriptor bind source is private at both attestations, which
|
|
1286
|
+
// prevents ongoing host propagation after bwrap clones that topology.
|
|
1287
|
+
// The final check is still not atomic with the subsequent native spawn.
|
|
1190
1288
|
mountTopologyAtomic: false,
|
|
1191
1289
|
contractDigest: issued.contractDigest,
|
|
1192
1290
|
policyDigest,
|
|
1193
1291
|
mountTopologyDigest: validated.workspaceMountTopology.digest,
|
|
1292
|
+
sourceMountSetDigest:
|
|
1293
|
+
validated.workspaceMountTopology.sourceMountSetDigest,
|
|
1294
|
+
sourceMountPropagationPrivateAtAttestation: true,
|
|
1194
1295
|
emptyRoot: true,
|
|
1195
1296
|
undeclaredRootReadOnly: true,
|
|
1196
1297
|
workspaceReadWrite: true,
|
|
@@ -1228,6 +1329,9 @@ export function planLinuxGenericBubblewrap(
|
|
|
1228
1329
|
"no-strict-descendants-or-forbidden-root-aliases-at-attestation",
|
|
1229
1330
|
mountTopologySource: validated.workspaceMountTopology.source,
|
|
1230
1331
|
mountTopologyDigest: validated.workspaceMountTopology.digest,
|
|
1332
|
+
sourceMountSetDigest:
|
|
1333
|
+
validated.workspaceMountTopology.sourceMountSetDigest,
|
|
1334
|
+
sourceMountPropagationPrivateAtAttestation: true,
|
|
1231
1335
|
mountTopologyAtomic: false,
|
|
1232
1336
|
}),
|
|
1233
1337
|
networkPolicy: Object.freeze({
|
|
@@ -1235,6 +1339,19 @@ export function planLinuxGenericBubblewrap(
|
|
|
1235
1339
|
namespaceIdentityChanged: true,
|
|
1236
1340
|
seccomp: "deny-network-creation",
|
|
1237
1341
|
}),
|
|
1342
|
+
ptyPolicy: issued.pty
|
|
1343
|
+
? Object.freeze({
|
|
1344
|
+
mode: "dedicated-controlling-terminal",
|
|
1345
|
+
launcherPath: validated.ptyLauncher.identity.realPath,
|
|
1346
|
+
launcherSha256: validated.ptyLauncher.sha256,
|
|
1347
|
+
launcherBytes: validated.ptyLauncher.bytes,
|
|
1348
|
+
launcherDescriptorBound: true,
|
|
1349
|
+
launcherExecutablePinned: true,
|
|
1350
|
+
launcherDescriptorConsumedBeforeTarget: true,
|
|
1351
|
+
launcherStagingPathHidden: true,
|
|
1352
|
+
bwrapNewSession: false,
|
|
1353
|
+
})
|
|
1354
|
+
: null,
|
|
1238
1355
|
postSpawn: Object.freeze({ required: false, mode: "none" }),
|
|
1239
1356
|
cleanup: finalCleanup,
|
|
1240
1357
|
});
|
|
@@ -1248,7 +1365,8 @@ export function verifyLinuxGenericBubblewrapPlan(plan) {
|
|
|
1248
1365
|
Object.isFrozen(plan) &&
|
|
1249
1366
|
Object.isFrozen(plan.args) &&
|
|
1250
1367
|
Object.isFrozen(plan.options) &&
|
|
1251
|
-
Object.isFrozen(plan.options.stdio)
|
|
1368
|
+
Object.isFrozen(plan.options.stdio) &&
|
|
1369
|
+
(plan.ptyPolicy === null || Object.isFrozen(plan.ptyPolicy))
|
|
1252
1370
|
);
|
|
1253
1371
|
}
|
|
1254
1372
|
|
|
@@ -321,6 +321,7 @@ function normalizeSandboxRequest(profileOrRequest, explicitRequest) {
|
|
|
321
321
|
profile,
|
|
322
322
|
requiredBoundaries,
|
|
323
323
|
sync: request.sync === true,
|
|
324
|
+
pty: request.pty === true,
|
|
324
325
|
executionContract:
|
|
325
326
|
request.executionContract &&
|
|
326
327
|
typeof request.executionContract === "object" &&
|
|
@@ -3040,7 +3041,7 @@ function validateLinuxPluginContract(
|
|
|
3040
3041
|
return { ok: false, reason: "execution_contract_missing" };
|
|
3041
3042
|
}
|
|
3042
3043
|
if (
|
|
3043
|
-
sync !== true ||
|
|
3044
|
+
(sync !== true && sync !== false) ||
|
|
3044
3045
|
spawnOpts?.shell !== false ||
|
|
3045
3046
|
spawnOpts?.detached === true ||
|
|
3046
3047
|
!linuxStdioIsNarrow(spawnOpts?.stdio) ||
|
|
@@ -4553,7 +4554,8 @@ function buildLinuxNetworkSeccompFilter(arch) {
|
|
|
4553
4554
|
instructions.push([0x54, 0, 0, ~architecture.x32SyscallBit]);
|
|
4554
4555
|
}
|
|
4555
4556
|
instructions.push(
|
|
4556
|
-
// No socket family is needed by this direct
|
|
4557
|
+
// No socket family is needed by this direct one-shot Plugin bin route,
|
|
4558
|
+
// whether the Broker call is synchronous or asynchronously supervised.
|
|
4557
4559
|
// Blocking creation rather than relying only on CLONE_NEWNET also closes
|
|
4558
4560
|
// non-network-namespaced transports such as AF_VSOCK.
|
|
4559
4561
|
[0x15, 0, 1, architecture.socketSyscall],
|
|
@@ -6179,6 +6181,7 @@ export function applyLinuxSandbox(
|
|
|
6179
6181
|
* profile?: string,
|
|
6180
6182
|
* requiredBoundaries?: string[],
|
|
6181
6183
|
* sync?: boolean,
|
|
6184
|
+
* pty?: boolean,
|
|
6182
6185
|
* executionContract?: Readonly<Object>|null
|
|
6183
6186
|
* }|null} explicitRequest
|
|
6184
6187
|
* @returns {ReturnType<typeof createSandboxPlan>}
|
|
@@ -6239,6 +6242,7 @@ export function applySandbox(
|
|
|
6239
6242
|
profileName: profiles[profileName] ? profileName : "default",
|
|
6240
6243
|
requiredBoundaries: sandboxRequest.requiredBoundaries,
|
|
6241
6244
|
sync: sandboxRequest.sync,
|
|
6245
|
+
pty: sandboxRequest.pty,
|
|
6242
6246
|
executionContract: sandboxRequest.executionContract,
|
|
6243
6247
|
};
|
|
6244
6248
|
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -121,6 +121,7 @@ import {
|
|
|
121
121
|
questionWithIdleTimeout,
|
|
122
122
|
} from "./permission-prompt.js";
|
|
123
123
|
import { describeAskContext, raceLocalAndRemote } from "./remote-approval.js";
|
|
124
|
+
import { runWithHostHooksV2Workspace } from "../lib/hooks-v2-workspace-context.js";
|
|
124
125
|
import {
|
|
125
126
|
parsePermissionTier,
|
|
126
127
|
parsePermissionModeArg,
|
|
@@ -477,6 +478,13 @@ export async function agentLoop(messages, options) {
|
|
|
477
478
|
* Start the agentic REPL
|
|
478
479
|
*/
|
|
479
480
|
export async function startAgentRepl(options = {}) {
|
|
481
|
+
const trustedWorkspaceRoot = process.cwd();
|
|
482
|
+
return runWithHostHooksV2Workspace(trustedWorkspaceRoot, () =>
|
|
483
|
+
startAgentReplInWorkspace(options),
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function startAgentReplInWorkspace(options = {}) {
|
|
480
488
|
// EPIPE guard: if the REPL's stdout is piped and the consumer closes (e.g.
|
|
481
489
|
// `cc agent | head`), the async stream `error` would otherwise crash the
|
|
482
490
|
// process. Route a broken pipe into the REPL's own graceful shutdown (the
|