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.
Files changed (185) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DIY6KxV-.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-BXH0muPt.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-D39w8vmg.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-CUU1zvLs.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-D8azHO3T.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-D0tAwXTg.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-C29mhJll.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-D6Aa1Gro.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-B1uoHbPQ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-Bs4DLOR5.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-DUYIBSle.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-CNcBPEa4.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-CIY89763.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-CsPYFmgo.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-BBocEge4.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-TQ8Ux0Lf.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-DfBsZTft.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-Cg-I6FVH.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-DTQzfRTp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-DGxEKGpR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-DLZ1RDSr.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-XneICKNI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-BRU00J1-.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BzCbJuAy.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-D7BRyNBG.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-D9DeFIhW.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-6rAtA9Ko.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-8Hqy80pU.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-Cmvkj1rt.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-BRiQUjjw.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-CKN_EzVf.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-BCDHwpwQ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-DK9ovhaB.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-D53pHiHy.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BILPGakp.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DTVCOr9-.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-Hzc1iow4.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-GXfgnGYV.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-B-3TJWsS.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-DwSJMWtK.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-D2uxf-DA.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-DG6kbKK0.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-f5p49EDU.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-FUgHyzSD.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-DCHGee7e.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer-Dm6LlIoa.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-BawA7qAg.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-D6a5wZU1.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-K8WavwnA.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-4JAJYaqx.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-BDWvi8PD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-DF_K4E9i.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-BYeZlesI.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-BIHlgnIc.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Cvf-6B5P.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-CnwfJrRz.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-b-jPXzF4.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-BGs47s1a.js → Providers-CxC7JuYv.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-VLf3gAfL.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-B5vGjRiF.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend--UW4iYs-.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-DMSPcfKR.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-kQXlEHmj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-CxGu3pit.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-Bi5lo8MZ.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-Cq_H5RTh.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-YFbmkaQS.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DLOGdaLm.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CteIxb3D.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-CccopaIZ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-AzfEQKX1.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-ekGo5l77.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-BmZmAgYH.js} +2 -2
  75. package/src/assets/web-panel/assets/Tasks-D7q3Z4uL.js +1 -0
  76. package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-CX1Tgeka.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Zc59jF4i.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-CXOD7y83.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-B1aBHPBo.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-zALlOq_U.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-CQfJMXaq.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-BU0RcM-w.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-IFZ3HsbH.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-BzslgW0v.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-BZAcQNS6.js} +2 -2
  86. package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-CBK1DMi0.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-BlMTPzO-.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-B51-8QVn.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-504RTByU.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-C-GP6IQM.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-DOkIj7ID.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-kB0nCNwN.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-DN7V-_FW.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index--n-ZciV3.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-7jIqHbzw.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-B2-x9bS8.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-BFIFJ5Dr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-B_vGU-Js.js} +1 -1
  99. package/src/assets/web-panel/assets/{index--rq90rL9.js → index-Bkxhaeta.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-By6u5IV1.js} +1 -1
  101. package/src/assets/web-panel/assets/index-Bya64sZE.js +1 -0
  102. package/src/assets/web-panel/assets/{index-caye7AOE.js → index-C0k_Nqg9.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-C3TSohzc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-C4DV_5uA.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-C8UltbXK.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-C9_48OzU.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CKTfZyfU.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-CP2A226L.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-CUhQpiYv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-CX78k-qf.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-CYopNjcJ.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-Cao5DZy7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-CfiEM64n.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-ConjaJro.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-CqcLo5WV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-CySTJKeq.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-D1phyca8.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-D9LyiN-L.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-DA2f1UV3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-DAOpQvl_.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DE-hmI2l.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-DEMPUVa3.js} +1 -1
  123. package/src/assets/web-panel/assets/index-D_FWpDZM.js +1 -0
  124. package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-DaA__-q1.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-FcV8J_Qz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-P2mfY27Z.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-et5JIPHO.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-f4Bo4KXm.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-kAqQXfP6.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-kt_5rfcZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-so8eIIxF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-yYTQZypF.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-CPYEIdpV.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-Cjim9pxs.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-DuU-cgz3.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-DJHNAkta.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-DSQBgdBj.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-CTmT_j5m.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-Lfm6v-r4.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-CgrcPcDi.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CuE3110i.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-DiK5nbpf.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-DPEJdQaZ.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-CnnodGmp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DWF84Yav.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-a5b5Dw2f.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-DAluzXOq.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-Dokg7uAZ.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +1 -1
  151. package/src/commands/agenda.js +6 -5
  152. package/src/commands/agent.js +237 -75
  153. package/src/commands/agents.js +6 -0
  154. package/src/commands/background-session.js +11 -1
  155. package/src/commands/mcp.js +217 -30
  156. package/src/data/changelog.json +14 -1
  157. package/src/harness/mcp-client.js +242 -42
  158. package/src/harness/worktree-isolator.js +26 -3
  159. package/src/lib/agent-worktree.js +41 -2
  160. package/src/lib/background-agent-supervisor.js +790 -186
  161. package/src/lib/background-command-argv.js +318 -0
  162. package/src/lib/background-launch-profile.js +1 -0
  163. package/src/lib/background-phase-reporter.js +22 -2
  164. package/src/lib/background-worktree-policy.js +109 -0
  165. package/src/lib/cowork-mcp-tools.js +51 -6
  166. package/src/lib/cowork-task-runner.js +1 -0
  167. package/src/lib/mcp-headers-helper.js +76 -8
  168. package/src/lib/mcp-sandbox-policy.js +155 -0
  169. package/src/lib/mcp-stdio-execution-authority.js +163 -0
  170. package/src/lib/mcp-stdio-workspace-authority.js +252 -0
  171. package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
  172. package/src/lib/process-execution-broker/index.js +179 -104
  173. package/src/lib/process-execution-broker/platform-sandbox.js +942 -168
  174. package/src/lib/process-tree-termination.js +17 -1
  175. package/src/lib/skill-mcp.js +65 -4
  176. package/src/lib/web-fetch.js +4 -4
  177. package/src/runtime/agent-runtime.js +61 -8
  178. package/src/runtime/mcp-config.js +235 -28
  179. package/src/workers/background-agent-worker.js +492 -71
  180. package/src/assets/web-panel/assets/MarkdownRenderer-Bf3DEqP6.js +0 -1
  181. package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
  182. package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
  183. package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
  184. package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
  185. package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { isProxy } from "node:util/types";
3
4
  import executionBroker from "./process-execution-broker/index.js";
4
5
  import hookShellCommand from "./hook-shell-command.cjs";
5
6
  import {
@@ -9,6 +10,7 @@ import {
9
10
  import { checkLocalMcpHeadersHelperTrust } from "./mcp-headers-helper-trust.js";
10
11
  import { resolvePluginWorkspaceAuthority } from "./plugin-runtime/sandbox-policy.js";
11
12
  import { resolveProjectMcpWorkspaceAuthority } from "./project-mcp-trust.js";
13
+ import { normalizeMcpSandboxPolicy } from "./mcp-sandbox-policy.js";
12
14
 
13
15
  const { buildExplicitHookShellInvocation } = hookShellCommand;
14
16
 
@@ -42,6 +44,45 @@ function helperError(code, message) {
42
44
  return error;
43
45
  }
44
46
 
47
+ function declaredSandboxPolicy(config) {
48
+ if (!config || typeof config !== "object" || isProxy(config)) {
49
+ throw helperError(
50
+ "CC_MCP_SANDBOX_POLICY_INVALID",
51
+ "MCP headersHelper sandbox policy owner must be a non-Proxy object",
52
+ );
53
+ }
54
+ const descriptor = Object.getOwnPropertyDescriptor(config, "sandboxPolicy");
55
+ if (!descriptor) return null;
56
+ if (!("value" in descriptor)) {
57
+ throw helperError(
58
+ "CC_MCP_SANDBOX_POLICY_INVALID",
59
+ "MCP headersHelper sandboxPolicy must be an own data property",
60
+ );
61
+ }
62
+ return normalizeMcpSandboxPolicy(descriptor.value, {
63
+ label: "MCP headersHelper sandboxPolicy",
64
+ });
65
+ }
66
+
67
+ function sandboxExecution(config, sandboxPolicy, fallbackOrigin) {
68
+ if (!sandboxPolicy) return null;
69
+ return Object.freeze({
70
+ origin:
71
+ typeof config.origin === "string" && config.origin.length > 0
72
+ ? config.origin
73
+ : fallbackOrigin,
74
+ policy:
75
+ typeof config.policy === "string" && config.policy.length > 0
76
+ ? config.policy
77
+ : "allow",
78
+ scope:
79
+ typeof config.scope === "string" && config.scope.length > 0
80
+ ? config.scope
81
+ : "mcp",
82
+ sandboxPolicy,
83
+ });
84
+ }
85
+
45
86
  function normalizeHelperCommand(value) {
46
87
  if (
47
88
  typeof value !== "string" ||
@@ -89,6 +130,16 @@ function canonicalHelperCwd(value, realpath = fs.realpathSync.native) {
89
130
  return canonical;
90
131
  }
91
132
 
133
+ function hasInvalidHeaderValueCharacter(value) {
134
+ for (let index = 0; index < value.length; index += 1) {
135
+ const code = value.charCodeAt(index);
136
+ if (code <= 0x08 || (code >= 0x0a && code <= 0x1f) || code === 0x7f) {
137
+ return true;
138
+ }
139
+ }
140
+ return false;
141
+ }
142
+
92
143
  function normalizeHeaderMap(value, source) {
93
144
  if (!value || typeof value !== "object" || Array.isArray(value)) {
94
145
  throw helperError(
@@ -108,7 +159,7 @@ function normalizeHeaderMap(value, source) {
108
159
  if (
109
160
  !HEADER_NAME.test(name) ||
110
161
  typeof headerValue !== "string" ||
111
- /[\0-\x08\x0a-\x1f\x7f]/.test(headerValue) ||
162
+ hasInvalidHeaderValueCharacter(headerValue) ||
112
163
  Buffer.byteLength(headerValue, "utf8") > MAX_HEADER_VALUE_BYTES ||
113
164
  (source === "dynamic" && TRANSPORT_OWNED_HEADERS.has(name.toLowerCase()))
114
165
  ) {
@@ -138,6 +189,7 @@ function isPathInside(root, candidate) {
138
189
  * plugin configs must carry the collector-issued opaque plugin authority.
139
190
  */
140
191
  export function resolveMcpHeadersHelperContext(config = {}, deps = {}) {
192
+ const sandboxPolicy = declaredSandboxPolicy(config);
141
193
  if (config.origin === "plugin:mcp") {
142
194
  const resolvePlugin =
143
195
  deps.resolvePluginWorkspaceAuthority || resolvePluginWorkspaceAuthority;
@@ -156,17 +208,15 @@ export function resolveMcpHeadersHelperContext(config = {}, deps = {}) {
156
208
  return {
157
209
  cwd: pluginRoot,
158
210
  pluginRoot,
159
- execution: {
211
+ execution: Object.freeze({
160
212
  origin: config.origin,
161
213
  policy: config.policy || "allow",
162
214
  scope: config.scope || "mcp",
163
215
  pluginId: config.pluginId,
164
216
  pluginVersion: config.pluginVersion,
165
217
  pluginSource: config.pluginSource,
166
- ...(config.sandboxPolicy
167
- ? { sandboxPolicy: config.sandboxPolicy }
168
- : {}),
169
- },
218
+ ...(sandboxPolicy ? { sandboxPolicy } : {}),
219
+ }),
170
220
  };
171
221
  }
172
222
 
@@ -253,12 +303,30 @@ export function resolveMcpHeadersHelperContext(config = {}, deps = {}) {
253
303
  );
254
304
  }
255
305
  }
256
- return { cwd: binding.workspaceRoot, pluginRoot: null, execution: null };
306
+ return {
307
+ cwd: binding.workspaceRoot,
308
+ pluginRoot: null,
309
+ execution: sandboxExecution(
310
+ config,
311
+ sandboxPolicy,
312
+ `mcp:headers-helper:${scope}`,
313
+ ),
314
+ };
315
+ }
316
+ if (sandboxPolicy && !binding?.workspaceRoot) {
317
+ throw helperError(
318
+ "CC_MCP_HEADERS_HELPER_UNTRUSTED_WORKSPACE",
319
+ "Policy-bearing MCP headersHelper requires a trusted host workspace",
320
+ );
257
321
  }
258
322
  return {
259
323
  cwd: binding?.workspaceRoot || path.resolve(deps.cwd || process.cwd()),
260
324
  pluginRoot: null,
261
- execution: null,
325
+ execution: sandboxExecution(
326
+ config,
327
+ sandboxPolicy,
328
+ `mcp:headers-helper:${scope}`,
329
+ ),
262
330
  };
263
331
  }
264
332
 
@@ -0,0 +1,155 @@
1
+ import { isProxy } from "node:util/types";
2
+
3
+ /**
4
+ * Declarative sandbox boundaries an external MCP source may require.
5
+ *
6
+ * Internal broker guarantees (for example process-tree and code-snapshot) are
7
+ * deliberately not part of this source-controlled contract. A trusted host
8
+ * may add its own non-removable requirements after this value is normalized.
9
+ */
10
+ export const MCP_SANDBOX_BOUNDARIES = Object.freeze(["filesystem", "network"]);
11
+
12
+ export const MCP_SANDBOX_POLICY_INVALID_CODE = "CC_MCP_SANDBOX_POLICY_INVALID";
13
+ export const MCP_STDIO_CWD_INVALID_CODE = "CC_MCP_STDIO_SANDBOX_CWD_INVALID";
14
+
15
+ const SUPPORTED_BOUNDARIES = new Set(MCP_SANDBOX_BOUNDARIES);
16
+ const MAX_BOUNDARY_DECLARATIONS = 32;
17
+
18
+ function invalidPolicy(message) {
19
+ const error = new TypeError(message);
20
+ error.code = MCP_SANDBOX_POLICY_INVALID_CODE;
21
+ return error;
22
+ }
23
+
24
+ function invalidCwd(message) {
25
+ const error = new TypeError(message);
26
+ error.code = MCP_STDIO_CWD_INVALID_CODE;
27
+ return error;
28
+ }
29
+
30
+ /**
31
+ * Read an optional stdio working directory without invoking accessors or Proxy
32
+ * traps. Absence means "retain/default" while null and the empty string are an
33
+ * explicit clear. Every other value must be a NUL-free string.
34
+ */
35
+ export function readMcpStdioCwd(owner, options = {}) {
36
+ const label =
37
+ typeof options.label === "string" && options.label.length > 0
38
+ ? options.label
39
+ : "MCP stdio config";
40
+ if (!owner || typeof owner !== "object" || isProxy(owner)) {
41
+ throw invalidCwd(`${label} must be a non-Proxy object`);
42
+ }
43
+ const descriptor = Object.getOwnPropertyDescriptor(owner, "cwd");
44
+ if (!descriptor) return { present: false, cwd: null };
45
+ if (!("value" in descriptor)) {
46
+ throw invalidCwd(`${label}.cwd must be an own data property`);
47
+ }
48
+ const value = descriptor.value;
49
+ if (value == null || value === "") return { present: true, cwd: null };
50
+ if (typeof value !== "string" || value.includes("\0")) {
51
+ throw invalidCwd(`${label}.cwd must be a NUL-free path string`);
52
+ }
53
+ return { present: true, cwd: value };
54
+ }
55
+
56
+ function ownDataDescriptor(value, key, label) {
57
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
58
+ if (!descriptor || !("value" in descriptor)) {
59
+ throw invalidPolicy(`${label} must be an own data property`);
60
+ }
61
+ return descriptor;
62
+ }
63
+
64
+ function normalizeRequiredBoundaries(value, label) {
65
+ if (!Array.isArray(value) || isProxy(value)) {
66
+ throw invalidPolicy(`${label} must be a non-Proxy array`);
67
+ }
68
+
69
+ const lengthDescriptor = ownDataDescriptor(
70
+ value,
71
+ "length",
72
+ `${label}.length`,
73
+ );
74
+ const length = lengthDescriptor.value;
75
+ if (length > MAX_BOUNDARY_DECLARATIONS) {
76
+ throw invalidPolicy(
77
+ `${label} exceeds ${MAX_BOUNDARY_DECLARATIONS} boundary declarations`,
78
+ );
79
+ }
80
+ const allowedKeys = new Set(["length"]);
81
+ const requiredBoundaries = new Set();
82
+
83
+ for (let index = 0; index < length; index += 1) {
84
+ const key = String(index);
85
+ allowedKeys.add(key);
86
+ const descriptor = ownDataDescriptor(value, key, `${label}[${index}]`);
87
+ const boundary = descriptor.value;
88
+ if (typeof boundary !== "string" || !SUPPORTED_BOUNDARIES.has(boundary)) {
89
+ throw invalidPolicy(`${label} contains an unsupported boundary`);
90
+ }
91
+ requiredBoundaries.add(boundary);
92
+ }
93
+
94
+ for (const key of Reflect.ownKeys(value)) {
95
+ if (typeof key !== "string" || !allowedKeys.has(key)) {
96
+ throw invalidPolicy(
97
+ `${label} contains unsupported property: ${String(key)}`,
98
+ );
99
+ }
100
+ }
101
+
102
+ return Object.freeze([...requiredBoundaries].sort());
103
+ }
104
+
105
+ /**
106
+ * Normalize an MCP source's small declarative sandbox contract.
107
+ *
108
+ * The input is treated as untrusted data: Proxy objects, inherited/accessor
109
+ * fields, non-plain objects, and unknown fields are rejected without invoking
110
+ * user-controlled code. `null` and an empty policy retain the legacy meaning
111
+ * of "no source-declared requirements".
112
+ *
113
+ * @param {unknown} value
114
+ * @param {{label?: string}} [options]
115
+ * @returns {{requiredBoundaries: readonly string[]}|null}
116
+ */
117
+ export function normalizeMcpSandboxPolicy(value, options = {}) {
118
+ const label =
119
+ typeof options.label === "string" && options.label.length > 0
120
+ ? options.label
121
+ : "sandboxPolicy";
122
+
123
+ if (value === undefined || value === null) return null;
124
+ if (typeof value !== "object" || Array.isArray(value) || isProxy(value)) {
125
+ throw invalidPolicy(`${label} must be a non-Proxy plain object`);
126
+ }
127
+
128
+ const prototype = Object.getPrototypeOf(value);
129
+ if (prototype !== Object.prototype && prototype !== null) {
130
+ throw invalidPolicy(`${label} must be a plain object`);
131
+ }
132
+
133
+ const keys = Reflect.ownKeys(value);
134
+ for (const key of keys) {
135
+ if (key !== "requiredBoundaries") {
136
+ throw invalidPolicy(
137
+ `${label} contains unsupported field: ${String(key)}`,
138
+ );
139
+ }
140
+ }
141
+
142
+ if (!keys.includes("requiredBoundaries")) return null;
143
+ const descriptor = ownDataDescriptor(
144
+ value,
145
+ "requiredBoundaries",
146
+ `${label}.requiredBoundaries`,
147
+ );
148
+ const requiredBoundaries = normalizeRequiredBoundaries(
149
+ descriptor.value,
150
+ `${label}.requiredBoundaries`,
151
+ );
152
+ if (requiredBoundaries.length === 0) return null;
153
+
154
+ return Object.freeze({ requiredBoundaries });
155
+ }
@@ -11,7 +11,14 @@
11
11
  */
12
12
 
13
13
  import crypto from "node:crypto";
14
+ import fs from "node:fs";
15
+ import path from "node:path";
14
16
  import { isProxy } from "node:util/types";
17
+ import {
18
+ currentHostHooksV2WorkspaceBinding,
19
+ resolveHostHooksV2WorkspaceBinding,
20
+ } from "./hooks-v2-workspace-context.js";
21
+ import { resolvePluginWorkspaceAuthority } from "./plugin-runtime/sandbox-policy.js";
15
22
 
16
23
  export const MCP_STDIO_LOCAL_CODE_TRUST_REQUIRED_CODE =
17
24
  "CC_MCP_STDIO_LOCAL_CODE_TRUST_REQUIRED";
@@ -175,6 +182,7 @@ function invocationSnapshot(serverName, config) {
175
182
  dataProperty(config, "projectPath", null),
176
183
  "projectPath",
177
184
  ),
185
+ cwd: scalar(dataProperty(config, "cwd", null), "cwd"),
178
186
  pluginId: scalar(dataProperty(config, "pluginId", null), "pluginId"),
179
187
  pluginVersion: scalar(
180
188
  dataProperty(config, "pluginVersion", null),
@@ -205,6 +213,8 @@ function comparableSnapshot(snapshot) {
205
213
  projectMcpWorkspaceAuthority: _projectAuthority,
206
214
  ...plain
207
215
  } = snapshot;
216
+ void _pluginAuthority;
217
+ void _projectAuthority;
208
218
  return plain;
209
219
  }
210
220
 
@@ -224,6 +234,116 @@ function sameSnapshot(expected, actual) {
224
234
  );
225
235
  }
226
236
 
237
+ function directoryGeneration(stat) {
238
+ return stat.birthtimeNs > 0n
239
+ ? `birth:${stat.birthtimeNs.toString()}`
240
+ : `ctime:${stat.ctimeNs.toString()}`;
241
+ }
242
+
243
+ function captureDirectoryIdentity(value, base, label) {
244
+ if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
245
+ throw new TypeError(`${label} must be a non-empty directory path`);
246
+ }
247
+ const requestedPath = path.isAbsolute(value)
248
+ ? path.resolve(value)
249
+ : path.resolve(base, value);
250
+ let canonicalRoot;
251
+ let stat;
252
+ try {
253
+ const implementation = fs.realpathSync.native || fs.realpathSync;
254
+ canonicalRoot = implementation.call(fs.realpathSync, requestedPath);
255
+ stat = fs.statSync(canonicalRoot, { bigint: true });
256
+ } catch (cause) {
257
+ throw new TypeError(`${label} must resolve to an existing directory`, {
258
+ cause,
259
+ });
260
+ }
261
+ if (!stat.isDirectory()) {
262
+ throw new TypeError(`${label} must resolve to a directory`);
263
+ }
264
+ return Object.freeze({
265
+ requestedPath,
266
+ canonicalRoot,
267
+ device: stat.dev,
268
+ inode: stat.ino,
269
+ generation: directoryGeneration(stat),
270
+ });
271
+ }
272
+
273
+ function sameDirectoryIdentity(expected, actual) {
274
+ return (
275
+ expected.canonicalRoot === actual.canonicalRoot &&
276
+ expected.device === actual.device &&
277
+ expected.inode === actual.inode &&
278
+ expected.generation === actual.generation
279
+ );
280
+ }
281
+
282
+ function verifyDirectoryIdentity(binding) {
283
+ try {
284
+ return sameDirectoryIdentity(
285
+ binding,
286
+ captureDirectoryIdentity(
287
+ binding.requestedPath,
288
+ process.cwd(),
289
+ "approved MCP stdio cwd",
290
+ ),
291
+ );
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ function approvedWorkspaceRoot(snapshot, workspaceRoot, workspaceBinding) {
298
+ if (snapshot.origin === "plugin:mcp") {
299
+ const pluginRoot = resolvePluginWorkspaceAuthority(
300
+ snapshot.pluginWorkspaceAuthority,
301
+ {
302
+ origin: snapshot.origin,
303
+ pluginId: snapshot.pluginId,
304
+ pluginVersion: snapshot.pluginVersion,
305
+ pluginSource: snapshot.pluginSource,
306
+ },
307
+ );
308
+ if (pluginRoot) return pluginRoot;
309
+ }
310
+ if (workspaceBinding != null) {
311
+ const resolvedBinding =
312
+ resolveHostHooksV2WorkspaceBinding(workspaceBinding);
313
+ if (!resolvedBinding?.workspaceRoot) {
314
+ throw new TypeError(
315
+ "approved MCP stdio workspace binding is invalid or stale",
316
+ );
317
+ }
318
+ return resolvedBinding.workspaceRoot;
319
+ }
320
+ if (typeof workspaceRoot === "string" && workspaceRoot.length > 0) {
321
+ return workspaceRoot;
322
+ }
323
+ const hostRoot = currentHostHooksV2WorkspaceBinding()?.workspaceRoot;
324
+ if (hostRoot) return hostRoot;
325
+ if (typeof snapshot.projectPath === "string" && snapshot.projectPath) {
326
+ return snapshot.projectPath;
327
+ }
328
+ return process.cwd();
329
+ }
330
+
331
+ function captureApprovedCwd(snapshot, workspaceRoot, workspaceBinding) {
332
+ if (snapshot.sandboxPolicy == null || snapshot.cwd == null) return null;
333
+ return captureDirectoryIdentity(
334
+ snapshot.cwd,
335
+ approvedWorkspaceRoot(snapshot, workspaceRoot, workspaceBinding),
336
+ "approved MCP stdio cwd",
337
+ );
338
+ }
339
+
340
+ function staleDirectoryError(serverName) {
341
+ return authorityError(
342
+ MCP_STDIO_EXECUTION_AUTHORITY_STALE_CODE,
343
+ `MCP stdio working directory for "${String(serverName)}" changed after approval`,
344
+ );
345
+ }
346
+
227
347
  function materializeSnapshot(snapshot) {
228
348
  return {
229
349
  command: snapshot.command,
@@ -237,6 +357,7 @@ function materializeSnapshot(snapshot) {
237
357
  configScope: snapshot.configScope,
238
358
  configSource: snapshot.configSource,
239
359
  projectPath: snapshot.projectPath,
360
+ cwd: snapshot.cwd,
240
361
  pluginId: snapshot.pluginId,
241
362
  pluginVersion: snapshot.pluginVersion,
242
363
  pluginSource: snapshot.pluginSource,
@@ -254,6 +375,8 @@ export function issueMcpStdioExecutionAuthority({
254
375
  config,
255
376
  approvalKind,
256
377
  approvalSource,
378
+ workspaceRoot,
379
+ workspaceBinding,
257
380
  }) {
258
381
  if (!APPROVAL_KINDS.has(approvalKind)) {
259
382
  throw new TypeError("MCP stdio approval kind is not recognized");
@@ -261,6 +384,11 @@ export function issueMcpStdioExecutionAuthority({
261
384
  const source = scalar(approvalSource, "approvalSource", { nullable: false });
262
385
  if (!source.trim()) throw new TypeError("approvalSource must not be empty");
263
386
  const snapshot = invocationSnapshot(serverName, config);
387
+ const cwdIdentity = captureApprovedCwd(
388
+ snapshot,
389
+ workspaceRoot,
390
+ workspaceBinding,
391
+ );
264
392
  const token = Object.freeze({});
265
393
  issuedAuthorities.set(
266
394
  token,
@@ -269,6 +397,7 @@ export function issueMcpStdioExecutionAuthority({
269
397
  approvalSource: source,
270
398
  fingerprint: snapshotDigest(snapshot),
271
399
  snapshot,
400
+ cwdIdentity,
272
401
  }),
273
402
  );
274
403
  return token;
@@ -309,6 +438,9 @@ export function consumeMcpStdioExecutionAuthority(
309
438
  `MCP stdio invocation for "${String(serverName)}" changed after approval`,
310
439
  );
311
440
  }
441
+ if (issued.cwdIdentity && !verifyDirectoryIdentity(issued.cwdIdentity)) {
442
+ throw staleDirectoryError(serverName);
443
+ }
312
444
  const approval = Object.freeze({
313
445
  approvalKind: issued.approvalKind,
314
446
  approvalSource: issued.approvalSource,
@@ -336,6 +468,9 @@ export function renewMcpStdioExecutionAuthority(
336
468
  `MCP stdio reconnect invocation for "${String(serverName)}" changed after approval`,
337
469
  );
338
470
  }
471
+ if (issued.cwdIdentity && !verifyDirectoryIdentity(issued.cwdIdentity)) {
472
+ throw staleDirectoryError(serverName);
473
+ }
339
474
  const token = Object.freeze({});
340
475
  issuedAuthorities.set(token, issued);
341
476
  return token;
@@ -352,6 +487,34 @@ export function materializeApprovedMcpStdioInvocation(approval) {
352
487
  return materializeSnapshot(issued.snapshot);
353
488
  }
354
489
 
490
+ export function verifyMcpStdioApprovedWorkingDirectory(
491
+ approval,
492
+ workingDirectory,
493
+ ) {
494
+ const issued = renewableApprovals.get(approval);
495
+ if (!issued) {
496
+ throw authorityError(
497
+ MCP_STDIO_LOCAL_CODE_TRUST_REQUIRED_CODE,
498
+ "MCP stdio working directory requires a consumed execution approval",
499
+ );
500
+ }
501
+ if (!issued.cwdIdentity) return true;
502
+ let actual;
503
+ try {
504
+ actual = captureDirectoryIdentity(
505
+ workingDirectory,
506
+ process.cwd(),
507
+ "materialized MCP stdio cwd",
508
+ );
509
+ } catch {
510
+ throw staleDirectoryError(issued.snapshot.serverName);
511
+ }
512
+ if (!sameDirectoryIdentity(issued.cwdIdentity, actual)) {
513
+ throw staleDirectoryError(issued.snapshot.serverName);
514
+ }
515
+ return true;
516
+ }
517
+
355
518
  /**
356
519
  * Return only the content-free provenance needed to key a durable executable
357
520
  * identity. The approval object itself remains an unforgeable WeakMap-backed