chainlesschain 0.166.91 → 0.166.93

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 (221) hide show
  1. package/README.md +17 -17
  2. package/package.json +3 -3
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-DgV7xb8F.js → AIOps-oo9rE0gh.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-CgANpkKu.js → ActionButton-CVJVVv4i.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-DnvpX7RB.js → Analytics-CUXuwmia.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-C2bHccBM.js → AppLayout-iM4PvCuY.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-BIhijC9g.js → Artifacts-DKUIJbf9.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-CKRjWGdp.js → Audit-C4_SzFN2.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-CblYtgBE.js → BackgroundAgents-C3427uy-.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-Cq35b78a.js → Backup-BpEQQiog.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-DcB7NoRz.js → BaseInput-C_exQomZ.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-tc3b-s7i.js → Chat-DzpkLtWa.js} +5 -5
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-DX4XmTWm.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-BLD5bH5u.js → Checkbox-CxAC1CU8.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-BLOsoazN.js → Codegen-wDFAGCLp.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-Bdr7JwIx.js → Col-BvwirhMf.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-C84BsWE0.js → Community-BJuA7paY.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-Dzdwd04z.js → Compact-DzV35_u2.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-CCG-R0EV.js → Compliance-CIDPPvr9.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-BYj72SK0.js → Cowork-DyTMxP3N.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-B6iKAwOf.js → Cron-PcNLjMFx.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-BJ-oDC6u.js → Crosschain-B_O7ck6Z.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-BnMckGLr.js → DID-CII35Me1.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-Cyje-gCO.js → Dashboard-DU56fkNz.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-BpNlenjK.js → Dropdown-DHC8HVeJ.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-BhonO7Nv.js → EmailListRenderer-oUOHFNsD.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-DVoNU-Id.js → EvolutionSettings-D7RCAWNl.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-tdn-KC67.js → FamilyGuardDashboard-CGONPZxC.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation--EEVrHhz.js → Federation-BbDlPWcl.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-Cy8zr0-4.js → FormItemContext-BBJbFQQT.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-BuUhpgFi.js → GenericCardRenderer-iuxAero4.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-Ctd3E5Pe.js → Git-Dw0prdY5.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-Ce7dV6aU.js → Governance-DOGqi_uV.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-CD1vExIH.js → Inference-BY5oUGGj.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-Dn64OdQ7.js → KnowledgeGraph-CcE2EVYX.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-PmElVRrT.js → Logs-DyfN4R5w.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-BFiVKf5t.js → MarkdownRenderer-Bj62Aq0M.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-OW4jTXwx.js → Marketplace-CuuuAXUh.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-BEq2Ib4x.js → McpTools-948Fithv.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-Can_P2oK.js → Memory-D4PINvyy.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-DJ8R-dn3.js → MobileBridge-ECKMaLzX.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-B7kV9GOt.js → MobileProjects-CB5MAcvD.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-BfxamolS.js → Mtc-BRzIM3HJ.js} +4 -4
  46. package/src/assets/web-panel/assets/{MtcAudit-DHtuv7av.js → MtcAudit-CGeAXJGn.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-DLkLpvxc.js → Multisig-C_qJ-8K-.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-u4XtJTIt.js → NLProgramming-DAEyCn7T.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-BwuuPJLS.js → Notes-DmD3MEec.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-HEeu-E9v.js → NotificationSettings-Cv4Q3VC9.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-BNlpaFjH.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-Yh-czs3b.js → Organization-DqAFdz6p.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-DmE8mYVM.js → Overflow-8Ye9cIcE.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-CWVck8WN.js → P2P-755SfHoJ.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-DgFqekod.js → PdhVaultBrowser-CgZLiucX.js} +4 -4
  56. package/src/assets/web-panel/assets/{Permissions-BtJBVOHV.js → Permissions-C-vFF_rL.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-6VBObXzd.js → PersonalDataHub-Diz1oXM3.js} +5 -5
  58. package/src/assets/web-panel/assets/{Pipeline-D222FmqY.js → Pipeline-D4kIzsZG.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-Bi9FMikp.js → Privacy-QKLkSAUH.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-Bo52H47H.js → ProjectInit-I0S9o_RT.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-BPkesfOc.js → ProjectSettings-DdgV3-Ho.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-GynohSFX.js → Projects-BJHHegtj.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-DVdMg04n.js → Providers-uVa1zfbS.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-DqJCb595.js → QrScannerModal-YLVpOChR.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-J7loQbXi.js → QuickAsk-535AYH61.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-BI5aURVc.js → Recommend-C_P6eNSw.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-BN2sqGvz.js → RemoteSession-EXH1SbNj.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-BGOND-Fi.js → Reputation-Cxc7d3eU.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-77dfwaUk.js → Row-EDqJy-wS.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-FtBAxS3g.js → RssFeed-C03Xeg9_.js} +2 -2
  71. package/src/assets/web-panel/assets/{Search-BaYVLFb7.js → Search-DsIga9DK.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-LE3Vv6ab.js → Security-DgArMFc-.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-C5pP-Whz.js → Services-MooDpHBP.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CLqKUVsF.js → Skeleton-B10P1Dr6.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-BPjN0SX5.js → Skills-DHLHEkLl.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-14jD70kg.js → Sla-D4RT7d4g.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Dr9ij42W.js → SpeechSettings-D8u6F-LV.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BzLHP7B8.js → SyncSettings-4nvNzRq8.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-YqV9b0rV.js → Tasks-CmCb8zNO.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-CxxAE4PK.js → Templates-CzESIa21.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CRb3PcKJ.js → Tenant-Ch_N3p4d.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-CTzPpBaP.js → Terminal-BZccUf_X.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-DjFUj_hy.js → TimelineRenderer-FrKcNy_3.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-Bbb8tPvx.js → Tokens-4xvLpXfD.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-CSeWGM0W.js → Trigger-84UjyxXd.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-JgESMBo3.js → Trust-M6UtHnji.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CiObLk0T.js → UkeySign-L_xEgtCg.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-CFEZaQ2x.js → VideoEditing-BLJCX9ru.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-DR8ZGDNn.js → Wallet-DwL6uJCj.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-DxZukvu7.js → WebAuthn-Bmn9pdzz.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-CKgSZQwn.js → WorkflowEditor-DSsZ5xG1.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CTxEQE66.js → chat--FGNUaa8.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CAjKMUu3.js → colors-DeuwNdqX.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-BXuz49at.js → compact-item-I4XKxRFU.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-CFN_Hwr9.js → createContext-RoD2p5Ba.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-ctnml4IW.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-BwoSBSfP.js → hasIn-BsvyddUM.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CB3kVd9c.js → index-0tRU1Sv-.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-o9DpkZKf.js → index-4xHc2WjT.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BmQITjMU.js → index-B8-eGLN9.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DY-gBSm8.js → index-BS_uqSTa.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-F2E5RCwz.js → index-BYEVBlCl.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CF9lWi1o.js → index-BcBM065G.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Bu2P6u8m.js → index-BnrL4s-3.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Dv7V2kg6.js → index-BrZPb0On.js} +1 -1
  106. package/src/assets/web-panel/assets/index-C7PN8mh1.js +1 -0
  107. package/src/assets/web-panel/assets/{index-DViK2bkv.js → index-CBazK4-7.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BmXpdcHI.js → index-CPS3zv1c.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bqv_Cray.js → index-CSrZiiAs.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B7kp-zRE.js → index-CVwZMt9Z.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-B0QbLlWn.js → index-CdgKZYgm.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DjF5BOZx.js → index-CesTu01s.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CSmFby3Q.js → index-CiHlSuMl.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B2o_BBjg.js → index-CsLVac9n.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CehdwJ_Q.js → index-D1ONyBTW.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Bj4hIiLW.js → index-D4IZsKbF.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-_ZtCyUdz.js → index-D4P4vpmv.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B9yR7zqr.js → index-D6J8dz7X.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-u9_DM3ZC.js → index-DChy2sHE.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-C8er4W9u.js → index-DKUCl_Lk.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BK4ftQ4d.js → index-DRluW3_r.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B2tEnBPx.js → index-DWUmhNO-.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-IY9bd5az.js → index-DXnpVGG3.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Cp5y7lX_.js → index-DYOpAS6-.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-T3MTE6jB.js → index-DiH-3cR2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DxunxaTw.js → index-DlEfwgzr.js} +1 -1
  127. package/src/assets/web-panel/assets/index-KnbBJAO-.js +1 -0
  128. package/src/assets/web-panel/assets/{index-DTX9CJPx.js → index-R4St_zjR.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Tz5dheZa.js → index-ScgoDU9V.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DmkbAADP.js → index-UWdT_lty.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bw9s2d3c.js → index-VAlor744.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CN53dE_u.js → index-Wco_uvRo.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DpdDnZX_.js → index-ZgFED_Zd.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-IahILygB.js → index-jaHeti4i.js} +3 -3
  135. package/src/assets/web-panel/assets/{index-Cgk0YEiq.js → index-rJDabtw4.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-t2Ch_JKn.js → index-vRiGKIP0.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-BoiGJDoO.js → initDefaultProps-BHT0PacY.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-5eL4E0pA.js → motion-CBYsMtsC.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-DjggDi_1.js → move-DnHfFV4K.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-Ce8Vo2T3.js → mtc-parser-DfGkRYrx.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-D1yOBaO2.js → omit-SjLcPQA9.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-8NLceQe8.js → pickAttrs-CCZHF4ep.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-CsIdBRmQ.js → placementArrow-DjCGn2sJ.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-GM5NE8En.js → responsiveObserve-BCFCl2on.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-44yj3u49.js → slide-DsfmuIZu.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-B8tqtWcX.js → statusUtils-hPKuJUia.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-NhDPBbyB.js → styleChecker-DOXKH7Kf.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-C8Qz1GtN.js → useFlexGapSupport-B_G7yqio.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-ClUv4kih.js → useFs-BXOTOm2l.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BwkOVEh6.js → usePersonalDataHub-CRvie3g7.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode--gMF33R0.js → vnode-D9_zMmww.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-DXg3TqOP.js → zoom-C4tW03qI.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/data/changelog.json +52 -0
  155. package/src/lib/artifact-store.js +51 -3
  156. package/src/lib/bounded-artifact-file-read.js +131 -0
  157. package/src/lib/evolution/evolution-artifact-ports.js +235 -129
  158. package/src/lib/evolution/evolution-eval-cohort-enrollment.js +713 -47
  159. package/src/lib/evolution/evolution-eval-gate.js +146 -0
  160. package/src/lib/evolution/evolution-eval-launch-admission.js +168 -90
  161. package/src/lib/evolution/evolution-eval-ledger-capture.js +50 -0
  162. package/src/lib/evolution/evolution-eval-readonly-audit.js +106 -0
  163. package/src/lib/evolution/evolution-ledger-file-backend.js +20 -0
  164. package/src/lib/evolution/evolution-ledger-ports.js +59 -27
  165. package/src/lib/evolution/evolution-ledger-v2-journal.js +55 -18
  166. package/src/lib/evolution/evolution-workbench-file-resources.js +19 -1
  167. package/src/lib/evolution/rrsi-cohort-registration.js +275 -0
  168. package/src/lib/evolution/rrsi-contracts.js +742 -0
  169. package/src/lib/evolution/rrsi-data.js +196 -0
  170. package/src/lib/evolution/rrsi-evaluation-adapter.js +275 -0
  171. package/src/lib/evolution/rrsi-evaluation-variants.js +267 -0
  172. package/src/lib/evolution/rrsi-group-statistics.js +553 -0
  173. package/src/lib/evolution/rrsi-history-ledger-adapter.js +3286 -0
  174. package/src/lib/evolution/rrsi-installation-route-contracts.js +275 -0
  175. package/src/lib/evolution/rrsi-installation-route-reader.js +282 -0
  176. package/src/lib/evolution/rrsi-native-batch-evidence.js +520 -0
  177. package/src/lib/evolution/rrsi-native-eval-row-collector.js +538 -0
  178. package/src/lib/evolution/rrsi-native-evaluation-batch.js +547 -0
  179. package/src/lib/evolution/rrsi-native-evaluation-plan.js +431 -0
  180. package/src/lib/evolution/rrsi-native-group-statistics-plan.js +121 -0
  181. package/src/lib/evolution/rrsi-native-group-statistics.js +482 -0
  182. package/src/lib/evolution/rrsi-native-quality-receipt-contracts.js +321 -0
  183. package/src/lib/evolution/rrsi-native-quality-receipt.js +256 -0
  184. package/src/lib/evolution/rrsi-native-statistics-protocol.js +330 -0
  185. package/src/lib/evolution/rrsi-observed-text-index.js +978 -0
  186. package/src/lib/evolution/rrsi-parent-binding.js +290 -0
  187. package/src/lib/evolution/rrsi-pm-execution-bridge.js +500 -0
  188. package/src/lib/evolution/rrsi-pm-training-mapping.js +134 -0
  189. package/src/lib/evolution/rrsi-preparation-contracts.js +213 -0
  190. package/src/lib/evolution/rrsi-registry-history-binding.js +193 -0
  191. package/src/lib/evolution/rrsi-registry-runtime-policy.js +648 -0
  192. package/src/lib/evolution/rrsi-registry-store-policy.js +1271 -0
  193. package/src/lib/evolution/rrsi-runtime-composition.js +79 -0
  194. package/src/lib/evolution/rrsi-selector.js +434 -0
  195. package/src/lib/evolution/rrsi-statistics-simulation.js +354 -0
  196. package/src/lib/evolution/rrsi-tenant-index-anchor-contracts.js +223 -0
  197. package/src/lib/evolution/rrsi-tenant-index-anchor-history.js +157 -0
  198. package/src/lib/evolution/skill-candidate-registry.js +216 -29
  199. package/src/lib/evolution/skill-registry-maintenance.js +80 -0
  200. package/src/lib/evolution/skill-registry-writer-control.js +138 -0
  201. package/src/lib/evolution/skill-release-registry.js +414 -169
  202. package/src/lib/formal-quality-eval-runtime.js +2 -14
  203. package/src/lib/goal-store.js +225 -16
  204. package/src/lib/process-execution-broker/windows-native-evaluator-capabilities.js +291 -30
  205. package/src/lib/record-replay/skill-recorder.js +38 -3
  206. package/src/lib/remote-read-loop-guard.js +227 -24
  207. package/src/lib/scheduler-kernel/authority-resolver.js +12 -163
  208. package/src/lib/scheduler-kernel/contract.js +27 -414
  209. package/src/lib/scheduler-kernel/runtime.js +21 -823
  210. package/src/lib/scheduler-kernel/service.js +11 -328
  211. package/src/lib/scheduler-kernel/source-locator-path.js +7 -142
  212. package/src/lib/scheduler-kernel/store.js +67 -5931
  213. package/src/lib/secure-fs.js +16 -1314
  214. package/src/lib/task-progress-tracker.js +6 -1
  215. package/src/lib/with-file-lock.js +122 -20
  216. package/src/runtime/agent-core.js +71 -23
  217. package/src/assets/web-panel/assets/ChatBubbleRenderer-fcc_Tvqu.js +0 -1
  218. package/src/assets/web-panel/assets/OrderTableRenderer-BtvN-Qwh.js +0 -1
  219. package/src/assets/web-panel/assets/devWarning-CfB8DxT2.js +0 -1
  220. package/src/assets/web-panel/assets/index-CCsRfd0k.js +0 -1
  221. package/src/assets/web-panel/assets/index-CsmiZIQ4.js +0 -1
@@ -1,1314 +1,16 @@
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
- import { isFormalQualityHermeticRuntime } from "./formal-quality-eval-runtime.js";
13
-
14
- export const PRIVATE_DIRECTORY_MODE = 0o700;
15
- export const PRIVATE_FILE_MODE = 0o600;
16
-
17
- function samePath(left, right, caseInsensitive = false) {
18
- const trim = (value) => String(value).replace(/[\\/]+$/, "");
19
- const a = trim(left);
20
- const b = trim(right);
21
- return caseInsensitive ? a.toLowerCase() === b.toLowerCase() : a === b;
22
- }
23
-
24
- /** Guard every ACL/chmod repair entry point, not just paths.js callers. */
25
- export function assertSafeOwnerOnlyPath(target, platform = process.platform) {
26
- const value = String(target || "").trim();
27
- if (!value) throw new Error("Owner-only storage path must not be empty");
28
- const pathApi =
29
- platform === "win32"
30
- ? win32
31
- : platform === "darwin" || platform === "linux"
32
- ? posix
33
- : { isAbsolute: isNativeAbsolute, parse, resolve };
34
- const native = pathApi.resolve(value);
35
- const nativeRoot = pathApi.parse(native).root;
36
- const posixPath = posix.isAbsolute(value) ? posix.normalize(value) : null;
37
- const windowsPath = win32.isAbsolute(value) ? win32.normalize(value) : null;
38
- const isAbsolute = pathApi.isAbsolute(value);
39
- const windowsLiteral = value.replaceAll("/", "\\");
40
- const normalizedWindowsLiteral = String(windowsPath || windowsLiteral);
41
- const extendedWindowsRoot =
42
- /^\\\\\?\\[A-Za-z]:\\?$/.test(normalizedWindowsLiteral) ||
43
- /^\\\\\?\\UNC\\[^\\]+\\[^\\]+\\?$/.test(normalizedWindowsLiteral);
44
- const normalizedWindowsLower = normalizedWindowsLiteral.toLowerCase();
45
- const forbiddenWindowsDevice =
46
- normalizedWindowsLower.startsWith("\\\\?\\") ||
47
- normalizedWindowsLower.startsWith("\\\\.\\");
48
- const isRoot =
49
- samePath(native, nativeRoot, platform === "win32") ||
50
- (posixPath !== null && samePath(posixPath, posix.parse(posixPath).root)) ||
51
- (windowsPath !== null &&
52
- samePath(windowsPath, win32.parse(windowsPath).root, true)) ||
53
- /^[A-Za-z]:$/.test(value) ||
54
- extendedWindowsRoot ||
55
- forbiddenWindowsDevice;
56
- if (isRoot) {
57
- const error = new Error(
58
- `Refusing owner-only permission changes on a filesystem root: ${value}`,
59
- );
60
- error.code = "CONFIG_HOME_UNSAFE";
61
- throw error;
62
- }
63
- const caseInsensitive = ["win32", "darwin"].includes(platform);
64
- const protectedLocation = samePath(
65
- native,
66
- pathApi.resolve(homedir()),
67
- caseInsensitive,
68
- );
69
- if (protectedLocation || !isAbsolute) {
70
- const error = new Error(
71
- `Refusing owner-only permission changes on a broad protected directory: ${value}`,
72
- );
73
- error.code = "CONFIG_HOME_UNSAFE";
74
- throw error;
75
- }
76
- return target;
77
- }
78
-
79
- const TRUSTED_DARWIN_SYSTEM_ALIASES = new Map([["/var", "/private/var"]]);
80
-
81
- /**
82
- * macOS exposes its real temporary-directory tree through the root-owned
83
- * `/var -> private/var` compatibility alias. Rejecting that immutable system
84
- * alias makes every `os.tmpdir()`-backed session fail, while accepting generic
85
- * links would re-open the chmod/DACL traversal attack this guard prevents.
86
- *
87
- * Keep the exception deliberately narrow: exact alias, exact canonical target,
88
- * root ownership on both entries, and a non-writable canonical directory.
89
- */
90
- function isTrustedDarwinSystemAlias(current, entry, deps, platform) {
91
- if (platform !== "darwin") return false;
92
- const expected = TRUSTED_DARWIN_SYSTEM_ALIASES.get(current);
93
- if (!expected || Number(entry?.uid) !== 0) return false;
94
- try {
95
- const canonical = deps.realpathSync(current);
96
- if (!samePath(canonical, expected)) return false;
97
- const target = deps.lstatSync(canonical);
98
- const targetMode = Number(target.mode);
99
- return (
100
- !target.isSymbolicLink() &&
101
- target.isDirectory() &&
102
- Number(target.uid) === 0 &&
103
- Number.isInteger(targetMode) &&
104
- (targetMode & 0o022) === 0
105
- );
106
- } catch {
107
- return false;
108
- }
109
- }
110
-
111
- function readWindowsDirectoryEntry(target, deps) {
112
- if (typeof deps.readdirSync !== "function") return null;
113
- const parent = win32.dirname(target);
114
- if (!parent || samePath(parent, target, true)) return null;
115
- let entries;
116
- try {
117
- entries = deps.readdirSync(parent, { withFileTypes: true });
118
- } catch {
119
- return null;
120
- }
121
- if (!Array.isArray(entries)) return null;
122
- const name = win32.basename(target);
123
- const matches = entries.filter(
124
- (candidate) => String(candidate?.name || "") === name,
125
- );
126
- // A successful complete directory enumeration is positive evidence that the
127
- // child is absent. A case-folded match is not absence on Windows; preserve it
128
- // as ambiguous so native verification remains fail-closed.
129
- if (matches.length === 0) {
130
- const caseFoldedMatches = entries.filter(
131
- (candidate) =>
132
- String(candidate?.name || "").toLowerCase() === name.toLowerCase(),
133
- );
134
- if (caseFoldedMatches.length === 0) return { exists: false };
135
- return null;
136
- }
137
- if (matches.length !== 1) return null;
138
- const [entry] = matches;
139
- if (
140
- typeof entry.isSymbolicLink !== "function" ||
141
- typeof entry.isDirectory !== "function" ||
142
- typeof entry.isFile !== "function"
143
- ) {
144
- return null;
145
- }
146
- try {
147
- const isSymbolicLink = Boolean(entry.isSymbolicLink());
148
- const isDirectory = Boolean(entry.isDirectory());
149
- const isFile = Boolean(entry.isFile());
150
- if (!isSymbolicLink && isDirectory === isFile) return null;
151
- return {
152
- exists: true,
153
- isDirectory,
154
- isFile,
155
- isSymbolicLink,
156
- };
157
- } catch {
158
- return null;
159
- }
160
- }
161
-
162
- function windowsUnreliablePathStatError(target, cause) {
163
- const error = new Error(
164
- `Windows path metadata could not reliably classify an existing path: ${target}`,
165
- cause ? { cause } : undefined,
166
- );
167
- error.code = "WINDOWS_PATH_STAT_UNRELIABLE";
168
- return error;
169
- }
170
-
171
- function assertNoLinkTraversal(
172
- target,
173
- deps = fs,
174
- checkedPaths = null,
175
- platform = process.platform,
176
- ) {
177
- // Dependency-injected cross-platform tests must walk the target platform's
178
- // path grammar, not the host running Vitest (for example `/var` on Windows).
179
- const pathApi =
180
- platform === "win32"
181
- ? win32
182
- : platform === "darwin" || platform === "linux"
183
- ? posix
184
- : { resolve, parse };
185
- const absolute = pathApi.resolve(String(target));
186
- const root = pathApi.parse(absolute).root;
187
- const segments = absolute
188
- .slice(root.length)
189
- .split(/[\\/]+/)
190
- .filter(Boolean);
191
- let current = root;
192
- for (const segment of segments) {
193
- current = pathApi.resolve(current, segment);
194
- const cacheKey =
195
- process.platform === "win32" ? current.toLowerCase() : current;
196
- if (checkedPaths?.has(cacheKey)) continue;
197
- let entry;
198
- try {
199
- entry = deps.lstatSync(current);
200
- } catch (error) {
201
- if (
202
- platform === "win32" &&
203
- ["ENOENT", "EPERM", "EACCES"].includes(error?.code)
204
- ) {
205
- const directoryEntry = readWindowsDirectoryEntry(current, deps);
206
- if (directoryEntry?.exists === false) break;
207
- if (directoryEntry) {
208
- if (directoryEntry.isSymbolicLink) {
209
- const unsafe = new Error(
210
- `Refusing owner-only permission changes through a symbolic link or junction: ${current}`,
211
- );
212
- unsafe.code = "CONFIG_HOME_UNSAFE";
213
- throw unsafe;
214
- }
215
- checkedPaths?.add(cacheKey);
216
- continue;
217
- }
218
- throw windowsUnreliablePathStatError(current, error);
219
- }
220
- if (error?.code === "ENOENT") break;
221
- throw error;
222
- }
223
- if (
224
- entry.isSymbolicLink() &&
225
- !isTrustedDarwinSystemAlias(current, entry, deps, platform)
226
- ) {
227
- const error = new Error(
228
- `Refusing owner-only permission changes through a symbolic link or junction: ${current}`,
229
- );
230
- error.code = "CONFIG_HOME_UNSAFE";
231
- throw error;
232
- }
233
- checkedPaths?.add(cacheKey);
234
- }
235
- return target;
236
- }
237
-
238
- // Reapplying an unchanged directory DACL propagates to existing children on
239
- // Windows and changes their ctime, invalidating otherwise intact session
240
- // witnesses. Serialize check-and-repair across processes and skip a verified
241
- // owner-only ACL. Keep the post-repair inspection and all traversal guards.
242
- const WINDOWS_ACL_IDEMPOTENT_REPAIR = String.raw`
243
- function Test-CcOwnerOnlySecurity($item, $security) {
244
- $owner = $security.GetOwner([System.Security.Principal.SecurityIdentifier]).Value
245
- $rules = @($security.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]))
246
- if ($owner -ne $sid.Value -or $rules.Count -ne 1 -or ($item.PSIsContainer -and -not $security.AreAccessRulesProtected)) { return $false }
247
- $rule = $rules[0]
248
- $inheritance = [System.Security.AccessControl.InheritanceFlags]::None
249
- if ($item.PSIsContainer) {
250
- $inheritance = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
251
- }
252
- return ($rule.IdentityReference.Value -eq $sid.Value -and
253
- $rule.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and
254
- (-not $item.PSIsContainer -or -not $rule.IsInherited) -and
255
- $rule.InheritanceFlags -eq $inheritance -and
256
- $rule.PropagationFlags -eq [System.Security.AccessControl.PropagationFlags]::None -and
257
- ($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl)
258
- }
259
- function Write-CcOwnerOnlyAcl($item, [string]$path) {
260
- $script:ccAclStage = 'repair-lock'
261
- [Console]::Error.WriteLine('CC_WINDOWS_ACL_STAGE=repair-lock')
262
- $digest = [System.Security.Cryptography.SHA256]::Create()
263
- try {
264
- $key = [System.BitConverter]::ToString($digest.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($sid.Value + ':' + [System.IO.Path]::GetFullPath($path).ToUpperInvariant()))).Replace('-', '')
265
- } finally { $digest.Dispose() }
266
- $mutex = [System.Threading.Mutex]::new($false, ('Global\ChainlessChain.PrivateAcl.' + $key))
267
- $held = $false
268
- try {
269
- try { $held = $mutex.WaitOne(10000) }
270
- catch [System.Threading.AbandonedMutexException] { $held = $true }
271
- if (-not $held) { throw 'Timed out waiting for owner-only ACL repair' }
272
- Assert-CcNoReparseTraversal $path
273
- $script:ccAclStage = 'repair-inspect'
274
- [Console]::Error.WriteLine('CC_WINDOWS_ACL_STAGE=repair-inspect')
275
- $current = Get-Item -LiteralPath $path -Force
276
- $security = Read-CcAcl $path
277
- if (Test-CcOwnerOnlySecurity $current $security) { return }
278
- $script:ccAclStage = 'repair-write'
279
- [Console]::Error.WriteLine('CC_WINDOWS_ACL_STAGE=repair-write')
280
- Write-CcOwnerOnlyAclImpl $current $path
281
- } finally {
282
- if ($held) { $mutex.ReleaseMutex() }
283
- $mutex.Dispose()
284
- }
285
- }
286
- `;
287
-
288
- const WINDOWS_ACL_SCRIPT = String.raw`
289
- param([string]$target, [string]$operation)
290
- $ErrorActionPreference = 'Stop'
291
- $script:ccAclStage = 'initialize'
292
- try {
293
- [Console]::Error.WriteLine('CC_WINDOWS_ACL_STAGE=initialize')
294
- $utf8 = [System.Text.UTF8Encoding]::new($false)
295
- [Console]::OutputEncoding = $utf8
296
- $sections =
297
- [System.Security.AccessControl.AccessControlSections]::Access -bor
298
- [System.Security.AccessControl.AccessControlSections]::Owner
299
- $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
300
- $sid = $identity.User
301
- $tokenOwner = $identity.Owner
302
-
303
- function Assert-CcNoReparseTraversal([string]$path) {
304
- $cursor = [System.IO.Path]::GetFullPath($path)
305
- while (-not (Test-Path -LiteralPath $cursor)) {
306
- $parent = [System.IO.Directory]::GetParent($cursor)
307
- if ($null -eq $parent) { return }
308
- $cursor = $parent.FullName
309
- }
310
- $entry = Get-Item -LiteralPath $cursor -Force
311
- while ($null -ne $entry) {
312
- if (($entry.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
313
- throw "symbolic links and reparse points are not allowed: $($entry.FullName)"
314
- }
315
- $entry = $entry.Parent
316
- }
317
- }
318
-
319
- $script:ccAclStage = 'traversal'
320
- [Console]::Error.WriteLine('CC_WINDOWS_ACL_STAGE=traversal')
321
- Assert-CcNoReparseTraversal $target
322
- $script:ccAclStage = 'lookup'
323
- [Console]::Error.WriteLine('CC_WINDOWS_ACL_STAGE=lookup')
324
- $item = Get-Item -LiteralPath $target -Force
325
-
326
- function Read-CcAcl([string]$path) {
327
- if ([System.IO.Directory]::Exists($path)) {
328
- return [System.Security.AccessControl.DirectorySecurity]::new($path, $sections)
329
- }
330
- return [System.Security.AccessControl.FileSecurity]::new($path, $sections)
331
- }
332
-
333
- ${WINDOWS_ACL_IDEMPOTENT_REPAIR}
334
- function Write-CcOwnerOnlyAclImpl($item, [string]$path) {
335
- $security = Read-CcAcl $path
336
- $owner = $security.GetOwner([System.Security.Principal.SecurityIdentifier])
337
- if ($owner.Value -ne $sid.Value) {
338
- # Elevated/service tokens can create files whose owner is the token's
339
- # default owner group (for example BUILTIN\Administrators on hosted CI),
340
- # even though the token user created the object. Only that exact token-owned
341
- # case may converge to the narrower user SID; every unrelated owner remains
342
- # fail-closed.
343
- if ($null -eq $tokenOwner -or $owner.Value -ne $tokenOwner.Value) {
344
- throw "owner-only ACL repair refuses a path owned by another identity: $path"
345
- }
346
- $security.SetOwner($sid)
347
- }
348
- $security.SetAccessRuleProtection($true, $false)
349
- $existingRules = @($security.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]))
350
- foreach ($existingRule in $existingRules) {
351
- $security.RemoveAccessRuleAll($existingRule)
352
- }
353
- if ($item.PSIsContainer) {
354
- $inheritance =
355
- [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
356
- [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
357
- $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
358
- $sid,
359
- [System.Security.AccessControl.FileSystemRights]::FullControl,
360
- $inheritance,
361
- [System.Security.AccessControl.PropagationFlags]::None,
362
- [System.Security.AccessControl.AccessControlType]::Allow
363
- )
364
- $security.AddAccessRule($rule) | Out-Null
365
- [System.IO.DirectoryInfo]::new($path).SetAccessControl($security)
366
- } else {
367
- $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
368
- $sid,
369
- [System.Security.AccessControl.FileSystemRights]::FullControl,
370
- [System.Security.AccessControl.InheritanceFlags]::None,
371
- [System.Security.AccessControl.PropagationFlags]::None,
372
- [System.Security.AccessControl.AccessControlType]::Allow
373
- )
374
- $security.AddAccessRule($rule) | Out-Null
375
- [System.IO.FileInfo]::new($path).SetAccessControl($security)
376
- }
377
- }
378
-
379
- if ($operation -eq 'repair') {
380
- Write-CcOwnerOnlyAcl $item $target
381
- }
382
-
383
- $script:ccAclStage = 'verify'
384
- [Console]::Error.WriteLine('CC_WINDOWS_ACL_STAGE=verify')
385
- $acl = Read-CcAcl $target
386
- $owner = $acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value
387
- $rules = @($acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]))
388
- $protectionOk = -not $item.PSIsContainer -or $acl.AreAccessRulesProtected
389
- $ownerOnly =
390
- $protectionOk -and
391
- $owner -eq $sid.Value -and
392
- $rules.Count -eq 1
393
- if ($ownerOnly) {
394
- foreach ($rule in $rules) {
395
- $full = ($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl
396
- if ($item.PSIsContainer) {
397
- $requiredInheritance =
398
- [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
399
- [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
400
- $inheritanceOk =
401
- ($rule.InheritanceFlags -band $requiredInheritance) -eq $requiredInheritance
402
- } else {
403
- $inheritanceOk =
404
- $rule.InheritanceFlags -eq [System.Security.AccessControl.InheritanceFlags]::None
405
- }
406
- $propagationOk =
407
- $rule.PropagationFlags -eq [System.Security.AccessControl.PropagationFlags]::None
408
- if ($rule.IdentityReference.Value -ne $sid.Value -or
409
- $rule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow -or
410
- ($item.PSIsContainer -and $rule.IsInherited) -or
411
- -not $inheritanceOk -or
412
- -not $propagationOk -or
413
- -not $full) {
414
- $ownerOnly = $false
415
- break
416
- }
417
- }
418
- }
419
- [pscustomobject]@{
420
- ownerOnly = [bool]$ownerOnly
421
- isDirectory = [bool]$item.PSIsContainer
422
- ownerSid = $owner
423
- currentSid = $sid.Value
424
- tokenOwnerSid = if ($null -eq $tokenOwner) { $null } else { $tokenOwner.Value }
425
- protected = [bool]$acl.AreAccessRulesProtected
426
- aceCount = $rules.Count
427
- } | ConvertTo-Json -Compress
428
- if (-not $ownerOnly) { exit 4 }
429
- } catch {
430
- # Emit only a fixed stage and the deepest native HRESULT. Never emit the
431
- # exception message, target path or arbitrary PowerShell error record.
432
- $failure = $_.Exception
433
- while ($null -ne $failure.InnerException) { $failure = $failure.InnerException }
434
- [pscustomobject]@{
435
- ownerOnly = $false
436
- failureStage = $script:ccAclStage
437
- hresult = ('0x{0:X8}' -f $failure.HResult)
438
- } | ConvertTo-Json -Compress
439
- exit 5
440
- }
441
- `;
442
-
443
- const WINDOWS_ACL_BATCH_SCRIPT = String.raw`
444
- $ErrorActionPreference = 'Stop'
445
- $utf8 = [System.Text.UTF8Encoding]::new($false)
446
- [Console]::InputEncoding = $utf8
447
- [Console]::OutputEncoding = $utf8
448
- $sections =
449
- [System.Security.AccessControl.AccessControlSections]::Access -bor
450
- [System.Security.AccessControl.AccessControlSections]::Owner
451
- $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
452
- $sid = $identity.User
453
- $tokenOwner = $identity.Owner
454
- $request = [Console]::In.ReadToEnd() | ConvertFrom-Json
455
- $operation = [string]$request.operation
456
- $targets = @($request.targets)
457
- $expectedKind = [string]$request.expectedKind
458
- if ($operation -ne 'inspect' -and $operation -ne 'repair' -and $operation -ne 'preflight') {
459
- throw "unsupported ACL batch operation: $operation"
460
- }
461
- if ($expectedKind -ne '' -and $expectedKind -ne 'file' -and $expectedKind -ne 'directory') {
462
- throw "unsupported expected path kind: $expectedKind"
463
- }
464
-
465
- function Assert-CcNoReparseTraversal([string]$path) {
466
- $cursor = [System.IO.Path]::GetFullPath($path)
467
- while (-not (Test-Path -LiteralPath $cursor)) {
468
- $parent = [System.IO.Directory]::GetParent($cursor)
469
- if ($null -eq $parent) { return }
470
- $cursor = $parent.FullName
471
- }
472
- $entry = Get-Item -LiteralPath $cursor -Force
473
- while ($null -ne $entry) {
474
- if (($entry.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
475
- throw "symbolic links and reparse points are not allowed: $($entry.FullName)"
476
- }
477
- $entry = $entry.Parent
478
- }
479
- }
480
-
481
- # A repair is all-or-nothing with respect to path safety. Preflight every
482
- # existing target before the first SetAccessControl call so a junction root
483
- # cannot fail after a regular-looking descendant has already been modified.
484
- if ($operation -eq 'repair' -or $operation -eq 'preflight') {
485
- $preflightFailures = @()
486
- foreach ($target in $targets) {
487
- try {
488
- Assert-CcNoReparseTraversal $target
489
- $candidate = Get-Item -LiteralPath $target -Force
490
- $kindMismatch =
491
- ($expectedKind -eq 'file' -and $candidate.PSIsContainer) -or
492
- ($expectedKind -eq 'directory' -and -not $candidate.PSIsContainer)
493
- if ($kindMismatch) {
494
- $preflightFailures += [pscustomobject]@{
495
- target = [string]$target
496
- error = "expected $expectedKind but found $(if ($candidate.PSIsContainer) { 'directory' } else { 'file' })"
497
- errorCode = 'EXPECTED_KIND_MISMATCH'
498
- }
499
- }
500
- } catch {
501
- if ($_.CategoryInfo.Category -ne [System.Management.Automation.ErrorCategory]::ObjectNotFound) {
502
- $preflightFailures += [pscustomobject]@{
503
- target = [string]$target
504
- error = $_.Exception.Message
505
- errorCode = 'PATH_PREFLIGHT_FAILED'
506
- }
507
- }
508
- }
509
- }
510
- if ($preflightFailures.Count -gt 0) {
511
- $firstFailure = $preflightFailures[0]
512
- $output = @($targets | ForEach-Object {
513
- [pscustomobject]@{
514
- target = [string]$_
515
- exists = [bool](Test-Path -LiteralPath $_)
516
- isDirectory = $null
517
- ok = $false
518
- error = "ACL batch preflight failed at $($firstFailure.target): $($firstFailure.error)"
519
- errorCode = $firstFailure.errorCode
520
- }
521
- })
522
- [Console]::Write((ConvertTo-Json -InputObject $output -Compress -Depth 4))
523
- exit 4
524
- }
525
- if ($operation -eq 'preflight') {
526
- $output = @($targets | ForEach-Object {
527
- $preflightTarget = [string]$_
528
- $nativeItem = $null
529
- try {
530
- $nativeItem = Get-Item -LiteralPath $preflightTarget -Force
531
- } catch {
532
- if ($_.CategoryInfo.Category -ne [System.Management.Automation.ErrorCategory]::ObjectNotFound) {
533
- throw
534
- }
535
- }
536
- [pscustomobject]@{
537
- target = $preflightTarget
538
- exists = [bool]($null -ne $nativeItem)
539
- isDirectory = if ($null -eq $nativeItem) { $null } else { [bool]$nativeItem.PSIsContainer }
540
- ok = $true
541
- error = $null
542
- }
543
- })
544
- [Console]::Write((ConvertTo-Json -InputObject $output -Compress -Depth 4))
545
- exit 0
546
- }
547
- }
548
-
549
- function Read-CcAcl([string]$path) {
550
- if ([System.IO.Directory]::Exists($path)) {
551
- return [System.Security.AccessControl.DirectorySecurity]::new($path, $sections)
552
- }
553
- return [System.Security.AccessControl.FileSecurity]::new($path, $sections)
554
- }
555
-
556
- ${WINDOWS_ACL_IDEMPOTENT_REPAIR}
557
- function Write-CcOwnerOnlyAclImpl($item, [string]$path) {
558
- $security = Read-CcAcl $path
559
- $owner = $security.GetOwner([System.Security.Principal.SecurityIdentifier])
560
- if ($owner.Value -ne $sid.Value) {
561
- if ($null -eq $tokenOwner -or $owner.Value -ne $tokenOwner.Value) {
562
- throw "owner-only ACL repair refuses a path owned by another identity: $path"
563
- }
564
- $security.SetOwner($sid)
565
- }
566
- $security.SetAccessRuleProtection($true, $false)
567
- $existingRules = @($security.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]))
568
- foreach ($existingRule in $existingRules) {
569
- $security.RemoveAccessRuleAll($existingRule)
570
- }
571
- if ($item.PSIsContainer) {
572
- $inheritance =
573
- [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
574
- [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
575
- $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
576
- $sid,
577
- [System.Security.AccessControl.FileSystemRights]::FullControl,
578
- $inheritance,
579
- [System.Security.AccessControl.PropagationFlags]::None,
580
- [System.Security.AccessControl.AccessControlType]::Allow
581
- )
582
- $security.AddAccessRule($rule) | Out-Null
583
- [System.IO.DirectoryInfo]::new($path).SetAccessControl($security)
584
- } else {
585
- $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
586
- $sid,
587
- [System.Security.AccessControl.FileSystemRights]::FullControl,
588
- [System.Security.AccessControl.InheritanceFlags]::None,
589
- [System.Security.AccessControl.PropagationFlags]::None,
590
- [System.Security.AccessControl.AccessControlType]::Allow
591
- )
592
- $security.AddAccessRule($rule) | Out-Null
593
- [System.IO.FileInfo]::new($path).SetAccessControl($security)
594
- }
595
- }
596
-
597
- $results = foreach ($target in $targets) {
598
- $item = $null
599
- try {
600
- Assert-CcNoReparseTraversal $target
601
- $item = Get-Item -LiteralPath $target -Force
602
- $kindMismatch =
603
- ($expectedKind -eq 'file' -and $item.PSIsContainer) -or
604
- ($expectedKind -eq 'directory' -and -not $item.PSIsContainer)
605
- if ($kindMismatch) {
606
- [pscustomobject]@{
607
- target = [string]$target
608
- exists = $true
609
- isDirectory = [bool]$item.PSIsContainer
610
- ok = $false
611
- error = "expected $expectedKind but found $(if ($item.PSIsContainer) { 'directory' } else { 'file' })"
612
- errorCode = 'EXPECTED_KIND_MISMATCH'
613
- }
614
- continue
615
- }
616
- if ($operation -eq 'repair') {
617
- Write-CcOwnerOnlyAcl $item $target
618
- }
619
-
620
- $acl = Read-CcAcl $target
621
- $owner = $acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value
622
- $rules = @($acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]))
623
- $protectionOk = -not $item.PSIsContainer -or $acl.AreAccessRulesProtected
624
- $ownerOnly =
625
- $protectionOk -and
626
- $owner -eq $sid.Value -and
627
- $rules.Count -eq 1
628
- if ($ownerOnly) {
629
- foreach ($rule in $rules) {
630
- $full = ($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl
631
- if ($item.PSIsContainer) {
632
- $requiredInheritance =
633
- [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
634
- [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
635
- $inheritanceOk =
636
- ($rule.InheritanceFlags -band $requiredInheritance) -eq $requiredInheritance
637
- } else {
638
- $inheritanceOk =
639
- $rule.InheritanceFlags -eq [System.Security.AccessControl.InheritanceFlags]::None
640
- }
641
- $propagationOk =
642
- $rule.PropagationFlags -eq [System.Security.AccessControl.PropagationFlags]::None
643
- if ($rule.IdentityReference.Value -ne $sid.Value -or
644
- $rule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow -or
645
- ($item.PSIsContainer -and $rule.IsInherited) -or
646
- -not $inheritanceOk -or
647
- -not $propagationOk -or
648
- -not $full) {
649
- $ownerOnly = $false
650
- break
651
- }
652
- }
653
- }
654
- [pscustomobject]@{
655
- target = [string]$target
656
- exists = $true
657
- isDirectory = [bool]$item.PSIsContainer
658
- ok = [bool]$ownerOnly
659
- ownerSid = $owner
660
- currentSid = $sid.Value
661
- tokenOwnerSid = if ($null -eq $tokenOwner) { $null } else { $tokenOwner.Value }
662
- protected = [bool]$acl.AreAccessRulesProtected
663
- aceCount = $rules.Count
664
- error = if ($ownerOnly) { $null } else { 'path is not owner-only' }
665
- errorCode = $null
666
- }
667
- } catch {
668
- [pscustomobject]@{
669
- target = [string]$target
670
- exists = [bool](($null -ne $item) -or (Test-Path -LiteralPath $target))
671
- isDirectory = $null
672
- ok = $false
673
- error = $_.Exception.Message
674
- errorCode = 'PATH_OPERATION_FAILED'
675
- }
676
- }
677
- }
678
- $output = [array]@($results)
679
- [Console]::Write((ConvertTo-Json -InputObject $output -Compress -Depth 4))
680
- if (@($output | Where-Object { -not $_.ok }).Count -gt 0) { exit 4 }
681
- `;
682
-
683
- export const _deps = {
684
- fs,
685
- spawnSync,
686
- platform: () => process.platform,
687
- };
688
-
689
- // ACL repair starts a short PowerShell process on Windows. Cache successful
690
- // production repairs by path and filesystem identity for this process; injected-dependency
691
- // tests and repairPrivatePath() always execute and verify the operation.
692
- const securedWindowsPaths = new Map();
693
-
694
- function windowsPathIdentity(target, deps) {
695
- try {
696
- const entry = deps.fs.lstatSync(target, { bigint: true });
697
- if (entry.isSymbolicLink() || !entry.ino) return null;
698
- return `${entry.dev}:${entry.ino}:${entry.birthtimeNs}`;
699
- } catch {
700
- // An unreliable Node stat must take the native verification path again.
701
- return null;
702
- }
703
- }
704
-
705
- function rememberSecuredWindowsPath(target, deps) {
706
- const identity = windowsPathIdentity(target, deps);
707
- if (identity) securedWindowsPaths.set(target, identity);
708
- else securedWindowsPaths.delete(target);
709
- }
710
-
711
- function isSecuredWindowsPath(target, deps) {
712
- const cached = securedWindowsPaths.get(target);
713
- return Boolean(cached && cached === windowsPathIdentity(target, deps));
714
- }
715
- const WINDOWS_ACL_BATCH_SIZE = 500;
716
- const WINDOWS_ACL_TIMEOUT_ENV = "CC_SECURE_FS_WINDOWS_ACL_TIMEOUT_MS";
717
- const MAX_WINDOWS_ACL_TIMEOUT_MS = 5 * 60_000;
718
-
719
- export function _windowsAclWorkingDirectory(
720
- environment = process.env,
721
- filesystem = fs,
722
- ) {
723
- // Isolated profiles can make Windows PowerShell first-use caches relative
724
- // to cwd. Contain those writes in application state, never the source tree.
725
- const isolatedLocalTarget =
726
- typeof environment.CC_EXECUTION_LOCATION_RUNNER_ID === "string" &&
727
- environment.CC_EXECUTION_LOCATION_RUNNER_ID.length > 0 &&
728
- typeof environment.CHAINLESSCHAIN_HOME === "string" &&
729
- isNativeAbsolute(environment.CHAINLESSCHAIN_HOME);
730
- if (!isFormalQualityHermeticRuntime(environment) && !isolatedLocalTarget)
731
- return null;
732
- const configuredHome = resolve(String(environment.CHAINLESSCHAIN_HOME));
733
- try {
734
- const entry = filesystem.lstatSync(configuredHome);
735
- if (!entry.isDirectory() || entry.isSymbolicLink()) return null;
736
- } catch {
737
- return null;
738
- }
739
- return configuredHome;
740
- }
741
-
742
- // Windows hosted runners can need more than the normal 15/30 second allowance
743
- // to cold-start PowerShell and load the access-control types. A trusted harness
744
- // may raise (but never lower) that allowance; the cap prevents an inherited
745
- // environment value from wedging owner-only storage initialization forever.
746
- export function _resolveWindowsAclTimeout(
747
- defaultTimeoutMs,
748
- environment = process.env,
749
- ) {
750
- const fallback = Math.max(1, Math.floor(Number(defaultTimeoutMs) || 1));
751
- const configured = Number(environment?.[WINDOWS_ACL_TIMEOUT_ENV]);
752
- if (!Number.isFinite(configured) || configured <= 0) return fallback;
753
- return Math.min(
754
- Math.max(fallback, Math.floor(configured)),
755
- MAX_WINDOWS_ACL_TIMEOUT_MS,
756
- );
757
- }
758
-
759
- // Give one cold PowerShell process the full bounded operation budget. Splitting
760
- // it into two shorter spawns repeatedly kills a slow startup before it can
761
- // finish (and can leave less time than the native mutex's 10-second wait).
762
- // A process that exhausts the budget remains a failure; never start another
763
- // process with a fresh budget or accept an unverified ACL after a timeout.
764
- function runWindowsAclCommand(deps, args, options) {
765
- return deps.spawnSync("powershell.exe", args, {
766
- ...options,
767
- timeout: Math.max(1, Math.floor(Number(options?.timeout) || 1)),
768
- });
769
- }
770
-
771
- function repairWindowsAclOnce(target, deps, options) {
772
- const cacheable = !options.deps;
773
- if (cacheable && isSecuredWindowsPath(target, deps)) {
774
- return { ok: true, platform: "win32", cached: true };
775
- }
776
- const result = windowsAcl(target, "repair", deps);
777
- if (result.ok && cacheable) rememberSecuredWindowsPath(target, deps);
778
- return result;
779
- }
780
-
781
- function windowsAcl(target, operation, deps) {
782
- const workingDirectory = _windowsAclWorkingDirectory(process.env, deps.fs);
783
- const result = runWindowsAclCommand(
784
- deps,
785
- [
786
- "-NoLogo",
787
- "-NoProfile",
788
- "-NonInteractive",
789
- "-ExecutionPolicy",
790
- "Bypass",
791
- "-Command",
792
- `& { ${WINDOWS_ACL_SCRIPT} }`,
793
- target,
794
- operation,
795
- ],
796
- {
797
- encoding: "utf8",
798
- windowsHide: true,
799
- timeout: _resolveWindowsAclTimeout(15_000),
800
- ...(workingDirectory ? { cwd: workingDirectory } : {}),
801
- },
802
- );
803
- let details = null;
804
- try {
805
- details = JSON.parse(String(result?.stdout || "").trim());
806
- } catch {
807
- details = null;
808
- }
809
- if (result?.error) {
810
- const progress =
811
- [
812
- ...String(result.stderr || "")
813
- .slice(0, 8192)
814
- .matchAll(
815
- /^CC_WINDOWS_ACL_STAGE=(initialize|traversal|lookup|repair-lock|repair-inspect|repair-write|verify)\r?$/gmu,
816
- ),
817
- ].at(-1)?.[1] || "startup";
818
- const stage =
819
- result.error.code === "ETIMEDOUT" ? `timeout-${progress}` : "spawn";
820
- return {
821
- ok: false,
822
- platform: "win32",
823
- error: `owner-only ACL process failed [windows-acl:${stage}]`,
824
- };
825
- }
826
- if (result?.status !== 0 || details?.ownerOnly !== true) {
827
- return {
828
- ok: false,
829
- platform: "win32",
830
- error: `owner-only ACL verification failed ${windowsAclFailureMarker(details)}`,
831
- details,
832
- };
833
- }
834
- return { ok: true, platform: "win32", details };
835
- }
836
-
837
- function windowsAclFailureMarker(details) {
838
- const allowedStages = new Set([
839
- "initialize",
840
- "traversal",
841
- "lookup",
842
- "repair-lock",
843
- "repair-inspect",
844
- "repair-write",
845
- "verify",
846
- ]);
847
- if (
848
- allowedStages.has(details?.failureStage) &&
849
- /^0x[0-9a-f]{8}$/iu.test(details?.hresult || "")
850
- ) {
851
- return `[windows-acl:${details.failureStage}:${details.hresult.toLowerCase()}]`;
852
- }
853
- return details?.ownerOnly === false
854
- ? "[windows-acl:verify]"
855
- : "[windows-acl:output]";
856
- }
857
-
858
- function windowsAclBatch(targets, operation, deps, expectedKind = null) {
859
- if (targets.length === 0) return [];
860
- const workingDirectory = _windowsAclWorkingDirectory(process.env, deps.fs);
861
- const result = runWindowsAclCommand(
862
- deps,
863
- [
864
- "-NoLogo",
865
- "-NoProfile",
866
- "-NonInteractive",
867
- "-ExecutionPolicy",
868
- "Bypass",
869
- "-Command",
870
- `& { ${WINDOWS_ACL_BATCH_SCRIPT} }`,
871
- ],
872
- {
873
- encoding: "utf8",
874
- input: JSON.stringify({
875
- operation,
876
- targets,
877
- ...(expectedKind ? { expectedKind } : {}),
878
- }),
879
- windowsHide: true,
880
- timeout: _resolveWindowsAclTimeout(30_000),
881
- maxBuffer: 4 * 1024 * 1024,
882
- ...(workingDirectory ? { cwd: workingDirectory } : {}),
883
- },
884
- );
885
- let details = [];
886
- try {
887
- const parsed = JSON.parse(String(result?.stdout || "").trim());
888
- details = Array.isArray(parsed) ? parsed : [parsed];
889
- } catch {
890
- details = [];
891
- }
892
- const anyFailed = details.some((entry) => entry?.ok !== true);
893
- const statusOk = result?.status === 0 || (result?.status === 4 && anyFailed);
894
- const uniqueDetailTargets = new Set(details.map((entry) => entry?.target));
895
- if (
896
- result?.error ||
897
- !statusOk ||
898
- details.length !== targets.length ||
899
- uniqueDetailTargets.size !== targets.length
900
- ) {
901
- const error =
902
- result?.error?.message ||
903
- String(result?.stderr || "").trim() ||
904
- "owner-only ACL batch verification failed";
905
- return targets.map((target) => ({
906
- target,
907
- ok: false,
908
- platform: "win32",
909
- error,
910
- }));
911
- }
912
- const byTarget = new Map(details.map((entry) => [entry.target, entry]));
913
- return targets.map((target) => {
914
- const entry = byTarget.get(target);
915
- return {
916
- target,
917
- exists: entry?.exists !== false,
918
- ok: entry?.ok === true,
919
- platform: "win32",
920
- details: entry || null,
921
- ...(entry?.errorCode ? { errorCode: entry.errorCode } : {}),
922
- ...(entry?.ok === true
923
- ? {}
924
- : {
925
- error:
926
- entry?.error ||
927
- String(result?.stderr || "").trim() ||
928
- `owner-only ACL batch verification exited ${result?.status}`,
929
- }),
930
- };
931
- });
932
- }
933
-
934
- function isWindowsPathStatUnreliable(error, platform) {
935
- return (
936
- platform === "win32" &&
937
- (error?.code === "EPERM" ||
938
- error?.code === "EACCES" ||
939
- error?.code === "ENOENT" ||
940
- error?.code === "WINDOWS_PATH_STAT_UNRELIABLE")
941
- );
942
- }
943
-
944
- function preflightWindowsPaths(targets, deps) {
945
- const results = [];
946
- for (
947
- let offset = 0;
948
- offset < targets.length;
949
- offset += WINDOWS_ACL_BATCH_SIZE
950
- ) {
951
- results.push(
952
- ...windowsAclBatch(
953
- targets.slice(offset, offset + WINDOWS_ACL_BATCH_SIZE),
954
- "preflight",
955
- deps,
956
- ),
957
- );
958
- }
959
- const failed = results.find((entry) => entry.ok !== true);
960
- if (failed) {
961
- const error = new Error(
962
- `Could not verify Windows path ancestors for ${failed.target}: ${failed.error || "native preflight failed"}`,
963
- );
964
- error.code = "CONFIG_HOME_UNSAFE";
965
- throw error;
966
- }
967
- return results;
968
- }
969
-
970
- function privatePathEntryFromNativeEvidence(target, nativeResult) {
971
- if (!nativeResult || !samePath(nativeResult.target, target, true)) {
972
- const unsafe = new Error(
973
- `Native Windows path evidence did not match requested target: ${target}`,
974
- );
975
- unsafe.code = "CONFIG_HOME_UNSAFE";
976
- throw unsafe;
977
- }
978
- if (nativeResult.exists === false) {
979
- return { exists: false, entry: null };
980
- }
981
- const isDirectory = nativeResult.details?.isDirectory;
982
- if (typeof isDirectory !== "boolean") {
983
- const unsafe = new Error(
984
- `Could not classify Windows path type after native preflight: ${target}`,
985
- );
986
- unsafe.code = "CONFIG_HOME_UNSAFE";
987
- throw unsafe;
988
- }
989
- return {
990
- exists: true,
991
- entry: {
992
- isDirectory: () => isDirectory,
993
- isSymbolicLink: () => false,
994
- },
995
- };
996
- }
997
-
998
- function readPrivatePathEntry(target, deps, platform, nativeEvidence = null) {
999
- if (platform === "win32" && nativeEvidence) {
1000
- return privatePathEntryFromNativeEvidence(target, nativeEvidence);
1001
- }
1002
- try {
1003
- return { exists: true, entry: deps.fs.lstatSync(target) };
1004
- } catch (error) {
1005
- if (platform !== "win32") {
1006
- if (error?.code === "ENOENT") return { exists: false, entry: null };
1007
- throw error;
1008
- }
1009
- if (!isWindowsPathStatUnreliable(error, platform)) throw error;
1010
- const directoryEntry = readWindowsDirectoryEntry(target, deps.fs);
1011
- if (directoryEntry?.exists === false) {
1012
- return { exists: false, entry: null };
1013
- }
1014
- if (directoryEntry) {
1015
- return {
1016
- exists: true,
1017
- entry: {
1018
- isDirectory: () => directoryEntry.isDirectory,
1019
- isSymbolicLink: () => directoryEntry.isSymbolicLink,
1020
- },
1021
- };
1022
- }
1023
- const [nativeResult] = preflightWindowsPaths([String(target)], deps);
1024
- return privatePathEntryFromNativeEvidence(target, nativeResult);
1025
- }
1026
- }
1027
-
1028
- function assertNoLinkTraversalWithWindowsFallback(
1029
- target,
1030
- deps,
1031
- checkedPaths,
1032
- platform,
1033
- ) {
1034
- try {
1035
- assertNoLinkTraversal(target, deps.fs, checkedPaths, platform);
1036
- return null;
1037
- } catch (error) {
1038
- if (!isWindowsPathStatUnreliable(error, platform)) throw error;
1039
- return preflightWindowsPaths([String(target)], deps)[0];
1040
- }
1041
- }
1042
-
1043
- export function inspectPrivatePaths(targets, options = {}) {
1044
- const uniqueTargets = [...new Set((targets || []).map(String))];
1045
- const deps = { ..._deps, ...(options.deps || {}) };
1046
- if ((options.platform || deps.platform()) === "win32") {
1047
- const results = [];
1048
- for (
1049
- let offset = 0;
1050
- offset < uniqueTargets.length;
1051
- offset += WINDOWS_ACL_BATCH_SIZE
1052
- ) {
1053
- results.push(
1054
- ...windowsAclBatch(
1055
- uniqueTargets.slice(offset, offset + WINDOWS_ACL_BATCH_SIZE),
1056
- "inspect",
1057
- deps,
1058
- ),
1059
- );
1060
- }
1061
- return results;
1062
- }
1063
- return uniqueTargets.map((target) => ({
1064
- target,
1065
- ...inspectPrivatePath(target, { ...options, deps }),
1066
- }));
1067
- }
1068
-
1069
- export function repairPrivatePaths(targets, options = {}) {
1070
- const uniqueTargets = [...new Set((targets || []).map(String))];
1071
- const deps = { ..._deps, ...(options.deps || {}) };
1072
- const platform = options.platform || deps.platform();
1073
- const checkedPaths = new Set();
1074
- let nativeWindowsPreflightRequired = false;
1075
- // Complete every path-safety check before starting a batched mutation. This
1076
- // prevents a linked trust root from failing only after a descendant changed.
1077
- for (const target of uniqueTargets) {
1078
- assertSafeOwnerOnlyPath(target, platform);
1079
- }
1080
- for (const target of uniqueTargets) {
1081
- try {
1082
- assertNoLinkTraversal(target, deps.fs, checkedPaths, platform);
1083
- } catch (error) {
1084
- if (!isWindowsPathStatUnreliable(error, platform)) throw error;
1085
- nativeWindowsPreflightRequired = true;
1086
- break;
1087
- }
1088
- }
1089
- if (nativeWindowsPreflightRequired) {
1090
- preflightWindowsPaths(uniqueTargets, deps);
1091
- }
1092
- if (platform === "win32") {
1093
- const results = [];
1094
- for (
1095
- let offset = 0;
1096
- offset < uniqueTargets.length;
1097
- offset += WINDOWS_ACL_BATCH_SIZE
1098
- ) {
1099
- results.push(
1100
- ...windowsAclBatch(
1101
- uniqueTargets.slice(offset, offset + WINDOWS_ACL_BATCH_SIZE),
1102
- "repair",
1103
- deps,
1104
- ),
1105
- );
1106
- }
1107
- const failed = results.filter(
1108
- (entry) => !entry.ok && entry.exists !== false,
1109
- );
1110
- if (failed.length > 0) {
1111
- throw new Error(
1112
- `Could not repair owner-only ACL for ${failed[0].target}: ${failed[0].error}`,
1113
- );
1114
- }
1115
- if (!options.deps) {
1116
- for (const result of results) {
1117
- if (result.ok && result.exists !== false) {
1118
- rememberSecuredWindowsPath(result.target, deps);
1119
- }
1120
- }
1121
- }
1122
- return results;
1123
- }
1124
- return uniqueTargets.map((target) => {
1125
- try {
1126
- return {
1127
- target,
1128
- ...repairPrivatePathAfterPreflight(target, options, deps),
1129
- };
1130
- } catch (error) {
1131
- if (error?.code === "ENOENT") {
1132
- return { target, ok: true, exists: false, skipped: true };
1133
- }
1134
- throw error;
1135
- }
1136
- });
1137
- }
1138
-
1139
- export function inspectPrivatePath(target, options = {}) {
1140
- const deps = { ..._deps, ...(options.deps || {}) };
1141
- const platform = options.platform || deps.platform();
1142
- if (platform === "win32") {
1143
- return windowsAclBatch([String(target)], "inspect", deps)[0];
1144
- }
1145
- let stat;
1146
- try {
1147
- stat = deps.fs.lstatSync(target);
1148
- } catch (error) {
1149
- return { ok: false, exists: false, error: error.message };
1150
- }
1151
- if (stat.isSymbolicLink()) {
1152
- return { ok: false, exists: true, error: "symbolic links are not allowed" };
1153
- }
1154
- const expected = stat.isDirectory()
1155
- ? PRIVATE_DIRECTORY_MODE
1156
- : PRIVATE_FILE_MODE;
1157
- const actual = stat.mode & 0o777;
1158
- const ownerOk =
1159
- typeof process.getuid !== "function" || stat.uid === process.getuid();
1160
- return {
1161
- ok: actual === expected && ownerOk,
1162
- exists: true,
1163
- platform: options.platform || deps.platform(),
1164
- expectedMode: expected,
1165
- actualMode: actual,
1166
- ownerOk,
1167
- };
1168
- }
1169
-
1170
- export function repairPrivatePath(target, options = {}) {
1171
- const deps = { ..._deps, ...(options.deps || {}) };
1172
- const platform = options.platform || deps.platform();
1173
- assertSafeOwnerOnlyPath(target, platform);
1174
- assertNoLinkTraversalWithWindowsFallback(target, deps, null, platform);
1175
- return repairPrivatePathAfterPreflight(target, options, deps);
1176
- }
1177
-
1178
- function repairPrivatePathAfterPreflight(target, options, deps) {
1179
- if ((options.platform || deps.platform()) === "win32") {
1180
- const result = windowsAcl(target, "repair", deps);
1181
- if (!result.ok)
1182
- throw new Error(result.error || "owner-only ACL repair failed");
1183
- if (!options.deps) rememberSecuredWindowsPath(target, deps);
1184
- return result;
1185
- }
1186
- const stat = deps.fs.lstatSync(target);
1187
- if (stat.isSymbolicLink()) {
1188
- throw new Error(
1189
- `Refusing to change permissions through a symbolic link: ${target}`,
1190
- );
1191
- }
1192
- deps.fs.chmodSync(
1193
- target,
1194
- stat.isDirectory() ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE,
1195
- );
1196
- const result = inspectPrivatePath(target, { ...options, deps });
1197
- if (!result.ok)
1198
- throw new Error(`Could not verify owner-only permissions: ${target}`);
1199
- return result;
1200
- }
1201
-
1202
- export function ensurePrivateDirectory(target, options = {}) {
1203
- const deps = { ..._deps, ...(options.deps || {}) };
1204
- const platform = options.platform || deps.platform();
1205
- assertSafeOwnerOnlyPath(target, platform);
1206
- const nativeEvidence = assertNoLinkTraversalWithWindowsFallback(
1207
- target,
1208
- deps,
1209
- null,
1210
- platform,
1211
- );
1212
- let existed = deps.fs.existsSync(target);
1213
- let existingMode = null;
1214
- if (existed) {
1215
- const pathEntry = readPrivatePathEntry(
1216
- target,
1217
- deps,
1218
- platform,
1219
- nativeEvidence,
1220
- );
1221
- existed = pathEntry.exists;
1222
- const existing = pathEntry.entry;
1223
- existingMode = existing ? Number(existing.mode) : null;
1224
- if (existed && existing.isSymbolicLink()) {
1225
- throw new Error(
1226
- `Refusing owner-only directory through a symbolic link: ${target}`,
1227
- );
1228
- }
1229
- if (
1230
- existed &&
1231
- typeof existing.isDirectory === "function" &&
1232
- !existing.isDirectory()
1233
- ) {
1234
- throw new Error(`Expected a directory for owner-only storage: ${target}`);
1235
- }
1236
- }
1237
- deps.fs.mkdirSync(target, {
1238
- recursive: true,
1239
- mode: PRIVATE_DIRECTORY_MODE,
1240
- });
1241
- if ((options.platform || deps.platform()) !== "win32") {
1242
- // chmod updates ctime even when the mode is already correct. Session and
1243
- // security stores use ctime as part of their physical witness, so avoid
1244
- // invalidating an otherwise unchanged owner-only directory.
1245
- if (
1246
- !existed ||
1247
- !Number.isFinite(existingMode) ||
1248
- (existingMode & 0o7777) !== PRIVATE_DIRECTORY_MODE
1249
- ) {
1250
- deps.fs.chmodSync(target, PRIVATE_DIRECTORY_MODE);
1251
- }
1252
- } else if (
1253
- options.applyWindowsAcl === true ||
1254
- (!existed && options.applyWindowsAcl !== false)
1255
- ) {
1256
- const result = repairWindowsAclOnce(target, deps, options);
1257
- if (!result.ok && options.failIfUnavailable) {
1258
- throw new Error(result.error || `Could not secure ${target}`);
1259
- }
1260
- }
1261
- return target;
1262
- }
1263
-
1264
- export function ensurePrivateFile(target, options = {}) {
1265
- const deps = { ..._deps, ...(options.deps || {}) };
1266
- const platform = options.platform || deps.platform();
1267
- assertSafeOwnerOnlyPath(target, platform);
1268
- const nativeEvidence = assertNoLinkTraversalWithWindowsFallback(
1269
- target,
1270
- deps,
1271
- null,
1272
- platform,
1273
- );
1274
- const pathEntry = readPrivatePathEntry(
1275
- target,
1276
- deps,
1277
- platform,
1278
- nativeEvidence,
1279
- );
1280
- if (!pathEntry.exists) return target;
1281
- const existing = pathEntry.entry;
1282
- if (existing.isSymbolicLink()) {
1283
- throw new Error(
1284
- `Refusing owner-only file through a symbolic link: ${target}`,
1285
- );
1286
- }
1287
- if (typeof existing.isDirectory === "function" && existing.isDirectory()) {
1288
- throw new Error(`Expected a file for owner-only storage: ${target}`);
1289
- }
1290
- if (platform !== "win32") {
1291
- // Preserve ctime for an already-secure file. Rejected CAS writes and
1292
- // read-only preflights must not make a persisted physical witness stale.
1293
- const existingMode = Number(existing.mode);
1294
- if (
1295
- !Number.isFinite(existingMode) ||
1296
- (existingMode & 0o7777) !== PRIVATE_FILE_MODE
1297
- ) {
1298
- deps.fs.chmodSync(target, PRIVATE_FILE_MODE);
1299
- }
1300
- } else if (
1301
- options.applyWindowsAcl === true &&
1302
- (options.deps || !isSecuredWindowsPath(target, deps))
1303
- ) {
1304
- const [result] = windowsAclBatch([String(target)], "repair", deps, "file");
1305
- if (result.errorCode === "EXPECTED_KIND_MISMATCH") {
1306
- throw new Error(`Expected a file for owner-only storage: ${target}`);
1307
- }
1308
- if (!result.ok && result.exists !== false && options.failIfUnavailable) {
1309
- throw new Error(result.error || `Could not secure ${target}`);
1310
- }
1311
- if (result.ok && !options.deps) rememberSecuredWindowsPath(target, deps);
1312
- }
1313
- return target;
1314
- }
1
+ // One owner-only path/ACL implementation shared by CLI and native desktop hosts.
2
+ import storage from "@chainlesschain/session-core/private-storage";
3
+ export const {
4
+ PRIVATE_DIRECTORY_MODE,
5
+ PRIVATE_FILE_MODE,
6
+ assertSafeOwnerOnlyPath,
7
+ _deps,
8
+ _windowsAclWorkingDirectory,
9
+ _resolveWindowsAclTimeout,
10
+ inspectPrivatePaths,
11
+ repairPrivatePaths,
12
+ inspectPrivatePath,
13
+ repairPrivatePath,
14
+ ensurePrivateDirectory,
15
+ ensurePrivateFile,
16
+ } = storage;