chainlesschain 0.163.2 → 0.163.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DIY6KxV-.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-BXH0muPt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-D39w8vmg.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-CUU1zvLs.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-D8azHO3T.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-D0tAwXTg.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-C29mhJll.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-D6Aa1Gro.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-B1uoHbPQ.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-Bs4DLOR5.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-DUYIBSle.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-CNcBPEa4.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-CIY89763.js} +1 -1
- package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-CsPYFmgo.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-BBocEge4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-TQ8Ux0Lf.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-DfBsZTft.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-Cg-I6FVH.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-DTQzfRTp.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-DGxEKGpR.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-DLZ1RDSr.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-XneICKNI.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-BRU00J1-.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BzCbJuAy.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-D7BRyNBG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-D9DeFIhW.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-6rAtA9Ko.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-8Hqy80pU.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-Cmvkj1rt.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-BRiQUjjw.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-CKN_EzVf.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-BCDHwpwQ.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-DK9ovhaB.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-D53pHiHy.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BILPGakp.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DTVCOr9-.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-Hzc1iow4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-GXfgnGYV.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-B-3TJWsS.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-DwSJMWtK.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-D2uxf-DA.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-DG6kbKK0.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-f5p49EDU.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-FUgHyzSD.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-DCHGee7e.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer-Dm6LlIoa.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-BawA7qAg.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-D6a5wZU1.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-K8WavwnA.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-4JAJYaqx.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-BDWvi8PD.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-DF_K4E9i.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-BYeZlesI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-BIHlgnIc.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Cvf-6B5P.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-CnwfJrRz.js} +2 -2
- package/src/assets/web-panel/assets/Projects-b-jPXzF4.js +1 -0
- package/src/assets/web-panel/assets/{Providers-BGs47s1a.js → Providers-CxC7JuYv.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-VLf3gAfL.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-B5vGjRiF.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend--UW4iYs-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-DMSPcfKR.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-kQXlEHmj.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-CxGu3pit.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-Bi5lo8MZ.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-Cq_H5RTh.js} +1 -1
- package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-YFbmkaQS.js} +3 -3
- package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DLOGdaLm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CteIxb3D.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-CccopaIZ.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-AzfEQKX1.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-ekGo5l77.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-BmZmAgYH.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-D7q3Z4uL.js +1 -0
- package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-CX1Tgeka.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Zc59jF4i.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-CXOD7y83.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-B1aBHPBo.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-zALlOq_U.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-CQfJMXaq.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-BU0RcM-w.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-IFZ3HsbH.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-BzslgW0v.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-BZAcQNS6.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-CBK1DMi0.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-BlMTPzO-.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-B51-8QVn.js} +1 -1
- package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-504RTByU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-C-GP6IQM.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-DOkIj7ID.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-kB0nCNwN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-DN7V-_FW.js} +1 -1
- package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index--n-ZciV3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-7jIqHbzw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-B2-x9bS8.js} +1 -1
- package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-BFIFJ5Dr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-B_vGU-Js.js} +1 -1
- package/src/assets/web-panel/assets/{index--rq90rL9.js → index-Bkxhaeta.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-By6u5IV1.js} +1 -1
- package/src/assets/web-panel/assets/index-Bya64sZE.js +1 -0
- package/src/assets/web-panel/assets/{index-caye7AOE.js → index-C0k_Nqg9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-C3TSohzc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-C4DV_5uA.js} +1 -1
- package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-C8UltbXK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-C9_48OzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CKTfZyfU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-CP2A226L.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-CUhQpiYv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-CX78k-qf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-CYopNjcJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-Cao5DZy7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-CfiEM64n.js} +3 -3
- package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-ConjaJro.js} +1 -1
- package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-CqcLo5WV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-CySTJKeq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-D1phyca8.js} +1 -1
- package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-D9LyiN-L.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-DA2f1UV3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-DAOpQvl_.js} +1 -1
- package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DE-hmI2l.js} +1 -1
- package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-DEMPUVa3.js} +1 -1
- package/src/assets/web-panel/assets/index-D_FWpDZM.js +1 -0
- package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-DaA__-q1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-FcV8J_Qz.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-P2mfY27Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-et5JIPHO.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-f4Bo4KXm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-kAqQXfP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-kt_5rfcZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-so8eIIxF.js} +1 -1
- package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-yYTQZypF.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-CPYEIdpV.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-Cjim9pxs.js} +1 -1
- package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-DuU-cgz3.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-DJHNAkta.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-DSQBgdBj.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-CTmT_j5m.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-Lfm6v-r4.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-CgrcPcDi.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CuE3110i.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-DiK5nbpf.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-DPEJdQaZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-CnnodGmp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DWF84Yav.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-a5b5Dw2f.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-DAluzXOq.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-Dokg7uAZ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agenda.js +6 -5
- package/src/commands/agent.js +237 -75
- package/src/commands/agents.js +6 -0
- package/src/commands/background-session.js +11 -1
- package/src/commands/mcp.js +217 -30
- package/src/data/changelog.json +14 -1
- package/src/harness/mcp-client.js +242 -42
- package/src/harness/worktree-isolator.js +26 -3
- package/src/lib/agent-worktree.js +41 -2
- package/src/lib/background-agent-supervisor.js +790 -186
- package/src/lib/background-command-argv.js +318 -0
- package/src/lib/background-launch-profile.js +1 -0
- package/src/lib/background-phase-reporter.js +22 -2
- package/src/lib/background-worktree-policy.js +109 -0
- package/src/lib/cowork-mcp-tools.js +51 -6
- package/src/lib/cowork-task-runner.js +1 -0
- package/src/lib/mcp-headers-helper.js +76 -8
- package/src/lib/mcp-sandbox-policy.js +155 -0
- package/src/lib/mcp-stdio-execution-authority.js +163 -0
- package/src/lib/mcp-stdio-workspace-authority.js +252 -0
- package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
- package/src/lib/process-execution-broker/index.js +179 -104
- package/src/lib/process-execution-broker/platform-sandbox.js +942 -168
- package/src/lib/process-tree-termination.js +17 -1
- package/src/lib/skill-mcp.js +65 -4
- package/src/lib/web-fetch.js +4 -4
- package/src/runtime/agent-runtime.js +61 -8
- package/src/runtime/mcp-config.js +235 -28
- package/src/workers/background-agent-worker.js +492 -71
- package/src/assets/web-panel/assets/MarkdownRenderer-Bf3DEqP6.js +0 -1
- package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
- package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
- package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isProxy } from "node:util/types";
|
|
4
|
+
import { resolveHostHooksV2WorkspaceBinding } from "./hooks-v2-workspace-context.js";
|
|
5
|
+
import { normalizeMcpSandboxPolicy } from "./mcp-sandbox-policy.js";
|
|
6
|
+
import { resolvePluginWorkspaceAuthority } from "./plugin-runtime/sandbox-policy.js";
|
|
7
|
+
import { resolveProjectMcpWorkspaceAuthority } from "./project-mcp-trust.js";
|
|
8
|
+
|
|
9
|
+
export const MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE =
|
|
10
|
+
"CC_MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED";
|
|
11
|
+
export const MCP_STDIO_SANDBOX_CWD_INVALID_CODE =
|
|
12
|
+
"CC_MCP_STDIO_SANDBOX_CWD_INVALID";
|
|
13
|
+
|
|
14
|
+
function workspaceError(code, message, cause) {
|
|
15
|
+
const error = new Error(message, cause ? { cause } : undefined);
|
|
16
|
+
error.code = code;
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function dataProperty(owner, key, fallback) {
|
|
21
|
+
if (!owner || typeof owner !== "object" || isProxy(owner)) {
|
|
22
|
+
throw workspaceError(
|
|
23
|
+
MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
|
|
24
|
+
"MCP stdio sandbox config must be a non-Proxy object",
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, key);
|
|
28
|
+
if (!descriptor) return fallback;
|
|
29
|
+
if (!("value" in descriptor)) {
|
|
30
|
+
throw workspaceError(
|
|
31
|
+
MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
|
|
32
|
+
`MCP stdio sandbox config.${key} must be an own data property`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return descriptor.value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function canonicalDirectory(value, label) {
|
|
39
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
40
|
+
throw workspaceError(
|
|
41
|
+
MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
|
|
42
|
+
`${label} must be a non-empty directory path`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const canonical = fs.realpathSync.native(path.resolve(value));
|
|
47
|
+
if (!fs.statSync(canonical).isDirectory()) {
|
|
48
|
+
throw new Error("not a directory");
|
|
49
|
+
}
|
|
50
|
+
return canonical;
|
|
51
|
+
} catch (cause) {
|
|
52
|
+
throw workspaceError(
|
|
53
|
+
MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
|
|
54
|
+
`${label} must resolve to an existing directory`,
|
|
55
|
+
cause,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isPathInside(root, candidate) {
|
|
61
|
+
const relative = path.relative(root, candidate);
|
|
62
|
+
return (
|
|
63
|
+
relative === "" ||
|
|
64
|
+
(relative !== ".." &&
|
|
65
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
66
|
+
!path.isAbsolute(relative))
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sandboxPolicyFromConfig(config, serverName) {
|
|
71
|
+
const raw = dataProperty(config, "sandboxPolicy", null);
|
|
72
|
+
return normalizeMcpSandboxPolicy(raw, {
|
|
73
|
+
label: `MCP stdio server "${serverName}" sandboxPolicy`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function pluginRootFromConfig(config, deps) {
|
|
78
|
+
if (dataProperty(config, "origin", null) !== "plugin:mcp") return null;
|
|
79
|
+
const resolvePlugin =
|
|
80
|
+
deps.resolvePluginWorkspaceAuthority || resolvePluginWorkspaceAuthority;
|
|
81
|
+
return resolvePlugin(dataProperty(config, "pluginWorkspaceAuthority", null), {
|
|
82
|
+
origin: "plugin:mcp",
|
|
83
|
+
pluginId: dataProperty(config, "pluginId", null),
|
|
84
|
+
pluginVersion: dataProperty(config, "pluginVersion", null),
|
|
85
|
+
pluginSource: dataProperty(config, "pluginSource", null),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isProjectFileSource(configScope, configSource) {
|
|
90
|
+
return (
|
|
91
|
+
configScope === "project" &&
|
|
92
|
+
typeof configSource === "string" &&
|
|
93
|
+
path.basename(configSource).toLowerCase() === ".mcp.json"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function requirePublicWorkspaceRoot(
|
|
98
|
+
serverName,
|
|
99
|
+
config,
|
|
100
|
+
workspaceBinding,
|
|
101
|
+
deps,
|
|
102
|
+
) {
|
|
103
|
+
const resolveHost =
|
|
104
|
+
deps.resolveHostWorkspaceBinding || resolveHostHooksV2WorkspaceBinding;
|
|
105
|
+
const binding = resolveHost(workspaceBinding);
|
|
106
|
+
if (!binding?.workspaceRoot) {
|
|
107
|
+
throw workspaceError(
|
|
108
|
+
MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
|
|
109
|
+
`Policy-bearing MCP stdio server "${serverName}" requires a trusted host workspace`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const hostRoot = canonicalDirectory(
|
|
113
|
+
binding.workspaceRoot,
|
|
114
|
+
`MCP stdio server "${serverName}" host workspace`,
|
|
115
|
+
);
|
|
116
|
+
const configScope = dataProperty(config, "configScope", null);
|
|
117
|
+
const projectPath = dataProperty(config, "projectPath", null);
|
|
118
|
+
const configSource = dataProperty(config, "configSource", null);
|
|
119
|
+
const projectAuthority = dataProperty(
|
|
120
|
+
config,
|
|
121
|
+
"projectMcpWorkspaceAuthority",
|
|
122
|
+
null,
|
|
123
|
+
);
|
|
124
|
+
const projectFileSource = isProjectFileSource(configScope, configSource);
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
configScope === "project" &&
|
|
128
|
+
(projectFileSource || projectAuthority) &&
|
|
129
|
+
!projectPath
|
|
130
|
+
) {
|
|
131
|
+
throw workspaceError(
|
|
132
|
+
MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
|
|
133
|
+
`Project MCP stdio server "${serverName}" is missing its project workspace`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if ((configScope === "local" || configScope === "project") && projectPath) {
|
|
138
|
+
const projectRoot = canonicalDirectory(
|
|
139
|
+
projectPath,
|
|
140
|
+
`MCP stdio server "${serverName}" project workspace`,
|
|
141
|
+
);
|
|
142
|
+
if (!isPathInside(projectRoot, hostRoot)) {
|
|
143
|
+
throw workspaceError(
|
|
144
|
+
MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
|
|
145
|
+
`MCP stdio server "${serverName}" does not belong to the trusted host workspace`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (projectAuthority || projectFileSource) {
|
|
150
|
+
const resolveProject =
|
|
151
|
+
deps.resolveProjectMcpWorkspaceAuthority ||
|
|
152
|
+
resolveProjectMcpWorkspaceAuthority;
|
|
153
|
+
const authorityRoot = resolveProject(projectAuthority, {
|
|
154
|
+
configSource,
|
|
155
|
+
serverName,
|
|
156
|
+
url: dataProperty(config, "url", null),
|
|
157
|
+
transport: dataProperty(config, "transport", null),
|
|
158
|
+
headersHelper: dataProperty(config, "headersHelper", null),
|
|
159
|
+
});
|
|
160
|
+
let canonicalAuthorityRoot = null;
|
|
161
|
+
try {
|
|
162
|
+
canonicalAuthorityRoot = authorityRoot
|
|
163
|
+
? canonicalDirectory(
|
|
164
|
+
authorityRoot,
|
|
165
|
+
`MCP stdio server "${serverName}" project authority`,
|
|
166
|
+
)
|
|
167
|
+
: null;
|
|
168
|
+
} catch {
|
|
169
|
+
canonicalAuthorityRoot = null;
|
|
170
|
+
}
|
|
171
|
+
if (canonicalAuthorityRoot !== projectRoot) {
|
|
172
|
+
throw workspaceError(
|
|
173
|
+
MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
|
|
174
|
+
`Project MCP stdio server "${serverName}" is missing its trusted file authority`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return hostRoot;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function requestedWorkingDirectory(config, trustedRoot, serverName) {
|
|
184
|
+
const requested = dataProperty(config, "cwd", null);
|
|
185
|
+
if (requested == null || requested === "") return trustedRoot;
|
|
186
|
+
if (typeof requested !== "string" || requested.includes("\0")) {
|
|
187
|
+
throw workspaceError(
|
|
188
|
+
MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
|
|
189
|
+
`MCP stdio server "${serverName}" cwd must be a path`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const candidate = canonicalDirectory(
|
|
193
|
+
path.isAbsolute(requested)
|
|
194
|
+
? requested
|
|
195
|
+
: path.resolve(trustedRoot, requested),
|
|
196
|
+
`MCP stdio server "${serverName}" cwd`,
|
|
197
|
+
);
|
|
198
|
+
if (!isPathInside(trustedRoot, candidate)) {
|
|
199
|
+
throw workspaceError(
|
|
200
|
+
MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
|
|
201
|
+
`MCP stdio server "${serverName}" cwd escapes its trusted workspace`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return candidate;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Resolve source-declared MCP isolation against an opaque host/plugin
|
|
209
|
+
* workspace capability. A policy-bearing launch never derives authority from
|
|
210
|
+
* process.cwd(); the selected cwd is canonical, existing, and contained by the
|
|
211
|
+
* trusted root before executable identity or Broker contract issuance.
|
|
212
|
+
*/
|
|
213
|
+
export function resolveMcpStdioSandboxContext(
|
|
214
|
+
{ serverName, config, workspaceBinding },
|
|
215
|
+
deps = {},
|
|
216
|
+
) {
|
|
217
|
+
const name = String(serverName || "unknown");
|
|
218
|
+
const sandboxPolicy = sandboxPolicyFromConfig(config, name);
|
|
219
|
+
const pluginWorkspaceRoot = pluginRootFromConfig(config, deps);
|
|
220
|
+
|
|
221
|
+
if (!sandboxPolicy) {
|
|
222
|
+
return Object.freeze({
|
|
223
|
+
sandboxPolicy: null,
|
|
224
|
+
pluginWorkspaceRoot,
|
|
225
|
+
workingDirectory: pluginWorkspaceRoot,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
dataProperty(config, "origin", null) === "plugin:mcp" &&
|
|
231
|
+
!pluginWorkspaceRoot
|
|
232
|
+
) {
|
|
233
|
+
throw workspaceError(
|
|
234
|
+
MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
|
|
235
|
+
`Plugin MCP stdio server "${name}" is missing its trusted workspace authority`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const trustedRoot = pluginWorkspaceRoot
|
|
240
|
+
? canonicalDirectory(
|
|
241
|
+
pluginWorkspaceRoot,
|
|
242
|
+
`Plugin MCP stdio server "${name}" workspace`,
|
|
243
|
+
)
|
|
244
|
+
: requirePublicWorkspaceRoot(name, config, workspaceBinding, deps);
|
|
245
|
+
const workingDirectory = requestedWorkingDirectory(config, trustedRoot, name);
|
|
246
|
+
|
|
247
|
+
return Object.freeze({
|
|
248
|
+
sandboxPolicy,
|
|
249
|
+
pluginWorkspaceRoot,
|
|
250
|
+
workingDirectory,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
@@ -20,6 +20,28 @@ const SUPPORTED_BOUNDARIES = new Set(PLUGIN_SANDBOX_BOUNDARIES);
|
|
|
20
20
|
const PLUGIN_WORKSPACE_AUTHORITY_KIND = "trusted-plugin-workspace";
|
|
21
21
|
const pluginWorkspaceAuthorities = new WeakMap();
|
|
22
22
|
|
|
23
|
+
function directoryGeneration(stat) {
|
|
24
|
+
return stat.birthtimeNs > 0n
|
|
25
|
+
? `birth:${stat.birthtimeNs.toString()}`
|
|
26
|
+
: `ctime:${stat.ctimeNs.toString()}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function directoryIdentity(stat) {
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
device: stat.dev,
|
|
32
|
+
inode: stat.ino,
|
|
33
|
+
generation: directoryGeneration(stat),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sameDirectoryIdentity(left, right) {
|
|
38
|
+
return (
|
|
39
|
+
left.device === right.dev &&
|
|
40
|
+
left.inode === right.ino &&
|
|
41
|
+
left.generation === directoryGeneration(right)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
function invalidPolicy(message) {
|
|
24
46
|
const error = new Error(message);
|
|
25
47
|
error.code = "ERR_PLUGIN_SANDBOX_POLICY_INVALID";
|
|
@@ -69,7 +91,7 @@ export function issuePluginWorkspaceAuthority(value = {}) {
|
|
|
69
91
|
try {
|
|
70
92
|
const implementation = fs.realpathSync.native || fs.realpathSync;
|
|
71
93
|
canonicalRoot = implementation.call(fs.realpathSync, requestedRoot);
|
|
72
|
-
stat = fs.statSync(canonicalRoot);
|
|
94
|
+
stat = fs.statSync(canonicalRoot, { bigint: true });
|
|
73
95
|
} catch {
|
|
74
96
|
throw invalidPolicy("trusted plugin workspace root is unavailable");
|
|
75
97
|
}
|
|
@@ -92,7 +114,11 @@ export function issuePluginWorkspaceAuthority(value = {}) {
|
|
|
92
114
|
});
|
|
93
115
|
pluginWorkspaceAuthorities.set(
|
|
94
116
|
authority,
|
|
95
|
-
Object.freeze({
|
|
117
|
+
Object.freeze({
|
|
118
|
+
root: canonicalRoot,
|
|
119
|
+
identity: directoryIdentity(stat),
|
|
120
|
+
provenance,
|
|
121
|
+
}),
|
|
96
122
|
);
|
|
97
123
|
return authority;
|
|
98
124
|
}
|
|
@@ -117,6 +143,20 @@ export function resolvePluginWorkspaceAuthority(authority, provenance = {}) {
|
|
|
117
143
|
) {
|
|
118
144
|
return null;
|
|
119
145
|
}
|
|
146
|
+
try {
|
|
147
|
+
const implementation = fs.realpathSync.native || fs.realpathSync;
|
|
148
|
+
const currentRoot = implementation.call(fs.realpathSync, binding.root);
|
|
149
|
+
const currentStat = fs.statSync(currentRoot, { bigint: true });
|
|
150
|
+
if (
|
|
151
|
+
currentRoot !== binding.root ||
|
|
152
|
+
!currentStat.isDirectory() ||
|
|
153
|
+
!sameDirectoryIdentity(binding.identity, currentStat)
|
|
154
|
+
) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
120
160
|
return binding.root;
|
|
121
161
|
}
|
|
122
162
|
|
|
@@ -1320,6 +1320,20 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1320
1320
|
"pluginTreeSnapshotContractBound",
|
|
1321
1321
|
"pluginTreeSnapshotAtomic",
|
|
1322
1322
|
];
|
|
1323
|
+
const nodePluginTreeSnapshotEvidence =
|
|
1324
|
+
plan.runtimeProbe.kind === "linux-bwrap-plugin-node-policy-v1" &&
|
|
1325
|
+
plan.runtimeProbe.targetRuntime === "node" &&
|
|
1326
|
+
plan.runtimeProbe.contentSnapshotScope === "plugin-entry-source";
|
|
1327
|
+
const nativePluginRuntimeEvidence =
|
|
1328
|
+
(plan.runtimeProbe.kind ===
|
|
1329
|
+
"linux-bwrap-plugin-native-static-elf-policy-v1" &&
|
|
1330
|
+
plan.runtimeProbe.targetRuntime === "native-static-elf") ||
|
|
1331
|
+
(plan.runtimeProbe.kind ===
|
|
1332
|
+
"linux-bwrap-plugin-native-dynamic-elf-policy-v1" &&
|
|
1333
|
+
plan.runtimeProbe.targetRuntime === "native-dynamic-elf");
|
|
1334
|
+
const nativePluginTreeSnapshotEvidence =
|
|
1335
|
+
nativePluginRuntimeEvidence &&
|
|
1336
|
+
plan.runtimeProbe.contentSnapshotScope === "plugin-entry-executable";
|
|
1323
1337
|
if (
|
|
1324
1338
|
pluginTreeSnapshot &&
|
|
1325
1339
|
(plan.applied !== true ||
|
|
@@ -1334,11 +1348,10 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1334
1348
|
plan.runtimeProbe.attempted !== true ||
|
|
1335
1349
|
plan.runtimeProbe.runnable !== true ||
|
|
1336
1350
|
plan.runtimeProbe.reason !== null ||
|
|
1337
|
-
|
|
1351
|
+
(!nodePluginTreeSnapshotEvidence &&
|
|
1352
|
+
!nativePluginTreeSnapshotEvidence) ||
|
|
1338
1353
|
plan.runtimeProbe.probeRuntime !== "node" ||
|
|
1339
|
-
plan.runtimeProbe.targetRuntime !== "node" ||
|
|
1340
1354
|
plan.runtimeProbe.contentSnapshot !== true ||
|
|
1341
|
-
plan.runtimeProbe.contentSnapshotScope !== "plugin-entry-source" ||
|
|
1342
1355
|
plan.runtimeProbe.contentSnapshotMechanism !==
|
|
1343
1356
|
"verified-o_tmpfile-copy-bwrap-ro-bind-data-v1" ||
|
|
1344
1357
|
plan.runtimeProbe.handleAtomic !== false ||
|
|
@@ -1382,6 +1395,18 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1382
1395
|
"Sandbox runtime probe plugin tree snapshot evidence requires pluginTreeContentSnapshot",
|
|
1383
1396
|
);
|
|
1384
1397
|
}
|
|
1398
|
+
const successfulNativeProbe =
|
|
1399
|
+
plan.applied === true &&
|
|
1400
|
+
plan.runtimeProbe.attempted === true &&
|
|
1401
|
+
plan.runtimeProbe.runnable === true &&
|
|
1402
|
+
plan.runtimeProbe.reason === null &&
|
|
1403
|
+
nativePluginRuntimeEvidence;
|
|
1404
|
+
if (successfulNativeProbe && !pluginTreeSnapshot) {
|
|
1405
|
+
throw this._sandboxError(
|
|
1406
|
+
"invalid_sandbox_plan",
|
|
1407
|
+
"Successful Linux native plugin evidence requires a complete plugin tree snapshot",
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1385
1410
|
const supervisorStringFields = [
|
|
1386
1411
|
"supervisorBindingScope",
|
|
1387
1412
|
"supervisorDescriptorBindingMechanism",
|
|
@@ -2585,12 +2610,6 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2585
2610
|
}
|
|
2586
2611
|
|
|
2587
2612
|
_createWorkspaceProcessCloseFence(auditEntry, proc) {
|
|
2588
|
-
if (
|
|
2589
|
-
!Array.isArray(auditEntry.workspaceTransactionIds) ||
|
|
2590
|
-
auditEntry.workspaceTransactionIds.length === 0
|
|
2591
|
-
) {
|
|
2592
|
-
return null;
|
|
2593
|
-
}
|
|
2594
2613
|
return new Promise((resolve) => {
|
|
2595
2614
|
if (typeof proc?.once === "function") {
|
|
2596
2615
|
proc.once("close", (exitCode, signal) => {
|
|
@@ -2612,6 +2631,54 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
2612
2631
|
});
|
|
2613
2632
|
}
|
|
2614
2633
|
|
|
2634
|
+
_postSpawnOwnershipError(
|
|
2635
|
+
error,
|
|
2636
|
+
{ proc, auditEntry, workspaceProcessClosed, cleanup },
|
|
2637
|
+
) {
|
|
2638
|
+
let failure =
|
|
2639
|
+
error instanceof Error
|
|
2640
|
+
? error
|
|
2641
|
+
: new Error(String(error || "spawn failed"));
|
|
2642
|
+
if (
|
|
2643
|
+
failure.processTerminationRequested !== true &&
|
|
2644
|
+
failure.workspaceTerminationRequested !== true
|
|
2645
|
+
) {
|
|
2646
|
+
try {
|
|
2647
|
+
if (typeof proc?.once === "function") proc.once("error", () => {});
|
|
2648
|
+
} catch {
|
|
2649
|
+
// Listener installation failure is itself post-spawn evidence.
|
|
2650
|
+
}
|
|
2651
|
+
try {
|
|
2652
|
+
proc?.kill?.("SIGKILL");
|
|
2653
|
+
} catch {
|
|
2654
|
+
// The close fence remains the only settlement authority.
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
try {
|
|
2658
|
+
cleanup?.();
|
|
2659
|
+
} catch {
|
|
2660
|
+
// Ownership evidence must survive cleanup reporting failures.
|
|
2661
|
+
}
|
|
2662
|
+
const attach = (target) => {
|
|
2663
|
+
target.auditEntry = auditEntry;
|
|
2664
|
+
target.spawnedProcess = proc;
|
|
2665
|
+
target.workspaceProcessClosed = workspaceProcessClosed;
|
|
2666
|
+
target.workspaceTerminationRequested = true;
|
|
2667
|
+
return target;
|
|
2668
|
+
};
|
|
2669
|
+
try {
|
|
2670
|
+
return attach(failure);
|
|
2671
|
+
} catch {
|
|
2672
|
+
const wrapped = new Error(failure.message || "post-spawn failure", {
|
|
2673
|
+
cause: failure,
|
|
2674
|
+
});
|
|
2675
|
+
wrapped.name = failure.name || wrapped.name;
|
|
2676
|
+
if (failure.code !== undefined) wrapped.code = failure.code;
|
|
2677
|
+
failure = wrapped;
|
|
2678
|
+
return attach(failure);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2615
2682
|
_runPostSpawnSandbox(proc, plan, auditEntry) {
|
|
2616
2683
|
if (!plan.applied || !plan.postSpawn.required) {
|
|
2617
2684
|
this._applySandboxAudit(auditEntry, plan, plan.applied);
|
|
@@ -3328,6 +3395,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
3328
3395
|
// Use native spawn from _native (set by patch-child-process.js)
|
|
3329
3396
|
const nativeSpawnFn = this._native?.spawn || nativeSpawn;
|
|
3330
3397
|
let proc;
|
|
3398
|
+
let workspaceProcessClosed = null;
|
|
3331
3399
|
try {
|
|
3332
3400
|
if (mcpStdioExecutableIdentityAuthority !== undefined) {
|
|
3333
3401
|
const admitted = consumeMcpStdioExecutableIdentityAuthority(
|
|
@@ -3341,16 +3409,20 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
3341
3409
|
auditEntry.mcpStdioExecutableIdentityDigest = admitted.identityDigest;
|
|
3342
3410
|
}
|
|
3343
3411
|
proc = nativeSpawnFn(command, args, optsForSpawn);
|
|
3412
|
+
workspaceProcessClosed = this._createWorkspaceProcessCloseFence(
|
|
3413
|
+
auditEntry,
|
|
3414
|
+
proc,
|
|
3415
|
+
);
|
|
3344
3416
|
this._bindWorkspaceTransactionProcess(auditEntry, proc);
|
|
3345
3417
|
} catch (spawnError) {
|
|
3346
|
-
if (proc
|
|
3347
|
-
|
|
3348
|
-
proc
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3418
|
+
if (proc) {
|
|
3419
|
+
throw this._postSpawnOwnershipError(spawnError, {
|
|
3420
|
+
proc,
|
|
3421
|
+
auditEntry,
|
|
3422
|
+
workspaceProcessClosed,
|
|
3423
|
+
cleanup: sandboxPlan.cleanup,
|
|
3424
|
+
});
|
|
3425
|
+
} else {
|
|
3354
3426
|
try {
|
|
3355
3427
|
this._settleWorkspaceTransactionSpawn(auditEntry, {
|
|
3356
3428
|
error: spawnError.message,
|
|
@@ -3363,102 +3435,105 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
3363
3435
|
throw spawnError;
|
|
3364
3436
|
}
|
|
3365
3437
|
auditEntry.pid = proc.pid;
|
|
3366
|
-
|
|
3367
|
-
auditEntry,
|
|
3368
|
-
proc,
|
|
3369
|
-
);
|
|
3370
|
-
const cleanupSandbox = this._scheduleSandboxCleanup(
|
|
3371
|
-
proc,
|
|
3372
|
-
sandboxPlan.cleanup,
|
|
3373
|
-
);
|
|
3374
|
-
if (
|
|
3375
|
-
sandboxPlan.applied === true &&
|
|
3376
|
-
(sandboxPlan.backend === "linux-bwrap-workspace" ||
|
|
3377
|
-
sandboxPlan.backend === "linux-bwrap" ||
|
|
3378
|
-
sandboxPlan.backend === "linux-fd-code-snapshot") &&
|
|
3379
|
-
sandboxPlan.postSpawn.required === false
|
|
3380
|
-
) {
|
|
3381
|
-
// child_process.spawn() has synchronously duplicated every stdio entry
|
|
3382
|
-
// into the launched process before returning. The generic/direct bwrap
|
|
3383
|
-
// backends and the MCP anonymous-code snapshot backend are complete
|
|
3384
|
-
// pre-spawn plans with no parent-side enforcement handle. Retaining
|
|
3385
|
-
// their pinned descriptors until a long-lived process exits would turn
|
|
3386
|
-
// each child into an avoidable FD leak.
|
|
3387
|
-
cleanupSandbox();
|
|
3388
|
-
}
|
|
3438
|
+
let cleanupSandbox = sandboxPlan.cleanup;
|
|
3389
3439
|
try {
|
|
3390
|
-
this.
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3440
|
+
cleanupSandbox = this._scheduleSandboxCleanup(proc, sandboxPlan.cleanup);
|
|
3441
|
+
if (
|
|
3442
|
+
sandboxPlan.applied === true &&
|
|
3443
|
+
(sandboxPlan.backend === "linux-bwrap-workspace" ||
|
|
3444
|
+
sandboxPlan.backend === "linux-bwrap" ||
|
|
3445
|
+
sandboxPlan.backend === "linux-fd-code-snapshot") &&
|
|
3446
|
+
sandboxPlan.postSpawn.required === false
|
|
3447
|
+
) {
|
|
3448
|
+
// child_process.spawn() has synchronously duplicated every stdio entry
|
|
3449
|
+
// into the launched process before returning. The generic/direct bwrap
|
|
3450
|
+
// backends and the MCP anonymous-code snapshot backend are complete
|
|
3451
|
+
// pre-spawn plans with no parent-side enforcement handle. Retaining
|
|
3452
|
+
// their pinned descriptors until a long-lived process exits would turn
|
|
3453
|
+
// each child into an avoidable FD leak.
|
|
3454
|
+
cleanupSandbox();
|
|
3398
3455
|
}
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3456
|
+
try {
|
|
3457
|
+
this._runPostSpawnSandbox(proc, sandboxPlan, auditEntry);
|
|
3458
|
+
this._updateWorkspaceTransactionProcessGuarantees(auditEntry);
|
|
3459
|
+
auditEntry.pid = proc.pid;
|
|
3460
|
+
if (Number.isSafeInteger(proc.sandboxWrapperPid)) {
|
|
3461
|
+
auditEntry.sandboxWrapperPid = proc.sandboxWrapperPid;
|
|
3462
|
+
}
|
|
3463
|
+
if (Number.isSafeInteger(proc.sandboxTargetPid)) {
|
|
3464
|
+
auditEntry.sandboxTargetPid = proc.sandboxTargetPid;
|
|
3465
|
+
}
|
|
3466
|
+
} catch (postSpawnError) {
|
|
3467
|
+
if (postSpawnError.processTerminationRequested !== true) {
|
|
3468
|
+
try {
|
|
3469
|
+
proc.kill?.("SIGKILL");
|
|
3470
|
+
} catch {
|
|
3471
|
+
// The close fence remains pending and recovery stays fail-closed.
|
|
3472
|
+
}
|
|
3405
3473
|
}
|
|
3474
|
+
cleanupSandbox();
|
|
3475
|
+
this._recordSandboxDenial(auditEntry, postSpawnError, startTime);
|
|
3476
|
+
postSpawnError.auditEntry = auditEntry;
|
|
3477
|
+
postSpawnError.spawnedProcess = proc;
|
|
3478
|
+
postSpawnError.workspaceProcessClosed = workspaceProcessClosed;
|
|
3479
|
+
postSpawnError.workspaceTerminationRequested = true;
|
|
3480
|
+
throw postSpawnError;
|
|
3406
3481
|
}
|
|
3407
|
-
cleanupSandbox();
|
|
3408
|
-
this._recordSandboxDenial(auditEntry, postSpawnError, startTime);
|
|
3409
|
-
postSpawnError.auditEntry = auditEntry;
|
|
3410
|
-
postSpawnError.spawnedProcess = proc;
|
|
3411
|
-
postSpawnError.workspaceProcessClosed = workspaceProcessClosed;
|
|
3412
|
-
postSpawnError.workspaceTerminationRequested = true;
|
|
3413
|
-
throw postSpawnError;
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
this._recordAudit(auditEntry);
|
|
3417
|
-
this._writeRplEntry(auditEntry, "started");
|
|
3418
|
-
this._emitHooksEvent("tool:start", {
|
|
3419
|
-
executionId,
|
|
3420
|
-
toolName: origin,
|
|
3421
|
-
command,
|
|
3422
|
-
args: [...auditEntry.args],
|
|
3423
|
-
cwd,
|
|
3424
|
-
pid: proc.pid,
|
|
3425
|
-
component: origin,
|
|
3426
|
-
});
|
|
3427
3482
|
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
auditEntry.signal = signal;
|
|
3432
|
-
auditEntry.endTime = endTime;
|
|
3433
|
-
auditEntry.durationMs = endTime - startTime;
|
|
3434
|
-
this._writeRplEntry(auditEntry, "completed");
|
|
3435
|
-
this._emitHooksEvent("tool:end", {
|
|
3483
|
+
this._recordAudit(auditEntry);
|
|
3484
|
+
this._writeRplEntry(auditEntry, "started");
|
|
3485
|
+
this._emitHooksEvent("tool:start", {
|
|
3436
3486
|
executionId,
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3487
|
+
toolName: origin,
|
|
3488
|
+
command,
|
|
3489
|
+
args: [...auditEntry.args],
|
|
3490
|
+
cwd,
|
|
3491
|
+
pid: proc.pid,
|
|
3441
3492
|
component: origin,
|
|
3442
3493
|
});
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3494
|
+
|
|
3495
|
+
proc.on("exit", (code, signal) => {
|
|
3496
|
+
const endTime = Date.now();
|
|
3497
|
+
auditEntry.exitCode = code;
|
|
3498
|
+
auditEntry.signal = signal;
|
|
3499
|
+
auditEntry.endTime = endTime;
|
|
3500
|
+
auditEntry.durationMs = endTime - startTime;
|
|
3501
|
+
this._writeRplEntry(auditEntry, "completed");
|
|
3502
|
+
this._emitHooksEvent("tool:end", {
|
|
3503
|
+
executionId,
|
|
3504
|
+
success: code === 0,
|
|
3505
|
+
exitCode: code,
|
|
3506
|
+
signal,
|
|
3507
|
+
durationMs: auditEntry.durationMs,
|
|
3508
|
+
component: origin,
|
|
3509
|
+
});
|
|
3510
|
+
this.emit("exit", auditEntry);
|
|
3455
3511
|
});
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3512
|
+
proc.on("error", (err) => {
|
|
3513
|
+
auditEntry.error = err.message;
|
|
3514
|
+
auditEntry.endTime = Date.now();
|
|
3515
|
+
auditEntry.durationMs = auditEntry.endTime - startTime;
|
|
3516
|
+
this._writeRplEntry(auditEntry, "error", err);
|
|
3517
|
+
this._emitHooksEvent("tool:end", {
|
|
3518
|
+
executionId,
|
|
3519
|
+
success: false,
|
|
3520
|
+
error: err.message,
|
|
3521
|
+
component: origin,
|
|
3522
|
+
});
|
|
3523
|
+
// EventEmitter treats an unhandled `error` event as an exception. A
|
|
3524
|
+
// process ENOENT must reach the child-process listener/callback instead
|
|
3525
|
+
// of crashing callers that did not subscribe to broker diagnostics.
|
|
3526
|
+
if (this.listenerCount("error") > 0) this.emit("error", auditEntry);
|
|
3527
|
+
});
|
|
3528
|
+
return proc;
|
|
3529
|
+
} catch (postNativeError) {
|
|
3530
|
+
throw this._postSpawnOwnershipError(postNativeError, {
|
|
3531
|
+
proc,
|
|
3532
|
+
auditEntry,
|
|
3533
|
+
workspaceProcessClosed,
|
|
3534
|
+
cleanup: cleanupSandbox,
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3462
3537
|
}
|
|
3463
3538
|
|
|
3464
3539
|
spawnSync(command, args, options = {}) {
|