chainlesschain 0.162.188 → 0.162.193

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 (337) hide show
  1. package/README.md +61 -11
  2. package/bin/chainlesschain.js +7 -52
  3. package/completions/_cc +209 -0
  4. package/completions/cc.bash +18 -0
  5. package/completions/cc.fish +13 -0
  6. package/completions/cc.ps1 +226 -0
  7. package/package.json +10 -2
  8. package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
  9. package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
  11. package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
  12. package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
  13. package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
  14. package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
  15. package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
  16. package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
  17. package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
  18. package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
  19. package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
  20. package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
  21. package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
  22. package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
  23. package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
  24. package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
  25. package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
  26. package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
  27. package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
  28. package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
  29. package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
  30. package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
  31. package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
  32. package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
  33. package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
  34. package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
  35. package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
  37. package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
  38. package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
  39. package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
  40. package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
  41. package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
  42. package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
  43. package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
  44. package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
  45. package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
  46. package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
  47. package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
  48. package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
  49. package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
  50. package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
  51. package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
  52. package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
  53. package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
  54. package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
  55. package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
  56. package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
  57. package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
  58. package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
  59. package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
  60. package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
  61. package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
  62. package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
  63. package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
  64. package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
  65. package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
  66. package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
  67. package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
  68. package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
  69. package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
  70. package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
  71. package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
  72. package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
  73. package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
  74. package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
  75. package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
  76. package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
  77. package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
  78. package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
  79. package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
  80. package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
  81. package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
  82. package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
  85. package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
  86. package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
  88. package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
  89. package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
  90. package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
  91. package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
  92. package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
  93. package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
  94. package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
  95. package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
  96. package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
  97. package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
  98. package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
  99. package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
  133. package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
  134. package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
  135. package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
  140. package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
  141. package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
  142. package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
  143. package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
  144. package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
  145. package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
  146. package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
  147. package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
  148. package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
  149. package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
  151. package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
  153. package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
  154. package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
  155. package/src/assets/web-panel/index.html +1 -1
  156. package/src/cli-help.js +181 -0
  157. package/src/command-help-index.json +181 -0
  158. package/src/command-manifest.json +1107 -175
  159. package/src/command-surface-policy.js +197 -0
  160. package/src/commands/agent.js +98 -33
  161. package/src/commands/artifacts.js +400 -2
  162. package/src/commands/background-session.js +41 -3
  163. package/src/commands/checkpoint-managed.js +1059 -0
  164. package/src/commands/checkpoint-restore-recovery.js +1476 -0
  165. package/src/commands/checkpoint.js +950 -22
  166. package/src/commands/compact.js +101 -25
  167. package/src/commands/config.js +506 -20
  168. package/src/commands/doctor.js +4 -1
  169. package/src/commands/hub.js +1 -1
  170. package/src/commands/mcp.js +688 -24
  171. package/src/commands/pack.js +16 -2
  172. package/src/commands/plugin.js +20 -12
  173. package/src/commands/session-mcp-recovery.js +218 -0
  174. package/src/commands/session.js +470 -110
  175. package/src/commands/setup.js +36 -10
  176. package/src/commands/skill.js +181 -110
  177. package/src/commands/status.js +21 -5
  178. package/src/commands/team-distributed.js +3324 -0
  179. package/src/commands/team.js +1130 -74
  180. package/src/commands/update.js +68 -6
  181. package/src/constants.js +11 -0
  182. package/src/data/changelog.json +26 -0
  183. package/src/gateways/ws/action-protocol.js +10 -0
  184. package/src/gateways/ws/session-protocol.js +555 -27
  185. package/src/gateways/ws/ws-agent-handler.js +541 -14
  186. package/src/gateways/ws/ws-session-gateway.js +129 -22
  187. package/src/gateways/ws/ws-session-state.js +431 -0
  188. package/src/harness/background-task-manager.js +233 -36
  189. package/src/harness/jsonl-session-store.js +2551 -167
  190. package/src/harness/mcp-client.js +802 -61
  191. package/src/harness/prompt-compressor.js +227 -27
  192. package/src/harness/session-index.js +89 -38
  193. package/src/harness/session-list-index.js +254 -0
  194. package/src/harness/structured-handoff.js +410 -0
  195. package/src/lazy-dispatch.js +747 -84
  196. package/src/lib/__tests__/logger.test.js +50 -2
  197. package/src/lib/advisor-runtime.js +1047 -0
  198. package/src/lib/agent-core.js +2 -0
  199. package/src/lib/agent-sandbox.js +120 -26
  200. package/src/lib/agent-session-export.js +20 -0
  201. package/src/lib/agent-team/task-lease.js +271 -3
  202. package/src/lib/agent-team/team-adjudication.js +1501 -0
  203. package/src/lib/agent-team/team-budget.js +4 -1
  204. package/src/lib/agent-team/team-control-store.js +582 -0
  205. package/src/lib/agent-team/team-distributed-queue.js +5246 -0
  206. package/src/lib/agent-team/team-process-checkpoint.js +211 -0
  207. package/src/lib/agent-team/team-runner.js +543 -24
  208. package/src/lib/agent-team/team-worktree.js +1403 -95
  209. package/src/lib/artifact-store.js +131 -8
  210. package/src/lib/background-agent-supervisor.js +185 -13
  211. package/src/lib/background-interaction-journal.js +37 -9
  212. package/src/lib/background-launch-profile.js +1058 -0
  213. package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
  214. package/src/lib/checkpoint-restore-orchestrator.js +987 -0
  215. package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
  216. package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
  217. package/src/lib/checkpoint-restore-recovery.js +884 -0
  218. package/src/lib/checkpoint-restore-retention.js +126 -0
  219. package/src/lib/checkpoint-restore-saga.js +5079 -0
  220. package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
  221. package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
  222. package/src/lib/checkpoint-store.js +1688 -97
  223. package/src/lib/checkpoint-timeline-authority.js +522 -0
  224. package/src/lib/checkpoint-timeline.js +642 -0
  225. package/src/lib/config-keys.js +18 -119
  226. package/src/lib/config-manager.js +446 -27
  227. package/src/lib/config-redaction.js +57 -0
  228. package/src/lib/config-schema.js +853 -0
  229. package/src/lib/cowork-mcp-tools.js +5 -0
  230. package/src/lib/cowork-task-runner.js +458 -63
  231. package/src/lib/delivery-coordinator.js +1234 -0
  232. package/src/lib/delivery-evidence.js +450 -0
  233. package/src/lib/doctor-checkup.js +199 -14
  234. package/src/lib/executable-path.js +34 -0
  235. package/src/lib/fatal-handler.js +16 -0
  236. package/src/lib/file-checkpoint.js +4909 -186
  237. package/src/lib/file-lines.js +125 -0
  238. package/src/lib/headless-config-command.js +16 -5
  239. package/src/lib/hook-runner.cjs +139 -38
  240. package/src/lib/hooks-v2-workspace-context.js +10 -0
  241. package/src/lib/impacted-gate-selector.js +424 -0
  242. package/src/lib/interaction-adapter.js +62 -0
  243. package/src/lib/jsonl-session-store.js +1 -35
  244. package/src/lib/logger.js +66 -14
  245. package/src/lib/managed-checkpoint-render.js +60 -0
  246. package/src/lib/managed-tool-checkpoint.js +194 -0
  247. package/src/lib/mcp-call-ledger-store.js +1069 -0
  248. package/src/lib/mcp-call-ledger.js +747 -0
  249. package/src/lib/mcp-client.js +5 -0
  250. package/src/lib/mcp-conflict-scheduler.js +468 -0
  251. package/src/lib/mcp-effect-contract.js +102 -0
  252. package/src/lib/mcp-headers-helper-trust.js +145 -0
  253. package/src/lib/mcp-headers-helper.js +616 -0
  254. package/src/lib/mcp-host-recovery-runtime.js +131 -0
  255. package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
  256. package/src/lib/mcp-recovery-adjudication.js +443 -0
  257. package/src/lib/output-context.js +86 -0
  258. package/src/lib/packer/file-identity.js +170 -0
  259. package/src/lib/packer/index.js +3 -0
  260. package/src/lib/packer/native-update-state.js +277 -0
  261. package/src/lib/packer/pack-update-applier.js +1983 -136
  262. package/src/lib/packer/pack-update-checker.js +34 -0
  263. package/src/lib/packer/pack-update-downloader.js +875 -86
  264. package/src/lib/packer/pack-update-signature.js +86 -0
  265. package/src/lib/packer/pkg-config-generator.js +6 -0
  266. package/src/lib/paths.js +65 -10
  267. package/src/lib/plan-mode.js +831 -14
  268. package/src/lib/plan-persistence.js +540 -0
  269. package/src/lib/plugin-runtime/hooks.js +227 -69
  270. package/src/lib/plugin-runtime/plugin-options.js +189 -44
  271. package/src/lib/pr-automation-policy.js +146 -8
  272. package/src/lib/process-execution-broker/credential-agent.js +7 -0
  273. package/src/lib/process-execution-broker/index.js +491 -13
  274. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
  275. package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
  276. package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
  277. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  278. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  279. package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
  280. package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
  281. package/src/lib/project-instructions.js +165 -29
  282. package/src/lib/project-mcp-trust.js +67 -0
  283. package/src/lib/public-ide-capability-manifest.js +272 -0
  284. package/src/lib/public-ide-capability-validation.js +351 -0
  285. package/src/lib/secret-store.js +267 -43
  286. package/src/lib/secure-file-identity.js +433 -0
  287. package/src/lib/secure-fs.js +1089 -0
  288. package/src/lib/security-audit.js +58 -0
  289. package/src/lib/service-manager.js +34 -13
  290. package/src/lib/session-budget-runtime.js +1452 -0
  291. package/src/lib/session-host-snapshot.js +388 -0
  292. package/src/lib/session-message-provenance.js +429 -0
  293. package/src/lib/session-projection.js +804 -0
  294. package/src/lib/session-resource-budget.js +1495 -0
  295. package/src/lib/settings-hooks.cjs +138 -22
  296. package/src/lib/settings-loader.cjs +25 -2
  297. package/src/lib/side-effect-ledger-store.js +45 -23
  298. package/src/lib/skill-execution-identity.js +217 -0
  299. package/src/lib/skill-loader.js +299 -18
  300. package/src/lib/sub-agent-context.js +236 -28
  301. package/src/lib/sub-agent-registry.js +5 -0
  302. package/src/lib/subagent-contract.js +94 -0
  303. package/src/lib/todo-manager.js +185 -18
  304. package/src/lib/todo-persistence.js +417 -0
  305. package/src/lib/turn-binding-store.js +41 -23
  306. package/src/lib/turn-binding.js +160 -2
  307. package/src/lib/with-file-lock.js +165 -24
  308. package/src/lib/ws-chat-handler.js +21 -0
  309. package/src/program-base.js +18 -0
  310. package/src/repl/advisor-command.js +103 -0
  311. package/src/repl/agent-repl.js +2175 -146
  312. package/src/repl/btw-command.js +93 -10
  313. package/src/repl/clipboard-image.js +119 -0
  314. package/src/repl/config-summary.js +64 -108
  315. package/src/repl/prompt-editor.js +233 -0
  316. package/src/repl/prompt-interactions.js +344 -0
  317. package/src/repl/prompt-stash.js +354 -0
  318. package/src/repl/prompt-suggestions.js +312 -0
  319. package/src/repl/repl-keybindings.js +227 -0
  320. package/src/repl/session-recap.js +217 -0
  321. package/src/repl/slash-command-registry.js +22 -0
  322. package/src/repl/terminal-layout.js +88 -0
  323. package/src/runtime/agent-core.js +1630 -447
  324. package/src/runtime/coding-agent-contract-shared.cjs +13 -1
  325. package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
  326. package/src/runtime/diagnostics.js +32 -13
  327. package/src/runtime/headless-runner.js +310 -118
  328. package/src/runtime/headless-stream.js +356 -95
  329. package/src/runtime/mcp-config.js +148 -14
  330. package/src/runtime/policies/agent-policy.js +14 -0
  331. package/src/runtime/status-diagnostics-lite.js +137 -0
  332. package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
  333. package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
  334. package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
  335. package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
  336. package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
  337. package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
@@ -0,0 +1,1089 @@
1
+ /** Owner-only filesystem helpers for configuration, credentials and sessions. */
2
+ import fs from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ import { homedir } from "node:os";
5
+ import {
6
+ isAbsolute as isNativeAbsolute,
7
+ parse,
8
+ resolve,
9
+ win32,
10
+ posix,
11
+ } from "node:path";
12
+
13
+ export const PRIVATE_DIRECTORY_MODE = 0o700;
14
+ export const PRIVATE_FILE_MODE = 0o600;
15
+
16
+ function samePath(left, right, caseInsensitive = false) {
17
+ const trim = (value) => String(value).replace(/[\\/]+$/, "");
18
+ const a = trim(left);
19
+ const b = trim(right);
20
+ return caseInsensitive ? a.toLowerCase() === b.toLowerCase() : a === b;
21
+ }
22
+
23
+ /** Guard every ACL/chmod repair entry point, not just paths.js callers. */
24
+ export function assertSafeOwnerOnlyPath(target, platform = process.platform) {
25
+ const value = String(target || "").trim();
26
+ if (!value) throw new Error("Owner-only storage path must not be empty");
27
+ const pathApi =
28
+ platform === "win32"
29
+ ? win32
30
+ : platform === "darwin" || platform === "linux"
31
+ ? posix
32
+ : { isAbsolute: isNativeAbsolute, parse, resolve };
33
+ const native = pathApi.resolve(value);
34
+ const nativeRoot = pathApi.parse(native).root;
35
+ const posixPath = posix.isAbsolute(value) ? posix.normalize(value) : null;
36
+ const windowsPath = win32.isAbsolute(value) ? win32.normalize(value) : null;
37
+ const isAbsolute = pathApi.isAbsolute(value);
38
+ const windowsLiteral = value.replaceAll("/", "\\");
39
+ const normalizedWindowsLiteral = String(windowsPath || windowsLiteral);
40
+ const extendedWindowsRoot =
41
+ /^\\\\\?\\[A-Za-z]:\\?$/.test(normalizedWindowsLiteral) ||
42
+ /^\\\\\?\\UNC\\[^\\]+\\[^\\]+\\?$/.test(normalizedWindowsLiteral);
43
+ const normalizedWindowsLower = normalizedWindowsLiteral.toLowerCase();
44
+ const forbiddenWindowsDevice =
45
+ normalizedWindowsLower.startsWith("\\\\?\\") ||
46
+ normalizedWindowsLower.startsWith("\\\\.\\");
47
+ const isRoot =
48
+ samePath(native, nativeRoot, platform === "win32") ||
49
+ (posixPath !== null && samePath(posixPath, posix.parse(posixPath).root)) ||
50
+ (windowsPath !== null &&
51
+ samePath(windowsPath, win32.parse(windowsPath).root, true)) ||
52
+ /^[A-Za-z]:$/.test(value) ||
53
+ extendedWindowsRoot ||
54
+ forbiddenWindowsDevice;
55
+ if (isRoot) {
56
+ const error = new Error(
57
+ `Refusing owner-only permission changes on a filesystem root: ${value}`,
58
+ );
59
+ error.code = "CONFIG_HOME_UNSAFE";
60
+ throw error;
61
+ }
62
+ const caseInsensitive = ["win32", "darwin"].includes(platform);
63
+ const protectedLocation = samePath(
64
+ native,
65
+ pathApi.resolve(homedir()),
66
+ caseInsensitive,
67
+ );
68
+ if (protectedLocation || !isAbsolute) {
69
+ const error = new Error(
70
+ `Refusing owner-only permission changes on a broad protected directory: ${value}`,
71
+ );
72
+ error.code = "CONFIG_HOME_UNSAFE";
73
+ throw error;
74
+ }
75
+ return target;
76
+ }
77
+
78
+ const TRUSTED_DARWIN_SYSTEM_ALIASES = new Map([["/var", "/private/var"]]);
79
+
80
+ /**
81
+ * macOS exposes its real temporary-directory tree through the root-owned
82
+ * `/var -> private/var` compatibility alias. Rejecting that immutable system
83
+ * alias makes every `os.tmpdir()`-backed session fail, while accepting generic
84
+ * links would re-open the chmod/DACL traversal attack this guard prevents.
85
+ *
86
+ * Keep the exception deliberately narrow: exact alias, exact canonical target,
87
+ * root ownership on both entries, and a non-writable canonical directory.
88
+ */
89
+ function isTrustedDarwinSystemAlias(current, entry, deps, platform) {
90
+ if (platform !== "darwin") return false;
91
+ const expected = TRUSTED_DARWIN_SYSTEM_ALIASES.get(current);
92
+ if (!expected || Number(entry?.uid) !== 0) return false;
93
+ try {
94
+ const canonical = deps.realpathSync(current);
95
+ if (!samePath(canonical, expected)) return false;
96
+ const target = deps.lstatSync(canonical);
97
+ const targetMode = Number(target.mode);
98
+ return (
99
+ !target.isSymbolicLink() &&
100
+ target.isDirectory() &&
101
+ Number(target.uid) === 0 &&
102
+ Number.isInteger(targetMode) &&
103
+ (targetMode & 0o022) === 0
104
+ );
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ function readWindowsDirectoryEntry(target, deps) {
111
+ if (typeof deps.readdirSync !== "function") return null;
112
+ const parent = win32.dirname(target);
113
+ if (!parent || samePath(parent, target, true)) return null;
114
+ let entries;
115
+ try {
116
+ entries = deps.readdirSync(parent, { withFileTypes: true });
117
+ } catch {
118
+ return null;
119
+ }
120
+ if (!Array.isArray(entries)) return null;
121
+ const name = win32.basename(target);
122
+ const matches = entries.filter(
123
+ (candidate) => String(candidate?.name || "") === name,
124
+ );
125
+ if (matches.length !== 1) return null;
126
+ const [entry] = matches;
127
+ if (
128
+ typeof entry.isSymbolicLink !== "function" ||
129
+ typeof entry.isDirectory !== "function" ||
130
+ typeof entry.isFile !== "function"
131
+ ) {
132
+ return null;
133
+ }
134
+ try {
135
+ const isSymbolicLink = Boolean(entry.isSymbolicLink());
136
+ const isDirectory = Boolean(entry.isDirectory());
137
+ const isFile = Boolean(entry.isFile());
138
+ if (!isSymbolicLink && isDirectory === isFile) return null;
139
+ return {
140
+ exists: true,
141
+ isDirectory,
142
+ isFile,
143
+ isSymbolicLink,
144
+ };
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ function windowsUnreliablePathStatError(target, cause) {
151
+ const error = new Error(
152
+ `Windows path metadata could not reliably classify an existing path: ${target}`,
153
+ cause ? { cause } : undefined,
154
+ );
155
+ error.code = "WINDOWS_PATH_STAT_UNRELIABLE";
156
+ return error;
157
+ }
158
+
159
+ function assertNoLinkTraversal(
160
+ target,
161
+ deps = fs,
162
+ checkedPaths = null,
163
+ platform = process.platform,
164
+ ) {
165
+ // Dependency-injected cross-platform tests must walk the target platform's
166
+ // path grammar, not the host running Vitest (for example `/var` on Windows).
167
+ const pathApi =
168
+ platform === "win32"
169
+ ? win32
170
+ : platform === "darwin" || platform === "linux"
171
+ ? posix
172
+ : { resolve, parse };
173
+ const absolute = pathApi.resolve(String(target));
174
+ const root = pathApi.parse(absolute).root;
175
+ const segments = absolute
176
+ .slice(root.length)
177
+ .split(/[\\/]+/)
178
+ .filter(Boolean);
179
+ let current = root;
180
+ for (const segment of segments) {
181
+ current = pathApi.resolve(current, segment);
182
+ const cacheKey =
183
+ process.platform === "win32" ? current.toLowerCase() : current;
184
+ if (checkedPaths?.has(cacheKey)) continue;
185
+ let entry;
186
+ try {
187
+ entry = deps.lstatSync(current);
188
+ } catch (error) {
189
+ if (
190
+ platform === "win32" &&
191
+ ["ENOENT", "EPERM", "EACCES"].includes(error?.code)
192
+ ) {
193
+ const directoryEntry = readWindowsDirectoryEntry(current, deps);
194
+ if (directoryEntry) {
195
+ if (directoryEntry.isSymbolicLink) {
196
+ const unsafe = new Error(
197
+ `Refusing owner-only permission changes through a symbolic link or junction: ${current}`,
198
+ );
199
+ unsafe.code = "CONFIG_HOME_UNSAFE";
200
+ throw unsafe;
201
+ }
202
+ checkedPaths?.add(cacheKey);
203
+ continue;
204
+ }
205
+ throw windowsUnreliablePathStatError(current, error);
206
+ }
207
+ if (error?.code === "ENOENT") break;
208
+ throw error;
209
+ }
210
+ if (
211
+ entry.isSymbolicLink() &&
212
+ !isTrustedDarwinSystemAlias(current, entry, deps, platform)
213
+ ) {
214
+ const error = new Error(
215
+ `Refusing owner-only permission changes through a symbolic link or junction: ${current}`,
216
+ );
217
+ error.code = "CONFIG_HOME_UNSAFE";
218
+ throw error;
219
+ }
220
+ checkedPaths?.add(cacheKey);
221
+ }
222
+ return target;
223
+ }
224
+
225
+ const WINDOWS_ACL_SCRIPT = String.raw`
226
+ param([string]$target, [string]$operation)
227
+ $ErrorActionPreference = 'Stop'
228
+ $utf8 = [System.Text.UTF8Encoding]::new($false)
229
+ [Console]::OutputEncoding = $utf8
230
+ $sections =
231
+ [System.Security.AccessControl.AccessControlSections]::Access -bor
232
+ [System.Security.AccessControl.AccessControlSections]::Owner
233
+ $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
234
+ $sid = $identity.User
235
+ $tokenOwner = $identity.Owner
236
+
237
+ function Assert-CcNoReparseTraversal([string]$path) {
238
+ $cursor = [System.IO.Path]::GetFullPath($path)
239
+ while (-not (Test-Path -LiteralPath $cursor)) {
240
+ $parent = [System.IO.Directory]::GetParent($cursor)
241
+ if ($null -eq $parent) { return }
242
+ $cursor = $parent.FullName
243
+ }
244
+ $entry = Get-Item -LiteralPath $cursor -Force
245
+ while ($null -ne $entry) {
246
+ if (($entry.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
247
+ throw "symbolic links and reparse points are not allowed: $($entry.FullName)"
248
+ }
249
+ $entry = $entry.Parent
250
+ }
251
+ }
252
+
253
+ Assert-CcNoReparseTraversal $target
254
+ $item = Get-Item -LiteralPath $target -Force
255
+
256
+ function Read-CcAcl([string]$path) {
257
+ if ([System.IO.Directory]::Exists($path)) {
258
+ return [System.Security.AccessControl.DirectorySecurity]::new($path, $sections)
259
+ }
260
+ return [System.Security.AccessControl.FileSecurity]::new($path, $sections)
261
+ }
262
+
263
+ function Write-CcOwnerOnlyAcl($item, [string]$path) {
264
+ $security = Read-CcAcl $path
265
+ $owner = $security.GetOwner([System.Security.Principal.SecurityIdentifier])
266
+ if ($owner.Value -ne $sid.Value) {
267
+ # Elevated/service tokens can create files whose owner is the token's
268
+ # default owner group (for example BUILTIN\Administrators on hosted CI),
269
+ # even though the token user created the object. Only that exact token-owned
270
+ # case may converge to the narrower user SID; every unrelated owner remains
271
+ # fail-closed.
272
+ if ($null -eq $tokenOwner -or $owner.Value -ne $tokenOwner.Value) {
273
+ throw "owner-only ACL repair refuses a path owned by another identity: $path"
274
+ }
275
+ $security.SetOwner($sid)
276
+ }
277
+ $security.SetAccessRuleProtection($true, $false)
278
+ $existingRules = @($security.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]))
279
+ foreach ($existingRule in $existingRules) {
280
+ $security.RemoveAccessRuleAll($existingRule)
281
+ }
282
+ if ($item.PSIsContainer) {
283
+ $inheritance =
284
+ [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
285
+ [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
286
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
287
+ $sid,
288
+ [System.Security.AccessControl.FileSystemRights]::FullControl,
289
+ $inheritance,
290
+ [System.Security.AccessControl.PropagationFlags]::None,
291
+ [System.Security.AccessControl.AccessControlType]::Allow
292
+ )
293
+ $security.AddAccessRule($rule) | Out-Null
294
+ [System.IO.DirectoryInfo]::new($path).SetAccessControl($security)
295
+ } else {
296
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
297
+ $sid,
298
+ [System.Security.AccessControl.FileSystemRights]::FullControl,
299
+ [System.Security.AccessControl.InheritanceFlags]::None,
300
+ [System.Security.AccessControl.PropagationFlags]::None,
301
+ [System.Security.AccessControl.AccessControlType]::Allow
302
+ )
303
+ $security.AddAccessRule($rule) | Out-Null
304
+ [System.IO.FileInfo]::new($path).SetAccessControl($security)
305
+ }
306
+ }
307
+
308
+ if ($operation -eq 'repair') {
309
+ Write-CcOwnerOnlyAcl $item $target
310
+ }
311
+
312
+ $acl = Read-CcAcl $target
313
+ $owner = $acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value
314
+ $rules = @($acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]))
315
+ $protectionOk = -not $item.PSIsContainer -or $acl.AreAccessRulesProtected
316
+ $ownerOnly =
317
+ $protectionOk -and
318
+ $owner -eq $sid.Value -and
319
+ $rules.Count -eq 1
320
+ if ($ownerOnly) {
321
+ foreach ($rule in $rules) {
322
+ $full = ($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl
323
+ if ($item.PSIsContainer) {
324
+ $requiredInheritance =
325
+ [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
326
+ [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
327
+ $inheritanceOk =
328
+ ($rule.InheritanceFlags -band $requiredInheritance) -eq $requiredInheritance
329
+ } else {
330
+ $inheritanceOk =
331
+ $rule.InheritanceFlags -eq [System.Security.AccessControl.InheritanceFlags]::None
332
+ }
333
+ $propagationOk =
334
+ $rule.PropagationFlags -eq [System.Security.AccessControl.PropagationFlags]::None
335
+ if ($rule.IdentityReference.Value -ne $sid.Value -or
336
+ $rule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow -or
337
+ ($item.PSIsContainer -and $rule.IsInherited) -or
338
+ -not $inheritanceOk -or
339
+ -not $propagationOk -or
340
+ -not $full) {
341
+ $ownerOnly = $false
342
+ break
343
+ }
344
+ }
345
+ }
346
+ [pscustomobject]@{
347
+ ownerOnly = [bool]$ownerOnly
348
+ isDirectory = [bool]$item.PSIsContainer
349
+ ownerSid = $owner
350
+ currentSid = $sid.Value
351
+ tokenOwnerSid = if ($null -eq $tokenOwner) { $null } else { $tokenOwner.Value }
352
+ protected = [bool]$acl.AreAccessRulesProtected
353
+ aceCount = $rules.Count
354
+ } | ConvertTo-Json -Compress
355
+ if (-not $ownerOnly) { exit 4 }
356
+ `;
357
+
358
+ const WINDOWS_ACL_BATCH_SCRIPT = String.raw`
359
+ $ErrorActionPreference = 'Stop'
360
+ $utf8 = [System.Text.UTF8Encoding]::new($false)
361
+ [Console]::InputEncoding = $utf8
362
+ [Console]::OutputEncoding = $utf8
363
+ $sections =
364
+ [System.Security.AccessControl.AccessControlSections]::Access -bor
365
+ [System.Security.AccessControl.AccessControlSections]::Owner
366
+ $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
367
+ $sid = $identity.User
368
+ $tokenOwner = $identity.Owner
369
+ $request = [Console]::In.ReadToEnd() | ConvertFrom-Json
370
+ $operation = [string]$request.operation
371
+ $targets = @($request.targets)
372
+ $expectedKind = [string]$request.expectedKind
373
+ if ($operation -ne 'inspect' -and $operation -ne 'repair' -and $operation -ne 'preflight') {
374
+ throw "unsupported ACL batch operation: $operation"
375
+ }
376
+ if ($expectedKind -ne '' -and $expectedKind -ne 'file' -and $expectedKind -ne 'directory') {
377
+ throw "unsupported expected path kind: $expectedKind"
378
+ }
379
+
380
+ function Assert-CcNoReparseTraversal([string]$path) {
381
+ $cursor = [System.IO.Path]::GetFullPath($path)
382
+ while (-not (Test-Path -LiteralPath $cursor)) {
383
+ $parent = [System.IO.Directory]::GetParent($cursor)
384
+ if ($null -eq $parent) { return }
385
+ $cursor = $parent.FullName
386
+ }
387
+ $entry = Get-Item -LiteralPath $cursor -Force
388
+ while ($null -ne $entry) {
389
+ if (($entry.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
390
+ throw "symbolic links and reparse points are not allowed: $($entry.FullName)"
391
+ }
392
+ $entry = $entry.Parent
393
+ }
394
+ }
395
+
396
+ # A repair is all-or-nothing with respect to path safety. Preflight every
397
+ # existing target before the first SetAccessControl call so a junction root
398
+ # cannot fail after a regular-looking descendant has already been modified.
399
+ if ($operation -eq 'repair' -or $operation -eq 'preflight') {
400
+ $preflightFailures = @()
401
+ foreach ($target in $targets) {
402
+ try {
403
+ Assert-CcNoReparseTraversal $target
404
+ $candidate = Get-Item -LiteralPath $target -Force
405
+ $kindMismatch =
406
+ ($expectedKind -eq 'file' -and $candidate.PSIsContainer) -or
407
+ ($expectedKind -eq 'directory' -and -not $candidate.PSIsContainer)
408
+ if ($kindMismatch) {
409
+ $preflightFailures += [pscustomobject]@{
410
+ target = [string]$target
411
+ error = "expected $expectedKind but found $(if ($candidate.PSIsContainer) { 'directory' } else { 'file' })"
412
+ errorCode = 'EXPECTED_KIND_MISMATCH'
413
+ }
414
+ }
415
+ } catch {
416
+ if ($_.CategoryInfo.Category -ne [System.Management.Automation.ErrorCategory]::ObjectNotFound) {
417
+ $preflightFailures += [pscustomobject]@{
418
+ target = [string]$target
419
+ error = $_.Exception.Message
420
+ errorCode = 'PATH_PREFLIGHT_FAILED'
421
+ }
422
+ }
423
+ }
424
+ }
425
+ if ($preflightFailures.Count -gt 0) {
426
+ $firstFailure = $preflightFailures[0]
427
+ $output = @($targets | ForEach-Object {
428
+ [pscustomobject]@{
429
+ target = [string]$_
430
+ exists = [bool](Test-Path -LiteralPath $_)
431
+ isDirectory = $null
432
+ ok = $false
433
+ error = "ACL batch preflight failed at $($firstFailure.target): $($firstFailure.error)"
434
+ errorCode = $firstFailure.errorCode
435
+ }
436
+ })
437
+ [Console]::Write((ConvertTo-Json -InputObject $output -Compress -Depth 4))
438
+ exit 4
439
+ }
440
+ if ($operation -eq 'preflight') {
441
+ $output = @($targets | ForEach-Object {
442
+ $preflightTarget = [string]$_
443
+ $nativeItem = $null
444
+ try {
445
+ $nativeItem = Get-Item -LiteralPath $preflightTarget -Force
446
+ } catch {
447
+ if ($_.CategoryInfo.Category -ne [System.Management.Automation.ErrorCategory]::ObjectNotFound) {
448
+ throw
449
+ }
450
+ }
451
+ [pscustomobject]@{
452
+ target = $preflightTarget
453
+ exists = [bool]($null -ne $nativeItem)
454
+ isDirectory = if ($null -eq $nativeItem) { $null } else { [bool]$nativeItem.PSIsContainer }
455
+ ok = $true
456
+ error = $null
457
+ }
458
+ })
459
+ [Console]::Write((ConvertTo-Json -InputObject $output -Compress -Depth 4))
460
+ exit 0
461
+ }
462
+ }
463
+
464
+ function Read-CcAcl([string]$path) {
465
+ if ([System.IO.Directory]::Exists($path)) {
466
+ return [System.Security.AccessControl.DirectorySecurity]::new($path, $sections)
467
+ }
468
+ return [System.Security.AccessControl.FileSecurity]::new($path, $sections)
469
+ }
470
+
471
+ function Write-CcOwnerOnlyAcl($item, [string]$path) {
472
+ $security = Read-CcAcl $path
473
+ $owner = $security.GetOwner([System.Security.Principal.SecurityIdentifier])
474
+ if ($owner.Value -ne $sid.Value) {
475
+ if ($null -eq $tokenOwner -or $owner.Value -ne $tokenOwner.Value) {
476
+ throw "owner-only ACL repair refuses a path owned by another identity: $path"
477
+ }
478
+ $security.SetOwner($sid)
479
+ }
480
+ $security.SetAccessRuleProtection($true, $false)
481
+ $existingRules = @($security.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]))
482
+ foreach ($existingRule in $existingRules) {
483
+ $security.RemoveAccessRuleAll($existingRule)
484
+ }
485
+ if ($item.PSIsContainer) {
486
+ $inheritance =
487
+ [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
488
+ [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
489
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
490
+ $sid,
491
+ [System.Security.AccessControl.FileSystemRights]::FullControl,
492
+ $inheritance,
493
+ [System.Security.AccessControl.PropagationFlags]::None,
494
+ [System.Security.AccessControl.AccessControlType]::Allow
495
+ )
496
+ $security.AddAccessRule($rule) | Out-Null
497
+ [System.IO.DirectoryInfo]::new($path).SetAccessControl($security)
498
+ } else {
499
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
500
+ $sid,
501
+ [System.Security.AccessControl.FileSystemRights]::FullControl,
502
+ [System.Security.AccessControl.InheritanceFlags]::None,
503
+ [System.Security.AccessControl.PropagationFlags]::None,
504
+ [System.Security.AccessControl.AccessControlType]::Allow
505
+ )
506
+ $security.AddAccessRule($rule) | Out-Null
507
+ [System.IO.FileInfo]::new($path).SetAccessControl($security)
508
+ }
509
+ }
510
+
511
+ $results = foreach ($target in $targets) {
512
+ $item = $null
513
+ try {
514
+ Assert-CcNoReparseTraversal $target
515
+ $item = Get-Item -LiteralPath $target -Force
516
+ $kindMismatch =
517
+ ($expectedKind -eq 'file' -and $item.PSIsContainer) -or
518
+ ($expectedKind -eq 'directory' -and -not $item.PSIsContainer)
519
+ if ($kindMismatch) {
520
+ [pscustomobject]@{
521
+ target = [string]$target
522
+ exists = $true
523
+ isDirectory = [bool]$item.PSIsContainer
524
+ ok = $false
525
+ error = "expected $expectedKind but found $(if ($item.PSIsContainer) { 'directory' } else { 'file' })"
526
+ errorCode = 'EXPECTED_KIND_MISMATCH'
527
+ }
528
+ continue
529
+ }
530
+ if ($operation -eq 'repair') {
531
+ Write-CcOwnerOnlyAcl $item $target
532
+ }
533
+
534
+ $acl = Read-CcAcl $target
535
+ $owner = $acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value
536
+ $rules = @($acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]))
537
+ $protectionOk = -not $item.PSIsContainer -or $acl.AreAccessRulesProtected
538
+ $ownerOnly =
539
+ $protectionOk -and
540
+ $owner -eq $sid.Value -and
541
+ $rules.Count -eq 1
542
+ if ($ownerOnly) {
543
+ foreach ($rule in $rules) {
544
+ $full = ($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl
545
+ if ($item.PSIsContainer) {
546
+ $requiredInheritance =
547
+ [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
548
+ [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
549
+ $inheritanceOk =
550
+ ($rule.InheritanceFlags -band $requiredInheritance) -eq $requiredInheritance
551
+ } else {
552
+ $inheritanceOk =
553
+ $rule.InheritanceFlags -eq [System.Security.AccessControl.InheritanceFlags]::None
554
+ }
555
+ $propagationOk =
556
+ $rule.PropagationFlags -eq [System.Security.AccessControl.PropagationFlags]::None
557
+ if ($rule.IdentityReference.Value -ne $sid.Value -or
558
+ $rule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow -or
559
+ ($item.PSIsContainer -and $rule.IsInherited) -or
560
+ -not $inheritanceOk -or
561
+ -not $propagationOk -or
562
+ -not $full) {
563
+ $ownerOnly = $false
564
+ break
565
+ }
566
+ }
567
+ }
568
+ [pscustomobject]@{
569
+ target = [string]$target
570
+ exists = $true
571
+ isDirectory = [bool]$item.PSIsContainer
572
+ ok = [bool]$ownerOnly
573
+ ownerSid = $owner
574
+ currentSid = $sid.Value
575
+ tokenOwnerSid = if ($null -eq $tokenOwner) { $null } else { $tokenOwner.Value }
576
+ protected = [bool]$acl.AreAccessRulesProtected
577
+ aceCount = $rules.Count
578
+ error = if ($ownerOnly) { $null } else { 'path is not owner-only' }
579
+ errorCode = $null
580
+ }
581
+ } catch {
582
+ [pscustomobject]@{
583
+ target = [string]$target
584
+ exists = [bool](($null -ne $item) -or (Test-Path -LiteralPath $target))
585
+ isDirectory = $null
586
+ ok = $false
587
+ error = $_.Exception.Message
588
+ errorCode = 'PATH_OPERATION_FAILED'
589
+ }
590
+ }
591
+ }
592
+ $output = [array]@($results)
593
+ [Console]::Write((ConvertTo-Json -InputObject $output -Compress -Depth 4))
594
+ if (@($output | Where-Object { -not $_.ok }).Count -gt 0) { exit 4 }
595
+ `;
596
+
597
+ export const _deps = {
598
+ fs,
599
+ spawnSync,
600
+ platform: () => process.platform,
601
+ };
602
+
603
+ // ACL repair starts a short PowerShell process on Windows. Cache successful
604
+ // production repairs by path for this process; explicit injected-dependency
605
+ // tests and repairPrivatePath() always execute and verify the operation.
606
+ const securedWindowsPaths = new Set();
607
+ const WINDOWS_ACL_BATCH_SIZE = 500;
608
+
609
+ function repairWindowsAclOnce(target, deps, options) {
610
+ const cacheable = !options.deps;
611
+ if (cacheable && securedWindowsPaths.has(target)) {
612
+ return { ok: true, platform: "win32", cached: true };
613
+ }
614
+ const result = windowsAcl(target, "repair", deps);
615
+ if (result.ok && cacheable) securedWindowsPaths.add(target);
616
+ return result;
617
+ }
618
+
619
+ function windowsAcl(target, operation, deps) {
620
+ const result = deps.spawnSync(
621
+ "powershell.exe",
622
+ [
623
+ "-NoLogo",
624
+ "-NoProfile",
625
+ "-NonInteractive",
626
+ "-ExecutionPolicy",
627
+ "Bypass",
628
+ "-Command",
629
+ `& { ${WINDOWS_ACL_SCRIPT} }`,
630
+ target,
631
+ operation,
632
+ ],
633
+ { encoding: "utf8", windowsHide: true, timeout: 15000 },
634
+ );
635
+ let details = null;
636
+ try {
637
+ details = JSON.parse(String(result?.stdout || "").trim());
638
+ } catch {
639
+ details = null;
640
+ }
641
+ if (result?.error) {
642
+ return { ok: false, platform: "win32", error: result.error.message };
643
+ }
644
+ if (result?.status !== 0 || details?.ownerOnly !== true) {
645
+ return {
646
+ ok: false,
647
+ platform: "win32",
648
+ error:
649
+ String(result?.stderr || "").trim() ||
650
+ `owner-only ACL verification exited ${result?.status}`,
651
+ details,
652
+ };
653
+ }
654
+ return { ok: true, platform: "win32", details };
655
+ }
656
+
657
+ function windowsAclBatch(targets, operation, deps, expectedKind = null) {
658
+ if (targets.length === 0) return [];
659
+ const result = deps.spawnSync(
660
+ "powershell.exe",
661
+ [
662
+ "-NoLogo",
663
+ "-NoProfile",
664
+ "-NonInteractive",
665
+ "-ExecutionPolicy",
666
+ "Bypass",
667
+ "-Command",
668
+ `& { ${WINDOWS_ACL_BATCH_SCRIPT} }`,
669
+ ],
670
+ {
671
+ encoding: "utf8",
672
+ input: JSON.stringify({
673
+ operation,
674
+ targets,
675
+ ...(expectedKind ? { expectedKind } : {}),
676
+ }),
677
+ windowsHide: true,
678
+ timeout: 30000,
679
+ maxBuffer: 4 * 1024 * 1024,
680
+ },
681
+ );
682
+ let details = [];
683
+ try {
684
+ const parsed = JSON.parse(String(result?.stdout || "").trim());
685
+ details = Array.isArray(parsed) ? parsed : [parsed];
686
+ } catch {
687
+ details = [];
688
+ }
689
+ const anyFailed = details.some((entry) => entry?.ok !== true);
690
+ const statusOk = result?.status === 0 || (result?.status === 4 && anyFailed);
691
+ const uniqueDetailTargets = new Set(details.map((entry) => entry?.target));
692
+ if (
693
+ result?.error ||
694
+ !statusOk ||
695
+ details.length !== targets.length ||
696
+ uniqueDetailTargets.size !== targets.length
697
+ ) {
698
+ const error =
699
+ result?.error?.message ||
700
+ String(result?.stderr || "").trim() ||
701
+ "owner-only ACL batch verification failed";
702
+ return targets.map((target) => ({
703
+ target,
704
+ ok: false,
705
+ platform: "win32",
706
+ error,
707
+ }));
708
+ }
709
+ const byTarget = new Map(details.map((entry) => [entry.target, entry]));
710
+ return targets.map((target) => {
711
+ const entry = byTarget.get(target);
712
+ return {
713
+ target,
714
+ exists: entry?.exists !== false,
715
+ ok: entry?.ok === true,
716
+ platform: "win32",
717
+ details: entry || null,
718
+ ...(entry?.errorCode ? { errorCode: entry.errorCode } : {}),
719
+ ...(entry?.ok === true
720
+ ? {}
721
+ : {
722
+ error:
723
+ entry?.error ||
724
+ String(result?.stderr || "").trim() ||
725
+ `owner-only ACL batch verification exited ${result?.status}`,
726
+ }),
727
+ };
728
+ });
729
+ }
730
+
731
+ function isWindowsPathStatUnreliable(error, platform) {
732
+ return (
733
+ platform === "win32" &&
734
+ (error?.code === "EPERM" ||
735
+ error?.code === "EACCES" ||
736
+ error?.code === "ENOENT" ||
737
+ error?.code === "WINDOWS_PATH_STAT_UNRELIABLE")
738
+ );
739
+ }
740
+
741
+ function preflightWindowsPaths(targets, deps) {
742
+ const results = [];
743
+ for (
744
+ let offset = 0;
745
+ offset < targets.length;
746
+ offset += WINDOWS_ACL_BATCH_SIZE
747
+ ) {
748
+ results.push(
749
+ ...windowsAclBatch(
750
+ targets.slice(offset, offset + WINDOWS_ACL_BATCH_SIZE),
751
+ "preflight",
752
+ deps,
753
+ ),
754
+ );
755
+ }
756
+ const failed = results.find((entry) => entry.ok !== true);
757
+ if (failed) {
758
+ const error = new Error(
759
+ `Could not verify Windows path ancestors for ${failed.target}: ${failed.error || "native preflight failed"}`,
760
+ );
761
+ error.code = "CONFIG_HOME_UNSAFE";
762
+ throw error;
763
+ }
764
+ return results;
765
+ }
766
+
767
+ function privatePathEntryFromNativeEvidence(target, nativeResult) {
768
+ if (!nativeResult || !samePath(nativeResult.target, target, true)) {
769
+ const unsafe = new Error(
770
+ `Native Windows path evidence did not match requested target: ${target}`,
771
+ );
772
+ unsafe.code = "CONFIG_HOME_UNSAFE";
773
+ throw unsafe;
774
+ }
775
+ if (nativeResult.exists === false) {
776
+ return { exists: false, entry: null };
777
+ }
778
+ const isDirectory = nativeResult.details?.isDirectory;
779
+ if (typeof isDirectory !== "boolean") {
780
+ const unsafe = new Error(
781
+ `Could not classify Windows path type after native preflight: ${target}`,
782
+ );
783
+ unsafe.code = "CONFIG_HOME_UNSAFE";
784
+ throw unsafe;
785
+ }
786
+ return {
787
+ exists: true,
788
+ entry: {
789
+ isDirectory: () => isDirectory,
790
+ isSymbolicLink: () => false,
791
+ },
792
+ };
793
+ }
794
+
795
+ function readPrivatePathEntry(target, deps, platform, nativeEvidence = null) {
796
+ if (platform === "win32" && nativeEvidence) {
797
+ return privatePathEntryFromNativeEvidence(target, nativeEvidence);
798
+ }
799
+ try {
800
+ return { exists: true, entry: deps.fs.lstatSync(target) };
801
+ } catch (error) {
802
+ if (platform !== "win32") {
803
+ if (error?.code === "ENOENT") return { exists: false, entry: null };
804
+ throw error;
805
+ }
806
+ if (!isWindowsPathStatUnreliable(error, platform)) throw error;
807
+ const directoryEntry = readWindowsDirectoryEntry(target, deps.fs);
808
+ if (directoryEntry) {
809
+ return {
810
+ exists: true,
811
+ entry: {
812
+ isDirectory: () => directoryEntry.isDirectory,
813
+ isSymbolicLink: () => directoryEntry.isSymbolicLink,
814
+ },
815
+ };
816
+ }
817
+ const [nativeResult] = preflightWindowsPaths([String(target)], deps);
818
+ return privatePathEntryFromNativeEvidence(target, nativeResult);
819
+ }
820
+ }
821
+
822
+ function assertNoLinkTraversalWithWindowsFallback(
823
+ target,
824
+ deps,
825
+ checkedPaths,
826
+ platform,
827
+ ) {
828
+ try {
829
+ assertNoLinkTraversal(target, deps.fs, checkedPaths, platform);
830
+ return null;
831
+ } catch (error) {
832
+ if (!isWindowsPathStatUnreliable(error, platform)) throw error;
833
+ return preflightWindowsPaths([String(target)], deps)[0];
834
+ }
835
+ }
836
+
837
+ export function inspectPrivatePaths(targets, options = {}) {
838
+ const uniqueTargets = [...new Set((targets || []).map(String))];
839
+ const deps = { ..._deps, ...(options.deps || {}) };
840
+ if ((options.platform || deps.platform()) === "win32") {
841
+ const results = [];
842
+ for (
843
+ let offset = 0;
844
+ offset < uniqueTargets.length;
845
+ offset += WINDOWS_ACL_BATCH_SIZE
846
+ ) {
847
+ results.push(
848
+ ...windowsAclBatch(
849
+ uniqueTargets.slice(offset, offset + WINDOWS_ACL_BATCH_SIZE),
850
+ "inspect",
851
+ deps,
852
+ ),
853
+ );
854
+ }
855
+ return results;
856
+ }
857
+ return uniqueTargets.map((target) => ({
858
+ target,
859
+ ...inspectPrivatePath(target, { ...options, deps }),
860
+ }));
861
+ }
862
+
863
+ export function repairPrivatePaths(targets, options = {}) {
864
+ const uniqueTargets = [...new Set((targets || []).map(String))];
865
+ const deps = { ..._deps, ...(options.deps || {}) };
866
+ const platform = options.platform || deps.platform();
867
+ const checkedPaths = new Set();
868
+ let nativeWindowsPreflightRequired = false;
869
+ // Complete every path-safety check before starting a batched mutation. This
870
+ // prevents a linked trust root from failing only after a descendant changed.
871
+ for (const target of uniqueTargets) {
872
+ assertSafeOwnerOnlyPath(target, platform);
873
+ }
874
+ for (const target of uniqueTargets) {
875
+ try {
876
+ assertNoLinkTraversal(target, deps.fs, checkedPaths, platform);
877
+ } catch (error) {
878
+ if (!isWindowsPathStatUnreliable(error, platform)) throw error;
879
+ nativeWindowsPreflightRequired = true;
880
+ break;
881
+ }
882
+ }
883
+ if (nativeWindowsPreflightRequired) {
884
+ preflightWindowsPaths(uniqueTargets, deps);
885
+ }
886
+ if (platform === "win32") {
887
+ const results = [];
888
+ for (
889
+ let offset = 0;
890
+ offset < uniqueTargets.length;
891
+ offset += WINDOWS_ACL_BATCH_SIZE
892
+ ) {
893
+ results.push(
894
+ ...windowsAclBatch(
895
+ uniqueTargets.slice(offset, offset + WINDOWS_ACL_BATCH_SIZE),
896
+ "repair",
897
+ deps,
898
+ ),
899
+ );
900
+ }
901
+ const failed = results.filter(
902
+ (entry) => !entry.ok && entry.exists !== false,
903
+ );
904
+ if (failed.length > 0) {
905
+ throw new Error(
906
+ `Could not repair owner-only ACL for ${failed[0].target}: ${failed[0].error}`,
907
+ );
908
+ }
909
+ if (!options.deps) {
910
+ for (const result of results) {
911
+ if (result.ok && result.exists !== false) {
912
+ securedWindowsPaths.add(result.target);
913
+ }
914
+ }
915
+ }
916
+ return results;
917
+ }
918
+ return uniqueTargets.map((target) => {
919
+ try {
920
+ return {
921
+ target,
922
+ ...repairPrivatePathAfterPreflight(target, options, deps),
923
+ };
924
+ } catch (error) {
925
+ if (error?.code === "ENOENT") {
926
+ return { target, ok: true, exists: false, skipped: true };
927
+ }
928
+ throw error;
929
+ }
930
+ });
931
+ }
932
+
933
+ export function inspectPrivatePath(target, options = {}) {
934
+ const deps = { ..._deps, ...(options.deps || {}) };
935
+ const platform = options.platform || deps.platform();
936
+ if (platform === "win32") {
937
+ return windowsAclBatch([String(target)], "inspect", deps)[0];
938
+ }
939
+ let stat;
940
+ try {
941
+ stat = deps.fs.lstatSync(target);
942
+ } catch (error) {
943
+ return { ok: false, exists: false, error: error.message };
944
+ }
945
+ if (stat.isSymbolicLink()) {
946
+ return { ok: false, exists: true, error: "symbolic links are not allowed" };
947
+ }
948
+ const expected = stat.isDirectory()
949
+ ? PRIVATE_DIRECTORY_MODE
950
+ : PRIVATE_FILE_MODE;
951
+ const actual = stat.mode & 0o777;
952
+ const ownerOk =
953
+ typeof process.getuid !== "function" || stat.uid === process.getuid();
954
+ return {
955
+ ok: actual === expected && ownerOk,
956
+ exists: true,
957
+ platform: options.platform || deps.platform(),
958
+ expectedMode: expected,
959
+ actualMode: actual,
960
+ ownerOk,
961
+ };
962
+ }
963
+
964
+ export function repairPrivatePath(target, options = {}) {
965
+ const deps = { ..._deps, ...(options.deps || {}) };
966
+ const platform = options.platform || deps.platform();
967
+ assertSafeOwnerOnlyPath(target, platform);
968
+ assertNoLinkTraversalWithWindowsFallback(target, deps, null, platform);
969
+ return repairPrivatePathAfterPreflight(target, options, deps);
970
+ }
971
+
972
+ function repairPrivatePathAfterPreflight(target, options, deps) {
973
+ if ((options.platform || deps.platform()) === "win32") {
974
+ const result = windowsAcl(target, "repair", deps);
975
+ if (!result.ok)
976
+ throw new Error(result.error || "owner-only ACL repair failed");
977
+ if (!options.deps) securedWindowsPaths.add(target);
978
+ return result;
979
+ }
980
+ const stat = deps.fs.lstatSync(target);
981
+ if (stat.isSymbolicLink()) {
982
+ throw new Error(
983
+ `Refusing to change permissions through a symbolic link: ${target}`,
984
+ );
985
+ }
986
+ deps.fs.chmodSync(
987
+ target,
988
+ stat.isDirectory() ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE,
989
+ );
990
+ const result = inspectPrivatePath(target, { ...options, deps });
991
+ if (!result.ok)
992
+ throw new Error(`Could not verify owner-only permissions: ${target}`);
993
+ return result;
994
+ }
995
+
996
+ export function ensurePrivateDirectory(target, options = {}) {
997
+ const deps = { ..._deps, ...(options.deps || {}) };
998
+ const platform = options.platform || deps.platform();
999
+ assertSafeOwnerOnlyPath(target, platform);
1000
+ const nativeEvidence = assertNoLinkTraversalWithWindowsFallback(
1001
+ target,
1002
+ deps,
1003
+ null,
1004
+ platform,
1005
+ );
1006
+ let existed = deps.fs.existsSync(target);
1007
+ if (existed) {
1008
+ const pathEntry = readPrivatePathEntry(
1009
+ target,
1010
+ deps,
1011
+ platform,
1012
+ nativeEvidence,
1013
+ );
1014
+ existed = pathEntry.exists;
1015
+ const existing = pathEntry.entry;
1016
+ if (existed && existing.isSymbolicLink()) {
1017
+ throw new Error(
1018
+ `Refusing owner-only directory through a symbolic link: ${target}`,
1019
+ );
1020
+ }
1021
+ if (
1022
+ existed &&
1023
+ typeof existing.isDirectory === "function" &&
1024
+ !existing.isDirectory()
1025
+ ) {
1026
+ throw new Error(`Expected a directory for owner-only storage: ${target}`);
1027
+ }
1028
+ }
1029
+ deps.fs.mkdirSync(target, {
1030
+ recursive: true,
1031
+ mode: PRIVATE_DIRECTORY_MODE,
1032
+ });
1033
+ if ((options.platform || deps.platform()) !== "win32") {
1034
+ deps.fs.chmodSync(target, PRIVATE_DIRECTORY_MODE);
1035
+ } else if (
1036
+ options.applyWindowsAcl === true ||
1037
+ (!existed && options.applyWindowsAcl !== false)
1038
+ ) {
1039
+ const result = repairWindowsAclOnce(target, deps, options);
1040
+ if (!result.ok && options.failIfUnavailable) {
1041
+ throw new Error(result.error || `Could not secure ${target}`);
1042
+ }
1043
+ }
1044
+ return target;
1045
+ }
1046
+
1047
+ export function ensurePrivateFile(target, options = {}) {
1048
+ const deps = { ..._deps, ...(options.deps || {}) };
1049
+ const platform = options.platform || deps.platform();
1050
+ assertSafeOwnerOnlyPath(target, platform);
1051
+ const nativeEvidence = assertNoLinkTraversalWithWindowsFallback(
1052
+ target,
1053
+ deps,
1054
+ null,
1055
+ platform,
1056
+ );
1057
+ const pathEntry = readPrivatePathEntry(
1058
+ target,
1059
+ deps,
1060
+ platform,
1061
+ nativeEvidence,
1062
+ );
1063
+ if (!pathEntry.exists) return target;
1064
+ const existing = pathEntry.entry;
1065
+ if (existing.isSymbolicLink()) {
1066
+ throw new Error(
1067
+ `Refusing owner-only file through a symbolic link: ${target}`,
1068
+ );
1069
+ }
1070
+ if (typeof existing.isDirectory === "function" && existing.isDirectory()) {
1071
+ throw new Error(`Expected a file for owner-only storage: ${target}`);
1072
+ }
1073
+ if (platform !== "win32") {
1074
+ deps.fs.chmodSync(target, PRIVATE_FILE_MODE);
1075
+ } else if (
1076
+ options.applyWindowsAcl === true &&
1077
+ (options.deps || !securedWindowsPaths.has(target))
1078
+ ) {
1079
+ const [result] = windowsAclBatch([String(target)], "repair", deps, "file");
1080
+ if (result.errorCode === "EXPECTED_KIND_MISMATCH") {
1081
+ throw new Error(`Expected a file for owner-only storage: ${target}`);
1082
+ }
1083
+ if (!result.ok && result.exists !== false && options.failIfUnavailable) {
1084
+ throw new Error(result.error || `Could not secure ${target}`);
1085
+ }
1086
+ if (result.ok && !options.deps) securedWindowsPaths.add(target);
1087
+ }
1088
+ return target;
1089
+ }