blun-king-cli 9.0.0 → 9.1.0

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 (238) hide show
  1. package/LIESMICH.txt +32 -13
  2. package/README.md +39 -20
  3. package/bin/blun.js +248 -160
  4. package/bin/core-bootstrap.js +47 -0
  5. package/bin/king.js +277 -1
  6. package/bin/launcher-mode.js +15 -3
  7. package/bin/launcher-runtime.js +293 -0
  8. package/bin/plugin-bootstrap.js +11 -4
  9. package/bin/private-paths.js +1577 -10
  10. package/bin/update-lease.js +399 -0
  11. package/bin/update-notice.js +1245 -0
  12. package/blun.mjs +26606 -25239
  13. package/dist-web/assets/{CodeBlockNode-Bsvp6ZEy.js → CodeBlockNode-CXnTLMNr.js} +1 -1
  14. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-ta7hLjia.js → CodeBlockShell.vue_vue_type_style_index_0_lang-CCF8Ntnq.js} +1 -1
  15. package/dist-web/assets/{DesignSystemView-BmDDe28A.js → DesignSystemView-BbMOu-XO.js} +1 -1
  16. package/dist-web/assets/{Tooltip-nEEfaYVY.js → Tooltip-CFQEzqC-.js} +1 -1
  17. package/dist-web/assets/abnfDiagram-N423BO3Z-CsByy-GW.js +1 -0
  18. package/dist-web/assets/abnfDiagram-N423BO3Z-Z1APZG5m.js +1 -0
  19. package/dist-web/assets/arc-C-LXCaja.js +1 -0
  20. package/dist-web/assets/arc-C4VyWloT.js +1 -0
  21. package/dist-web/assets/architectureDiagram-T3A2C74G-DZq4L18e.js +36 -0
  22. package/dist-web/assets/architectureDiagram-T3A2C74G-DpddbUcM.js +36 -0
  23. package/dist-web/assets/blockDiagram-VBNYF7ZC-BV4sIf98.js +132 -0
  24. package/dist-web/assets/blockDiagram-VBNYF7ZC-BqccOSMr.js +132 -0
  25. package/dist-web/assets/{c4Diagram-AAUBKEIU-DtzacMnD.js → c4Diagram-5PPSVZJV-0MRkZlgP.js} +1 -1
  26. package/dist-web/assets/{c4Diagram-AAUBKEIU-D1VdMx8d.js → c4Diagram-5PPSVZJV-OHAcJrTy.js} +1 -1
  27. package/dist-web/assets/channel-B2VOQOI-.js +1 -0
  28. package/dist-web/assets/channel-DKjwM05G.js +1 -0
  29. package/dist-web/assets/{chunk-ND2GUHAM-DJoY9wNJ.js → chunk-2GRJ4B5K-DqJHs8Fw.js} +1 -1
  30. package/dist-web/assets/{chunk-ND2GUHAM-C4-rwdcv.js → chunk-2GRJ4B5K-oO6nqeLA.js} +1 -1
  31. package/dist-web/assets/{chunk-QZHKN3VN-C7YGaShq.js → chunk-2Q5K7J3B-B1nCd91o.js} +1 -1
  32. package/dist-web/assets/{chunk-QZHKN3VN-_Rz9_NuS.js → chunk-2Q5K7J3B-BC0Hsfqy.js} +1 -1
  33. package/dist-web/assets/chunk-5RXB4S5H-BfZa4yDH.js +231 -0
  34. package/dist-web/assets/chunk-5RXB4S5H-neG70kZx.js +231 -0
  35. package/dist-web/assets/{chunk-FMBD7UC4-DlLNDUx6.js → chunk-5VM5RSS4-l7biGqeH.js} +1 -1
  36. package/dist-web/assets/{chunk-FMBD7UC4-B2zs_Y-d.js → chunk-5VM5RSS4-u00AcpiJ.js} +1 -1
  37. package/dist-web/assets/chunk-6Q2QTUOP-BO-F11Gs.js +88 -0
  38. package/dist-web/assets/chunk-6Q2QTUOP-RUVRPBzG.js +88 -0
  39. package/dist-web/assets/{chunk-727SXJPM-Dn5wkDO_.js → chunk-GF5L2VYU-Ba-86KTd.js} +5 -5
  40. package/dist-web/assets/{chunk-727SXJPM-Dk2Xa3bA.js → chunk-GF5L2VYU-BtC69fw0.js} +5 -5
  41. package/dist-web/assets/{chunk-4BX2VUAB-DxZViaHS.js → chunk-JWPE2WC7-DIOubEqr.js} +1 -1
  42. package/dist-web/assets/{chunk-4BX2VUAB-WqrE2gaw.js → chunk-JWPE2WC7-u8Pcfhwh.js} +1 -1
  43. package/dist-web/assets/{chunk-2J33WTMH-Dm_s0_4f.js → chunk-KBJHAD2P-DDUeH_Ba.js} +1 -1
  44. package/dist-web/assets/{chunk-2J33WTMH-w4sdiKFO.js → chunk-KBJHAD2P-DS3BL64y.js} +1 -1
  45. package/dist-web/assets/chunk-RYQCIY6F-B9gFMVbS.js +1 -0
  46. package/dist-web/assets/chunk-RYQCIY6F-xg3EO0le.js +1 -0
  47. package/dist-web/assets/{chunk-55IACEB6-B5dE1-Um.js → chunk-XXDRQBXY-B60H0rRC.js} +1 -1
  48. package/dist-web/assets/{chunk-55IACEB6-B12zGIFz.js → chunk-XXDRQBXY-Bn1Xz6TQ.js} +1 -1
  49. package/dist-web/assets/classDiagram-JCYQIIEL-BA1TjAxj.js +1 -0
  50. package/dist-web/assets/classDiagram-JCYQIIEL-BNU7Qn4k.js +1 -0
  51. package/dist-web/assets/classDiagram-v2-OCEON4UE-BA1TjAxj.js +1 -0
  52. package/dist-web/assets/classDiagram-v2-OCEON4UE-BNU7Qn4k.js +1 -0
  53. package/dist-web/assets/{cose-bilkent-S5V4N54A-Bi19n8m-.js → cose-bilkent-JH36ORCC-4Ybddg08.js} +1 -1
  54. package/dist-web/assets/{cose-bilkent-S5V4N54A-c8o8NFp5.js → cose-bilkent-JH36ORCC-DqigO3T4.js} +1 -1
  55. package/dist-web/assets/cynefin-VYW2F7L2-C-EfYT07.js +178 -0
  56. package/dist-web/assets/cynefin-VYW2F7L2-rpD93tQ-.js +178 -0
  57. package/dist-web/assets/cynefinDiagram-MW4NZA55-DWZPLJcD.js +62 -0
  58. package/dist-web/assets/cynefinDiagram-MW4NZA55-DbTqnkVJ.js +62 -0
  59. package/dist-web/assets/dagre-VZM6K2ZE-DcM8aEVY.js +4 -0
  60. package/dist-web/assets/dagre-VZM6K2ZE-M_HVNtqP.js +4 -0
  61. package/dist-web/assets/diagram-7IWD3JNH-DoNximNF.js +30 -0
  62. package/dist-web/assets/diagram-7IWD3JNH-UymgsBNa.js +30 -0
  63. package/dist-web/assets/{diagram-KO2AKTUF-CFQs13i5.js → diagram-B4RE2ZJO-BVA2EhnC.js} +3 -3
  64. package/dist-web/assets/{diagram-KO2AKTUF-agFIuPJW.js → diagram-B4RE2ZJO-BiYu5WPF.js} +3 -3
  65. package/dist-web/assets/{diagram-LMA3HP47-TMIXizxR.js → diagram-LBJQPF4R-BD3kkgOO.js} +2 -2
  66. package/dist-web/assets/{diagram-LMA3HP47-He5zlyMt.js → diagram-LBJQPF4R-m3a1Euj3.js} +2 -2
  67. package/dist-web/assets/diagram-Q27KOJAE-D1mYQ_cI.js +24 -0
  68. package/dist-web/assets/diagram-Q27KOJAE-hy_T73PK.js +24 -0
  69. package/dist-web/assets/diagram-UB23O5K3-BWkoQZ4A.js +41 -0
  70. package/dist-web/assets/diagram-UB23O5K3-BtX8u6HG.js +41 -0
  71. package/dist-web/assets/ebnfDiagram-BXEA7PRR-BKs9JUHW.js +1 -0
  72. package/dist-web/assets/ebnfDiagram-BXEA7PRR-CBAnFkRF.js +1 -0
  73. package/dist-web/assets/erDiagram-JOGREHBK-3vKcWsxm.js +85 -0
  74. package/dist-web/assets/erDiagram-JOGREHBK-BIpd6dTf.js +85 -0
  75. package/dist-web/assets/flowDiagram-UKHOOZJN-D9wBbHm2.js +156 -0
  76. package/dist-web/assets/flowDiagram-UKHOOZJN-Z83i2UWI.js +156 -0
  77. package/dist-web/assets/ganttDiagram-PKOTCBZU-Cd_dViF-.js +292 -0
  78. package/dist-web/assets/ganttDiagram-PKOTCBZU-DFrb_S1d.js +292 -0
  79. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-BSDFfwdY.js → gitGraphDiagram-DS77QQ5N-BiEEtTo7.js} +3 -3
  80. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-DMbTATyw.js → gitGraphDiagram-DS77QQ5N-Dp3Ar-hi.js} +3 -3
  81. package/dist-web/assets/{graph-BwjfAU3j.js → graph-CAUpWmoK.js} +1 -1
  82. package/dist-web/assets/{graph--OzhPTMs.js → graph-DOmOIIwC.js} +1 -1
  83. package/dist-web/assets/{index-W6VnSrkB.js → index-Ba1OvLjf.js} +2 -2
  84. package/dist-web/assets/{index-CkVgGDpx.js → index-CiPA8Kex.js} +4 -4
  85. package/dist-web/assets/{index-BzK4vJBu.js → index-DdEakFJM.js} +1 -1
  86. package/dist-web/assets/{index10-Czg_oF-W.js → index10-CNr4U-Nw.js} +1 -1
  87. package/dist-web/assets/{index11-BjzCZuCr.js → index11-Dto_yZNz.js} +1 -1
  88. package/dist-web/assets/{index5-DlVoNkCD.js → index5-B2PldaO2.js} +1 -1
  89. package/dist-web/assets/{index6-BxiXA2yo.js → index6-CBdZmmyx.js} +1 -1
  90. package/dist-web/assets/{index7-BPdza6d1.js → index7-Dz9oCsUF.js} +1 -1
  91. package/dist-web/assets/{index8-Q_XzGC1Z.js → index8-B6k25jtU.js} +2 -2
  92. package/dist-web/assets/{infoDiagram-5YYISTIA-2JF3XEdD.js → infoDiagram-6WML65LV-BSa2g04G.js} +1 -1
  93. package/dist-web/assets/{infoDiagram-5YYISTIA-UrQusrJq.js → infoDiagram-6WML65LV-D4ph2sIH.js} +1 -1
  94. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-7RVUL-vB.js → ishikawaDiagram-WSZJBQD7-DjKjbMm9.js} +5 -5
  95. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-C5OstRVV.js → ishikawaDiagram-WSZJBQD7-Hm36UOQe.js} +5 -5
  96. package/dist-web/assets/{journeyDiagram-JHISSGLW-B34fCMu9.js → journeyDiagram-NVQOT4AX-DttHDfPQ.js} +1 -1
  97. package/dist-web/assets/{journeyDiagram-JHISSGLW-yctdo4bX.js → journeyDiagram-NVQOT4AX-MvWWPNDV.js} +1 -1
  98. package/dist-web/assets/{kanban-definition-UN3LZRKU-BS4FBZsQ.js → kanban-definition-27J2QSJJ-C5gB-W0d.js} +7 -7
  99. package/dist-web/assets/{kanban-definition-UN3LZRKU-CxBCv27E.js → kanban-definition-27J2QSJJ-Cu-E_cXA.js} +5 -5
  100. package/dist-web/assets/layout-CvP6o9AK.js +1 -0
  101. package/dist-web/assets/layout-D-LzfAck.js +1 -0
  102. package/dist-web/assets/{linear-3mB6q2-g.js → linear-B3HWgQI2.js} +1 -1
  103. package/dist-web/assets/{linear-BKCnUo31.js → linear-BRGe4-d6.js} +1 -1
  104. package/dist-web/assets/map-CWKZqZjr.js +1 -0
  105. package/dist-web/assets/map-DxJ2ADlA.js +1 -0
  106. package/dist-web/assets/mermaid.core-D-9Sx1No.js +314 -0
  107. package/dist-web/assets/mermaidParser.worker-PTFfzpoB.js +318 -0
  108. package/dist-web/assets/{mindmap-definition-RKZ34NQL-BmJZpjh6.js → mindmap-definition-FAOFIHXS-C4vnms3i.js} +2 -2
  109. package/dist-web/assets/{mindmap-definition-RKZ34NQL-1mjWUcf6.js → mindmap-definition-FAOFIHXS-Cg_hoVIq.js} +1 -1
  110. package/dist-web/assets/pegDiagram-VL7TDLO6-B2fFEFUH.js +1 -0
  111. package/dist-web/assets/pegDiagram-VL7TDLO6-CSX9hitD.js +1 -0
  112. package/dist-web/assets/pieDiagram-7S7Q4E2Y-0L0mxahZ.js +39 -0
  113. package/dist-web/assets/pieDiagram-7S7Q4E2Y-C7T0Cmw1.js +39 -0
  114. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-BmT_qM09.js → quadrantDiagram-CIZ2JOQS-BBy7w4kD.js} +3 -3
  115. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-C0kPuyNP.js → quadrantDiagram-CIZ2JOQS-U483z0er.js} +3 -3
  116. package/dist-web/assets/railroadDiagram-AXF67PYL-D80a2_DJ.js +1 -0
  117. package/dist-web/assets/railroadDiagram-AXF67PYL-DX8y0MzW.js +1 -0
  118. package/dist-web/assets/{requirementDiagram-4Y6WPE33-C0YP_Amt.js → requirementDiagram-LRYGKXZP-CeyHfq8t.js} +3 -3
  119. package/dist-web/assets/{requirementDiagram-4Y6WPE33-CZrxH1Y2.js → requirementDiagram-LRYGKXZP-DGD5J_4k.js} +3 -3
  120. package/dist-web/assets/sankeyDiagram-W5VNT64P-CCISkkrR.js +40 -0
  121. package/dist-web/assets/sankeyDiagram-W5VNT64P-CT5PdZz2.js +40 -0
  122. package/dist-web/assets/sequenceDiagram-SI44F4Z6-BeMzHWXI.js +162 -0
  123. package/dist-web/assets/sequenceDiagram-SI44F4Z6-jQfm2XS7.js +162 -0
  124. package/dist-web/assets/sizeCapture-X5ZJPWSS-BQsTdtrv.js +1 -0
  125. package/dist-web/assets/sizeCapture-X5ZJPWSS-lRZsmCoS.js +1 -0
  126. package/dist-web/assets/stateDiagram-OKZ733FA-ClCgTdUm.js +1 -0
  127. package/dist-web/assets/stateDiagram-OKZ733FA-Cv59z5YT.js +1 -0
  128. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-R8jTc--F.js +1 -0
  129. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fi_lrTAx.js +1 -0
  130. package/dist-web/assets/swimlanes-SLNWSIFB-BCdOUN05.js +2 -0
  131. package/dist-web/assets/swimlanes-SLNWSIFB-Cu5l7SuR.js +1 -0
  132. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-BUd_uYK3.js +8 -0
  133. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-CalrsgPK.js +8 -0
  134. package/dist-web/assets/{timeline-definition-PNZ67QCA-DsVZdrZa.js → timeline-definition-Z64GVDOM-CMjnh4bu.js} +4 -4
  135. package/dist-web/assets/{timeline-definition-PNZ67QCA-DnB6Usl0.js → timeline-definition-Z64GVDOM-DpWutSKa.js} +4 -4
  136. package/dist-web/assets/vennDiagram-T6HMQDX7-DKJW_8oU.js +34 -0
  137. package/dist-web/assets/vennDiagram-T6HMQDX7-DVC5JVsc.js +34 -0
  138. package/dist-web/assets/{vue.runtime.esm-bundler-pgHGheCW.js → vue.runtime.esm-bundler-BwwRtQwQ.js} +1 -1
  139. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-oBauTCEw.js → wardleyDiagram-T6FBY63Y-BK2mrq7L.js} +3 -3
  140. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-DCiyDQNB.js → wardleyDiagram-T6FBY63Y-oUvgepFn.js} +3 -3
  141. package/dist-web/assets/xychartDiagram-ELKLHX3M-DI6n79q0.js +7 -0
  142. package/dist-web/assets/xychartDiagram-ELKLHX3M-DUJu9F7-.js +7 -0
  143. package/dist-web/index.html +1 -1
  144. package/dist-web/vis/index.html.gz +0 -0
  145. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  146. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  147. package/package.json +9 -11
  148. package/skills/screenshot-lesen/SKILL.md +0 -1
  149. package/skills/web-lesen/SKILL.md +0 -1
  150. package/telegram-plugin/dist/bridge.mjs +7 -25
  151. package/telegram-plugin/dist/mcp-server.mjs +236 -71
  152. package/dist-web/assets/arc-4xPfymbf.js +0 -1
  153. package/dist-web/assets/arc-Doj0wRZ0.js +0 -1
  154. package/dist-web/assets/architectureDiagram-3BPJPVTR-BGih283a.js +0 -36
  155. package/dist-web/assets/architectureDiagram-3BPJPVTR-CQ09RrbH.js +0 -36
  156. package/dist-web/assets/blockDiagram-GPEHLZMM-3ShMf1gu.js +0 -132
  157. package/dist-web/assets/blockDiagram-GPEHLZMM-CAtDjkJt.js +0 -132
  158. package/dist-web/assets/channel-DNkUo9e6.js +0 -1
  159. package/dist-web/assets/channel-DcnUTIGB.js +0 -1
  160. package/dist-web/assets/chunk-AQP2D5EJ-FS8-f8lF.js +0 -231
  161. package/dist-web/assets/chunk-AQP2D5EJ-UKhb4V4r.js +0 -231
  162. package/dist-web/assets/classDiagram-4FO5ZUOK-B1ZO8EbE.js +0 -1
  163. package/dist-web/assets/classDiagram-4FO5ZUOK-xk6cM9Kq.js +0 -1
  164. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-B1ZO8EbE.js +0 -1
  165. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-xk6cM9Kq.js +0 -1
  166. package/dist-web/assets/dagre-BM42HDAG-CmUO9qKN.js +0 -4
  167. package/dist-web/assets/dagre-BM42HDAG-DLMfLCoV.js +0 -4
  168. package/dist-web/assets/diagram-2AECGRRQ-DQTst0OH.js +0 -43
  169. package/dist-web/assets/diagram-2AECGRRQ-L74iLMqd.js +0 -43
  170. package/dist-web/assets/diagram-5GNKFQAL-BiBm7CJR.js +0 -10
  171. package/dist-web/assets/diagram-5GNKFQAL-DoE6q7H1.js +0 -10
  172. package/dist-web/assets/diagram-OG6HWLK6-BHCt1E0n.js +0 -24
  173. package/dist-web/assets/diagram-OG6HWLK6-DRyxjFMa.js +0 -24
  174. package/dist-web/assets/erDiagram-TEJ5UH35-BoDczjGG.js +0 -85
  175. package/dist-web/assets/erDiagram-TEJ5UH35-yd6SOv_7.js +0 -85
  176. package/dist-web/assets/flowDiagram-I6XJVG4X-DHoceI7r.js +0 -162
  177. package/dist-web/assets/flowDiagram-I6XJVG4X-DkYNQv0R.js +0 -162
  178. package/dist-web/assets/ganttDiagram-6RSMTGT7-Cg1TQBc0.js +0 -292
  179. package/dist-web/assets/ganttDiagram-6RSMTGT7-Z6klo7KK.js +0 -292
  180. package/dist-web/assets/layout-C1ojF0zw.js +0 -1
  181. package/dist-web/assets/layout-SsrduOYp.js +0 -1
  182. package/dist-web/assets/mermaid.core-D_lKT0dR.js +0 -309
  183. package/dist-web/assets/mermaidParser.worker-Dx4jPi9z.js +0 -313
  184. package/dist-web/assets/pieDiagram-4H26LBE5-BU41t2A_.js +0 -30
  185. package/dist-web/assets/pieDiagram-4H26LBE5-BkwDAvbt.js +0 -30
  186. package/dist-web/assets/sankeyDiagram-5OEKKPKP-De_o7hDr.js +0 -40
  187. package/dist-web/assets/sankeyDiagram-5OEKKPKP-Dvuj6bMA.js +0 -40
  188. package/dist-web/assets/sequenceDiagram-3UESZ5HK-DuCrgPyA.js +0 -162
  189. package/dist-web/assets/sequenceDiagram-3UESZ5HK-VAxffBe7.js +0 -162
  190. package/dist-web/assets/stateDiagram-AJRCARHV-CYLupxM7.js +0 -1
  191. package/dist-web/assets/stateDiagram-AJRCARHV-nDExQydZ.js +0 -1
  192. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-BhEc7Thx.js +0 -1
  193. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-BzwD_BIl.js +0 -1
  194. package/dist-web/assets/vennDiagram-CIIHVFJN-BBHQmCFf.js +0 -34
  195. package/dist-web/assets/vennDiagram-CIIHVFJN-Cfj-K9NW.js +0 -34
  196. package/dist-web/assets/wardley-L42UT6IY-BJFn8eDD.js +0 -173
  197. package/dist-web/assets/wardley-L42UT6IY-BSu8-4hg.js +0 -173
  198. package/dist-web/assets/xychartDiagram-2RQKCTM6-C2iZpl9I.js +0 -7
  199. package/dist-web/assets/xychartDiagram-2RQKCTM6-Db_VoDBV.js +0 -7
  200. package/mnemo/access_routes.js +0 -692
  201. package/mnemo/agent_governance.js +0 -4242
  202. package/mnemo/agent_mail.js +0 -901
  203. package/mnemo/bootstrap_auto.js +0 -137
  204. package/mnemo/brief_coordination.js +0 -226
  205. package/mnemo/code_read_tools.js +0 -375
  206. package/mnemo/context_preview_tools.js +0 -603
  207. package/mnemo/embeddings.js +0 -66
  208. package/mnemo/external_repo_ops.js +0 -575
  209. package/mnemo/facts/example-project-rules.json +0 -90
  210. package/mnemo/facts/example.json +0 -34
  211. package/mnemo/identity_schema.sql +0 -139
  212. package/mnemo/journal_schema.js +0 -561
  213. package/mnemo/loop_doctor_tools.js +0 -661
  214. package/mnemo/mail_secret_refs.js +0 -150
  215. package/mnemo/mcp.js +0 -9309
  216. package/mnemo/memory_consolidation.js +0 -1914
  217. package/mnemo/memory_health_tools.js +0 -165
  218. package/mnemo/package.json +0 -79
  219. package/mnemo/protected_scope_gate.js +0 -627
  220. package/mnemo/resource_access_control.js +0 -684
  221. package/mnemo/runtime_governance.js +0 -1256
  222. package/mnemo/runtime_turn_gate.js +0 -862
  223. package/mnemo/sandbox.js +0 -143
  224. package/mnemo/schema.sql +0 -389
  225. package/mnemo/shared_utils.js +0 -763
  226. package/mnemo/skills/agent-auto-resume/SKILL.md +0 -56
  227. package/mnemo/skills/agent_hand/SKILL.md +0 -43
  228. package/mnemo/skills/agent_hand/run.js +0 -63
  229. package/mnemo/skills/book_flight/SKILL.md +0 -34
  230. package/mnemo/skills/external_repo_review/SKILL.md +0 -43
  231. package/mnemo/skills/external_repo_review/run.js +0 -73
  232. package/mnemo/skills/pay_invoice/SKILL.md +0 -34
  233. package/mnemo/team_quality_ops.js +0 -944
  234. package/mnemo/timeline_report_tools.js +0 -810
  235. package/mnemo/write_gate_risk.js +0 -80
  236. package/mnemo/writer_health.js +0 -152
  237. package/skills/doku-ingestion/SKILL.md +0 -48
  238. package/skills/doku-ingestion/ingest_docs.py +0 -133
@@ -1,33 +1,1599 @@
1
1
  'use strict';
2
2
 
3
+ const { execFileSync } = require('node:child_process');
3
4
  const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { gzipSync } = require('node:zlib');
4
7
 
5
8
  const PRIVATE_DIRECTORY_MODE = 0o700;
6
9
  const PRIVATE_FILE_MODE = 0o600;
10
+ const PRIVATE_TREE_MAX_DEPTH = 64;
11
+ const PRIVATE_TREE_MAX_ENTRIES = 4096;
12
+ const PRIVATE_TREE_MAX_PATH_LENGTH = 4096;
13
+ const WINDOWS_ACL_OK_SENTINEL = 'BLUN_PRIVATE_ACL_OK_V1';
14
+ const WINDOWS_SID_PATTERN = /S-\d-\d+(?:-\d+)+/gi;
15
+ const WINDOWS_LOCAL_DOS_PATH_PATTERN = /^[A-Za-z]:\\/u;
16
+ const WINDOWS_RESERVED_PATH_SEGMENT = /^(?:CON|PRN|AUX|NUL|CONIN\$|CONOUT\$|COM[1-9]|LPT[1-9])(?:\..*)?$/iu;
7
17
 
8
- function applyPosixMode(targetPath, mode, options = {}) {
9
- const fsImpl = options.fsImpl || fs;
10
- const platform = options.platform || process.platform;
18
+ const WINDOWS_ACL_CSHARP = String.raw`using Microsoft.Win32.SafeHandles;
19
+ using System;
20
+ using System.Collections.Generic;
21
+ using System.ComponentModel;
22
+ using System.IO;
23
+ using System.Runtime.InteropServices;
24
+ using System.Security.AccessControl;
25
+ using System.Security.Principal;
26
+ using System.Text;
27
+
28
+ public static class BlunPrivateAclNative
29
+ {
30
+ private const uint GENERIC_READ = 0x80000000;
31
+ private const uint GENERIC_WRITE = 0x40000000;
32
+ private const uint DELETE = 0x00010000;
33
+ private const uint READ_CONTROL = 0x00020000;
34
+ private const uint WRITE_DAC = 0x00040000;
35
+ private const uint SYNCHRONIZE = 0x00100000;
36
+ private const uint CREATE_NEW = 1;
37
+ private const uint OPEN_EXISTING = 3;
38
+ private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
39
+ private const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
40
+ private const uint FILE_ATTRIBUTE_DEVICE = 0x00000040;
41
+ private const uint FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
42
+ private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
43
+ private const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
44
+ private const uint FILE_DIRECTORY_FILE = 0x00000001;
45
+ private const uint FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
46
+ private const uint FILE_OPEN_REPARSE_POINT = 0x00200000;
47
+ private const uint FILE_OPEN_IF = 3;
48
+ private const uint OBJ_CASE_INSENSITIVE = 0x00000040;
49
+ private const uint SE_FILE_OBJECT = 1;
50
+ private const uint OWNER_SECURITY_INFORMATION = 0x00000001;
51
+ private const uint DACL_SECURITY_INFORMATION = 0x00000004;
52
+ private const uint PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000;
53
+ private const int FILE_DISPOSITION_INFO_CLASS = 4;
54
+ private const int FILE_STANDARD_INFO_CLASS = 1;
55
+ private const int FILE_REMOTE_PROTOCOL_INFO_CLASS = 13;
56
+ private const int FILE_ID_BOTH_DIRECTORY_INFORMATION_CLASS = 37;
57
+ private const int FILE_STANDARD_INFO_BUFFER_SIZE = 24;
58
+ private const int REMOTE_PROTOCOL_INFO_BUFFER_SIZE = 256;
59
+ private const int DIRECTORY_INFORMATION_BUFFER_SIZE = 65536;
60
+ private const int DIRECTORY_INFORMATION_FILE_ID_OFFSET = 96;
61
+ private const int DIRECTORY_INFORMATION_NAME_OFFSET = 104;
62
+ private const int MAX_TREE_DEPTH = 64;
63
+ private const int MAX_TREE_ENTRIES = 4096;
64
+ private const int MAX_TREE_PATH_LENGTH = 4096;
65
+ private const int ERROR_INVALID_PARAMETER = 87;
66
+ private const uint FILE_TYPE_DISK = 1;
67
+ private const int STATUS_NO_MORE_FILES = unchecked((int)0x80000006);
68
+
69
+ private sealed class TreeEntry
70
+ {
71
+ public string Name;
72
+ public uint Attributes;
73
+ public ulong FileId;
74
+ }
75
+
76
+ private sealed class HeldTreeHandle
77
+ {
78
+ public SafeFileHandle Handle;
79
+ public string Path;
80
+ public bool Directory;
81
+ public string Owner;
82
+ }
83
+
84
+ [StructLayout(LayoutKind.Sequential)]
85
+ private struct SecurityAttributes
86
+ {
87
+ public int Length;
88
+ public IntPtr SecurityDescriptor;
89
+ [MarshalAs(UnmanagedType.Bool)] public bool InheritHandle;
90
+ }
91
+
92
+ [StructLayout(LayoutKind.Sequential)]
93
+ private struct FileTime
94
+ {
95
+ public uint Low;
96
+ public uint High;
97
+ }
98
+
99
+ [StructLayout(LayoutKind.Sequential)]
100
+ private struct ByHandleFileInformation
101
+ {
102
+ public uint FileAttributes;
103
+ public FileTime CreationTime;
104
+ public FileTime LastAccessTime;
105
+ public FileTime LastWriteTime;
106
+ public uint VolumeSerialNumber;
107
+ public uint FileSizeHigh;
108
+ public uint FileSizeLow;
109
+ public uint NumberOfLinks;
110
+ public uint FileIndexHigh;
111
+ public uint FileIndexLow;
112
+ }
113
+
114
+ [StructLayout(LayoutKind.Sequential)]
115
+ private struct FileDispositionInfo
116
+ {
117
+ [MarshalAs(UnmanagedType.Bool)] public bool DeleteFile;
118
+ }
119
+
120
+ [StructLayout(LayoutKind.Sequential)]
121
+ private struct UnicodeString
122
+ {
123
+ public ushort Length;
124
+ public ushort MaximumLength;
125
+ public IntPtr Buffer;
126
+ }
127
+
128
+ [StructLayout(LayoutKind.Sequential)]
129
+ private struct ObjectAttributes
130
+ {
131
+ public int Length;
132
+ public IntPtr RootDirectory;
133
+ public IntPtr ObjectName;
134
+ public uint Attributes;
135
+ public IntPtr SecurityDescriptor;
136
+ public IntPtr SecurityQualityOfService;
137
+ }
138
+
139
+ [StructLayout(LayoutKind.Sequential)]
140
+ private struct IoStatusBlock
141
+ {
142
+ public IntPtr Status;
143
+ public UIntPtr Information;
144
+ }
145
+
146
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true,
147
+ EntryPoint = "CreateFileW")]
148
+ private static extern SafeFileHandle CreateFileNew(
149
+ string fileName,
150
+ uint desiredAccess,
151
+ uint shareMode,
152
+ ref SecurityAttributes securityAttributes,
153
+ uint creationDisposition,
154
+ uint flagsAndAttributes,
155
+ IntPtr templateFile);
156
+
157
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true,
158
+ EntryPoint = "CreateFileW")]
159
+ private static extern SafeFileHandle CreateFileExisting(
160
+ string fileName,
161
+ uint desiredAccess,
162
+ uint shareMode,
163
+ IntPtr securityAttributes,
164
+ uint creationDisposition,
165
+ uint flagsAndAttributes,
166
+ IntPtr templateFile);
167
+
168
+ [DllImport("kernel32.dll", SetLastError = true)]
169
+ [return: MarshalAs(UnmanagedType.Bool)]
170
+ private static extern bool GetFileInformationByHandle(
171
+ SafeFileHandle handle,
172
+ out ByHandleFileInformation information);
173
+
174
+ [DllImport("kernel32.dll", SetLastError = true)]
175
+ private static extern uint GetFileType(SafeFileHandle handle);
176
+
177
+ [DllImport("kernel32.dll", SetLastError = true)]
178
+ [return: MarshalAs(UnmanagedType.Bool)]
179
+ private static extern bool GetFileInformationByHandleEx(
180
+ SafeFileHandle handle,
181
+ int informationClass,
182
+ IntPtr information,
183
+ uint bufferSize);
184
+
185
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
186
+ private static extern uint GetFinalPathNameByHandleW(
187
+ SafeFileHandle handle,
188
+ StringBuilder path,
189
+ uint pathLength,
190
+ uint flags);
191
+
192
+ [DllImport("kernel32.dll", SetLastError = true)]
193
+ [return: MarshalAs(UnmanagedType.Bool)]
194
+ private static extern bool SetFileInformationByHandle(
195
+ SafeFileHandle handle,
196
+ int informationClass,
197
+ ref FileDispositionInfo information,
198
+ uint bufferSize);
199
+
200
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
201
+ [return: MarshalAs(UnmanagedType.Bool)]
202
+ private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptorW(
203
+ string stringSecurityDescriptor,
204
+ uint stringSdRevision,
205
+ out IntPtr securityDescriptor,
206
+ out uint securityDescriptorSize);
207
+
208
+ [DllImport("advapi32.dll", SetLastError = true)]
209
+ [return: MarshalAs(UnmanagedType.Bool)]
210
+ private static extern bool GetSecurityDescriptorDacl(
211
+ IntPtr securityDescriptor,
212
+ [MarshalAs(UnmanagedType.Bool)] out bool daclPresent,
213
+ out IntPtr dacl,
214
+ [MarshalAs(UnmanagedType.Bool)] out bool daclDefaulted);
215
+
216
+ [DllImport("advapi32.dll", SetLastError = true)]
217
+ private static extern uint SetSecurityInfo(
218
+ SafeFileHandle handle,
219
+ uint objectType,
220
+ uint securityInformation,
221
+ IntPtr owner,
222
+ IntPtr group,
223
+ IntPtr dacl,
224
+ IntPtr sacl);
225
+
226
+ [DllImport("advapi32.dll", SetLastError = true)]
227
+ private static extern uint GetSecurityInfo(
228
+ SafeFileHandle handle,
229
+ uint objectType,
230
+ uint securityInformation,
231
+ out IntPtr owner,
232
+ out IntPtr group,
233
+ out IntPtr dacl,
234
+ out IntPtr sacl,
235
+ out IntPtr securityDescriptor);
236
+
237
+ [DllImport("advapi32.dll", SetLastError = true)]
238
+ private static extern uint GetSecurityDescriptorLength(IntPtr securityDescriptor);
239
+
240
+ [DllImport("kernel32.dll")]
241
+ private static extern IntPtr LocalFree(IntPtr memory);
242
+
243
+ [DllImport("ntdll.dll")]
244
+ private static extern int NtCreateFile(
245
+ out IntPtr fileHandle,
246
+ uint desiredAccess,
247
+ ref ObjectAttributes objectAttributes,
248
+ out IoStatusBlock ioStatusBlock,
249
+ IntPtr allocationSize,
250
+ uint fileAttributes,
251
+ uint shareAccess,
252
+ uint createDisposition,
253
+ uint createOptions,
254
+ IntPtr eaBuffer,
255
+ uint eaLength);
256
+
257
+ [DllImport("ntdll.dll")]
258
+ private static extern uint RtlNtStatusToDosError(int status);
259
+
260
+ [DllImport("ntdll.dll")]
261
+ private static extern int NtQueryDirectoryFile(
262
+ SafeFileHandle fileHandle,
263
+ IntPtr eventHandle,
264
+ IntPtr apcRoutine,
265
+ IntPtr apcContext,
266
+ out IoStatusBlock ioStatusBlock,
267
+ IntPtr fileInformation,
268
+ uint length,
269
+ int fileInformationClass,
270
+ [MarshalAs(UnmanagedType.U1)] bool returnSingleEntry,
271
+ IntPtr fileName,
272
+ [MarshalAs(UnmanagedType.U1)] bool restartScan);
273
+
274
+ private static HashSet<string> ExpectedSids(string userSid)
275
+ {
276
+ SecurityIdentifier user = new SecurityIdentifier(userSid);
277
+ HashSet<string> expected = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
278
+ expected.Add(user.Value);
279
+ expected.Add("S-1-5-18");
280
+ expected.Add("S-1-5-32-544");
281
+ return expected;
282
+ }
283
+
284
+ private static string ExactDaclSddl(string userSid, bool directory)
285
+ {
286
+ string flags = directory ? "OICI" : "";
287
+ string[] trustees = new string[] {
288
+ new SecurityIdentifier(userSid).Value,
289
+ "S-1-5-18",
290
+ "S-1-5-32-544"
291
+ };
292
+ HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
293
+ StringBuilder builder = new StringBuilder("D:P");
294
+ foreach (string trustee in trustees)
295
+ {
296
+ if (!seen.Add(trustee)) continue;
297
+ builder.Append("(A;").Append(flags).Append(";FA;;;")
298
+ .Append(trustee).Append(")");
299
+ }
300
+ return builder.ToString();
301
+ }
302
+
303
+ private static IntPtr CreateExactDescriptor(string userSid, bool directory)
304
+ {
305
+ IntPtr descriptor;
306
+ uint descriptorSize;
307
+ if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
308
+ ExactDaclSddl(userSid, directory), 1, out descriptor, out descriptorSize))
309
+ {
310
+ throw new Win32Exception(Marshal.GetLastWin32Error());
311
+ }
312
+ return descriptor;
313
+ }
314
+
315
+ private static void SetExactDacl(SafeFileHandle handle, string userSid, bool directory)
316
+ {
317
+ IntPtr descriptor = CreateExactDescriptor(userSid, directory);
318
+ try
319
+ {
320
+ bool present;
321
+ bool defaulted;
322
+ IntPtr dacl;
323
+ if (!GetSecurityDescriptorDacl(descriptor, out present, out dacl, out defaulted)
324
+ || !present || dacl == IntPtr.Zero)
325
+ {
326
+ throw new Win32Exception(Marshal.GetLastWin32Error());
327
+ }
328
+ uint result = SetSecurityInfo(
329
+ handle,
330
+ SE_FILE_OBJECT,
331
+ DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
332
+ IntPtr.Zero,
333
+ IntPtr.Zero,
334
+ dacl,
335
+ IntPtr.Zero);
336
+ if (result != 0) throw new Win32Exception((int)result);
337
+ }
338
+ finally
339
+ {
340
+ LocalFree(descriptor);
341
+ }
342
+ }
343
+
344
+ private static RawSecurityDescriptor ReadAccessDescriptor(SafeFileHandle handle)
345
+ {
346
+ IntPtr owner;
347
+ IntPtr group;
348
+ IntPtr dacl;
349
+ IntPtr sacl;
350
+ IntPtr descriptor;
351
+ uint result = GetSecurityInfo(
352
+ handle,
353
+ SE_FILE_OBJECT,
354
+ OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
355
+ out owner,
356
+ out group,
357
+ out dacl,
358
+ out sacl,
359
+ out descriptor);
360
+ if (result != 0) throw new Win32Exception((int)result);
361
+ try
362
+ {
363
+ uint length = GetSecurityDescriptorLength(descriptor);
364
+ if (length == 0 || length > 65536) throw new InvalidDataException();
365
+ byte[] bytes = new byte[length];
366
+ Marshal.Copy(descriptor, bytes, 0, (int)length);
367
+ return new RawSecurityDescriptor(bytes, 0);
368
+ }
369
+ finally
370
+ {
371
+ LocalFree(descriptor);
372
+ }
373
+ }
374
+
375
+ private static string ValidateOwner(
376
+ SafeFileHandle handle,
377
+ HashSet<string> expectedSids)
378
+ {
379
+ RawSecurityDescriptor descriptor = ReadAccessDescriptor(handle);
380
+ if (descriptor.Owner == null || !expectedSids.Contains(descriptor.Owner.Value))
381
+ throw new UnauthorizedAccessException();
382
+ return descriptor.Owner.Value;
383
+ }
384
+
385
+ private static void VerifyExactDacl(
386
+ SafeFileHandle handle,
387
+ string userSid,
388
+ bool directory,
389
+ string expectedOwner)
390
+ {
391
+ HashSet<string> expectedSids = ExpectedSids(userSid);
392
+ RawSecurityDescriptor descriptor = ReadAccessDescriptor(handle);
393
+ if (descriptor.Owner == null
394
+ || !String.Equals(descriptor.Owner.Value, expectedOwner, StringComparison.OrdinalIgnoreCase))
395
+ throw new UnauthorizedAccessException();
396
+ if ((descriptor.ControlFlags & ControlFlags.DiscretionaryAclProtected) == 0)
397
+ throw new UnauthorizedAccessException();
398
+ RawAcl acl = descriptor.DiscretionaryAcl;
399
+ if (acl == null || acl.Count != expectedSids.Count)
400
+ throw new UnauthorizedAccessException();
401
+
402
+ AceFlags expectedFlags = directory
403
+ ? AceFlags.ObjectInherit | AceFlags.ContainerInherit
404
+ : AceFlags.None;
405
+ HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
406
+ foreach (GenericAce genericAce in acl)
407
+ {
408
+ CommonAce ace = genericAce as CommonAce;
409
+ if (ace == null
410
+ || ace.AceQualifier != AceQualifier.AccessAllowed
411
+ || ace.AccessMask != (int)FileSystemRights.FullControl
412
+ || ace.AceFlags != expectedFlags
413
+ || ace.SecurityIdentifier == null
414
+ || !expectedSids.Contains(ace.SecurityIdentifier.Value)
415
+ || !seen.Add(ace.SecurityIdentifier.Value))
416
+ {
417
+ throw new UnauthorizedAccessException();
418
+ }
419
+ }
420
+ if (!seen.SetEquals(expectedSids)) throw new UnauthorizedAccessException();
421
+ }
422
+
423
+ private static ByHandleFileInformation ValidateHandle(
424
+ SafeFileHandle handle,
425
+ bool directory)
426
+ {
427
+ ByHandleFileInformation information;
428
+ if (!GetFileInformationByHandle(handle, out information))
429
+ throw new Win32Exception(Marshal.GetLastWin32Error());
430
+ bool actualDirectory = (information.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
431
+ if (actualDirectory != directory
432
+ || (information.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0)
433
+ {
434
+ throw new UnauthorizedAccessException();
435
+ }
436
+ if (GetFileType(handle) != FILE_TYPE_DISK)
437
+ throw new UnauthorizedAccessException();
438
+ if (!directory) VerifyFreshFileState(handle, false, true);
439
+ VerifyLocalHandle(handle);
440
+ return information;
441
+ }
442
+
443
+ private static ulong FileId(ByHandleFileInformation information)
444
+ {
445
+ return ((ulong)information.FileIndexHigh << 32) | information.FileIndexLow;
446
+ }
447
+
448
+ private static void VerifyTreeHandleIdentity(SafeFileHandle handle, ulong expectedFileId)
449
+ {
450
+ ByHandleFileInformation information;
451
+ if (!GetFileInformationByHandle(handle, out information))
452
+ throw new Win32Exception(Marshal.GetLastWin32Error());
453
+ ulong actualFileId = FileId(information);
454
+ if (expectedFileId == 0 || actualFileId == 0 || actualFileId != expectedFileId)
455
+ throw new UnauthorizedAccessException();
456
+ }
457
+
458
+ private static void VerifyFreshFileState(
459
+ SafeFileHandle handle,
460
+ bool expectedDeletePending,
461
+ bool requireSingleLink)
462
+ {
463
+ IntPtr buffer = Marshal.AllocHGlobal(FILE_STANDARD_INFO_BUFFER_SIZE);
464
+ try
465
+ {
466
+ for (int offset = 0; offset < FILE_STANDARD_INFO_BUFFER_SIZE; offset += 4)
467
+ Marshal.WriteInt32(buffer, offset, 0);
468
+ if (!GetFileInformationByHandleEx(
469
+ handle,
470
+ FILE_STANDARD_INFO_CLASS,
471
+ buffer,
472
+ FILE_STANDARD_INFO_BUFFER_SIZE))
473
+ {
474
+ throw new Win32Exception(Marshal.GetLastWin32Error());
475
+ }
476
+ uint numberOfLinks = unchecked((uint)Marshal.ReadInt32(buffer, 16));
477
+ bool deletePending = Marshal.ReadByte(buffer, 20) != 0;
478
+ bool directory = Marshal.ReadByte(buffer, 21) != 0;
479
+ if (directory
480
+ || deletePending != expectedDeletePending
481
+ || (requireSingleLink ? numberOfLinks != 1 : numberOfLinks > 1))
482
+ {
483
+ throw new UnauthorizedAccessException();
484
+ }
485
+ }
486
+ finally
487
+ {
488
+ Marshal.FreeHGlobal(buffer);
489
+ }
490
+ }
491
+
492
+ private static void VerifyLocalHandle(SafeFileHandle handle)
493
+ {
494
+ IntPtr buffer = Marshal.AllocHGlobal(REMOTE_PROTOCOL_INFO_BUFFER_SIZE);
495
+ try
496
+ {
497
+ for (int offset = 0; offset < REMOTE_PROTOCOL_INFO_BUFFER_SIZE; offset += 4)
498
+ Marshal.WriteInt32(buffer, offset, 0);
499
+ if (!GetFileInformationByHandleEx(
500
+ handle,
501
+ FILE_REMOTE_PROTOCOL_INFO_CLASS,
502
+ buffer,
503
+ REMOTE_PROTOCOL_INFO_BUFFER_SIZE))
504
+ {
505
+ int error = Marshal.GetLastWin32Error();
506
+ if (error == ERROR_INVALID_PARAMETER) return;
507
+ throw new Win32Exception(error);
508
+ }
509
+ int structureSize = (ushort)Marshal.ReadInt16(buffer, 2);
510
+ uint protocol = unchecked((uint)Marshal.ReadInt32(buffer, 4));
511
+ if (structureSize < 20
512
+ || structureSize > REMOTE_PROTOCOL_INFO_BUFFER_SIZE
513
+ || protocol != 0)
514
+ {
515
+ throw new UnauthorizedAccessException();
516
+ }
517
+ }
518
+ finally
519
+ {
520
+ Marshal.FreeHGlobal(buffer);
521
+ }
522
+ }
523
+
524
+ private static string NormalizeHandlePath(string path)
525
+ {
526
+ if (path.StartsWith(@"\\?\UNC\", StringComparison.OrdinalIgnoreCase))
527
+ return @"\\" + path.Substring(8);
528
+ if (path.StartsWith(@"\\?\", StringComparison.OrdinalIgnoreCase))
529
+ return path.Substring(4);
530
+ return path;
531
+ }
532
+
533
+ private static void VerifyHandlePath(SafeFileHandle handle, string expectedPath)
534
+ {
535
+ StringBuilder buffer = new StringBuilder(32768);
536
+ uint length = GetFinalPathNameByHandleW(handle, buffer, (uint)buffer.Capacity, 0);
537
+ if (length == 0 || length >= buffer.Capacity)
538
+ throw new Win32Exception(Marshal.GetLastWin32Error());
539
+ string actual = Path.GetFullPath(NormalizeHandlePath(buffer.ToString()));
540
+ string expected = Path.GetFullPath(expectedPath);
541
+ if (!String.Equals(actual.TrimEnd('\\'), expected.TrimEnd('\\'),
542
+ StringComparison.OrdinalIgnoreCase))
543
+ {
544
+ throw new UnauthorizedAccessException();
545
+ }
546
+ }
547
+
548
+ private static SafeFileHandle OpenExisting(string target, bool directory, bool write)
549
+ {
550
+ uint access = GENERIC_READ | READ_CONTROL | WRITE_DAC | DELETE;
551
+ if (write) access |= GENERIC_WRITE;
552
+ uint flags = FILE_FLAG_OPEN_REPARSE_POINT;
553
+ if (directory) flags |= FILE_FLAG_BACKUP_SEMANTICS;
554
+ SafeFileHandle handle = CreateFileExisting(
555
+ target, access, 0, IntPtr.Zero, OPEN_EXISTING, flags, IntPtr.Zero);
556
+ if (handle.IsInvalid)
557
+ {
558
+ int error = Marshal.GetLastWin32Error();
559
+ handle.Dispose();
560
+ throw new Win32Exception(error);
561
+ }
562
+ return handle;
563
+ }
564
+
565
+ private static string NativePath(string target)
566
+ {
567
+ string full = Path.GetFullPath(target);
568
+ if (full.StartsWith(@"\\", StringComparison.Ordinal))
569
+ return @"\??\UNC\" + full.Substring(2);
570
+ return @"\??\" + full;
571
+ }
572
+
573
+ private static SafeFileHandle OpenOrCreateDirectory(
574
+ string target,
575
+ string userSid)
576
+ {
577
+ string nativePath = NativePath(target);
578
+ if (Encoding.Unicode.GetByteCount(nativePath) > UInt16.MaxValue - 2)
579
+ throw new PathTooLongException();
580
+
581
+ IntPtr nameBuffer = IntPtr.Zero;
582
+ IntPtr namePointer = IntPtr.Zero;
583
+ IntPtr descriptor = IntPtr.Zero;
584
+ IntPtr rawHandle = IntPtr.Zero;
585
+ try
586
+ {
587
+ nameBuffer = Marshal.StringToHGlobalUni(nativePath);
588
+ UnicodeString name = new UnicodeString();
589
+ name.Length = (ushort)Encoding.Unicode.GetByteCount(nativePath);
590
+ name.MaximumLength = (ushort)(name.Length + 2);
591
+ name.Buffer = nameBuffer;
592
+ namePointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(UnicodeString)));
593
+ Marshal.StructureToPtr(name, namePointer, false);
594
+ descriptor = CreateExactDescriptor(userSid, true);
595
+
596
+ ObjectAttributes attributes = new ObjectAttributes();
597
+ attributes.Length = Marshal.SizeOf(typeof(ObjectAttributes));
598
+ attributes.ObjectName = namePointer;
599
+ attributes.Attributes = OBJ_CASE_INSENSITIVE;
600
+ attributes.SecurityDescriptor = descriptor;
601
+
602
+ IoStatusBlock ioStatus;
603
+ int status = NtCreateFile(
604
+ out rawHandle,
605
+ GENERIC_READ | READ_CONTROL | WRITE_DAC | DELETE | SYNCHRONIZE,
606
+ ref attributes,
607
+ out ioStatus,
608
+ IntPtr.Zero,
609
+ 0,
610
+ 0,
611
+ FILE_OPEN_IF,
612
+ FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT,
613
+ IntPtr.Zero,
614
+ 0);
615
+ if (status < 0)
616
+ throw new Win32Exception((int)RtlNtStatusToDosError(status));
617
+ return new SafeFileHandle(rawHandle, true);
618
+ }
619
+ catch
620
+ {
621
+ if (rawHandle != IntPtr.Zero && rawHandle != new IntPtr(-1))
622
+ new SafeFileHandle(rawHandle, true).Dispose();
623
+ throw;
624
+ }
625
+ finally
626
+ {
627
+ if (descriptor != IntPtr.Zero) LocalFree(descriptor);
628
+ if (namePointer != IntPtr.Zero) Marshal.FreeHGlobal(namePointer);
629
+ if (nameBuffer != IntPtr.Zero) Marshal.FreeHGlobal(nameBuffer);
630
+ }
631
+ }
632
+
633
+ private static void EnsureParentDirectories(string target, string userSid)
634
+ {
635
+ string parent = Path.GetDirectoryName(Path.GetFullPath(target));
636
+ if (String.IsNullOrEmpty(parent) || Directory.Exists(parent)) return;
637
+ EnsurePrivateDirectory(parent, userSid);
638
+ }
639
+
640
+ public static void EnsurePrivateDirectory(string target, string userSid)
641
+ {
642
+ string fullTarget = Path.GetFullPath(target);
643
+ string root = Path.GetPathRoot(fullTarget);
644
+ if (String.Equals(fullTarget.TrimEnd('\\'), root.TrimEnd('\\'),
645
+ StringComparison.OrdinalIgnoreCase))
646
+ throw new UnauthorizedAccessException();
647
+ EnsureParentDirectories(fullTarget, userSid);
648
+ using (SafeFileHandle handle = OpenOrCreateDirectory(fullTarget, userSid))
649
+ {
650
+ ValidateHandle(handle, true);
651
+ VerifyHandlePath(handle, fullTarget);
652
+ HashSet<string> expectedSids = ExpectedSids(userSid);
653
+ string owner = ValidateOwner(handle, expectedSids);
654
+ SetExactDacl(handle, userSid, true);
655
+ VerifyExactDacl(handle, userSid, true, owner);
656
+ VerifyHandlePath(handle, fullTarget);
657
+ }
658
+ }
659
+
660
+ public static void HardenExisting(string target, string userSid, bool directory)
661
+ {
662
+ using (SafeFileHandle handle = OpenExisting(target, directory, false))
663
+ {
664
+ ValidateHandle(handle, directory);
665
+ VerifyHandlePath(handle, target);
666
+ HashSet<string> expectedSids = ExpectedSids(userSid);
667
+ string owner = ValidateOwner(handle, expectedSids);
668
+ SetExactDacl(handle, userSid, directory);
669
+ VerifyExactDacl(handle, userSid, directory, owner);
670
+ ValidateHandle(handle, directory);
671
+ VerifyHandlePath(handle, target);
672
+ }
673
+ }
674
+
675
+ private static bool IsReservedTreeName(string name)
676
+ {
677
+ string stem = name;
678
+ int dot = stem.IndexOf('.');
679
+ if (dot >= 0) stem = stem.Substring(0, dot);
680
+ stem = stem.ToUpperInvariant();
681
+ if (stem == "CON" || stem == "PRN" || stem == "AUX" || stem == "NUL"
682
+ || stem == "CONIN$" || stem == "CONOUT$") return true;
683
+ if (stem.Length == 4 && (stem.StartsWith("COM", StringComparison.Ordinal)
684
+ || stem.StartsWith("LPT", StringComparison.Ordinal)))
685
+ {
686
+ char suffix = stem[3];
687
+ return suffix >= '1' && suffix <= '9';
688
+ }
689
+ return false;
690
+ }
691
+
692
+ private static void ValidateTreeEntryName(string name)
693
+ {
694
+ if (String.IsNullOrEmpty(name) || name == "." || name == ".."
695
+ || name.EndsWith(".", StringComparison.Ordinal)
696
+ || name.EndsWith(" ", StringComparison.Ordinal)
697
+ || IsReservedTreeName(name))
698
+ {
699
+ throw new UnauthorizedAccessException();
700
+ }
701
+ foreach (char character in name)
702
+ {
703
+ if (character < 32 || character == ':' || character == '\\' || character == '/'
704
+ || character == '<' || character == '>' || character == '"'
705
+ || character == '|' || character == '?' || character == '*')
706
+ {
707
+ throw new UnauthorizedAccessException();
708
+ }
709
+ }
710
+ }
711
+
712
+ private static string ValidateTreeRootPath(string target)
713
+ {
714
+ if (String.IsNullOrEmpty(target) || target.Length > MAX_TREE_PATH_LENGTH
715
+ || target.Length < 3 || !Char.IsLetter(target[0]) || target[1] != ':'
716
+ || target[2] != '\\' || target.IndexOf('/') >= 0
717
+ || target.IndexOf(':', 2) >= 0)
718
+ {
719
+ throw new UnauthorizedAccessException();
720
+ }
721
+ string fullTarget = Path.GetFullPath(target);
722
+ if (!String.Equals(fullTarget, target, StringComparison.OrdinalIgnoreCase))
723
+ throw new UnauthorizedAccessException();
724
+ string root = Path.GetPathRoot(fullTarget);
725
+ if (String.Equals(fullTarget.TrimEnd('\\'), root.TrimEnd('\\'),
726
+ StringComparison.OrdinalIgnoreCase))
727
+ {
728
+ throw new UnauthorizedAccessException();
729
+ }
730
+ string relative = fullTarget.Substring(root.Length);
731
+ foreach (string segment in relative.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries))
732
+ ValidateTreeEntryName(segment);
733
+ return fullTarget;
734
+ }
735
+
736
+ private static string TreeChildPath(string root, string parent, string name)
737
+ {
738
+ ValidateTreeEntryName(name);
739
+ string child = Path.GetFullPath(Path.Combine(parent, name));
740
+ string prefix = root.TrimEnd('\\') + "\\";
741
+ if (child.Length > MAX_TREE_PATH_LENGTH
742
+ || !child.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
743
+ || !String.Equals(Path.GetFileName(child), name, StringComparison.Ordinal))
744
+ {
745
+ throw new UnauthorizedAccessException();
746
+ }
747
+ return child;
748
+ }
749
+
750
+ private static List<TreeEntry> ReadDirectoryEntries(SafeFileHandle handle)
751
+ {
752
+ List<TreeEntry> entries = new List<TreeEntry>();
753
+ HashSet<string> names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
754
+ IntPtr buffer = Marshal.AllocHGlobal(DIRECTORY_INFORMATION_BUFFER_SIZE);
755
+ try
756
+ {
757
+ bool restartScan = true;
758
+ while (true)
759
+ {
760
+ IoStatusBlock ioStatus;
761
+ int status = NtQueryDirectoryFile(
762
+ handle,
763
+ IntPtr.Zero,
764
+ IntPtr.Zero,
765
+ IntPtr.Zero,
766
+ out ioStatus,
767
+ buffer,
768
+ DIRECTORY_INFORMATION_BUFFER_SIZE,
769
+ FILE_ID_BOTH_DIRECTORY_INFORMATION_CLASS,
770
+ true,
771
+ IntPtr.Zero,
772
+ restartScan);
773
+ restartScan = false;
774
+ if (status == STATUS_NO_MORE_FILES) break;
775
+ if (status != 0)
776
+ throw new Win32Exception((int)RtlNtStatusToDosError(status));
777
+
778
+ ulong bytesReturned = ioStatus.Information.ToUInt64();
779
+ if (bytesReturned < DIRECTORY_INFORMATION_NAME_OFFSET
780
+ || bytesReturned > DIRECTORY_INFORMATION_BUFFER_SIZE)
781
+ {
782
+ throw new InvalidDataException();
783
+ }
784
+ int nameLength = Marshal.ReadInt32(buffer, 60);
785
+ if (nameLength < 0 || (nameLength & 1) != 0
786
+ || (ulong)(DIRECTORY_INFORMATION_NAME_OFFSET + nameLength) > bytesReturned)
787
+ {
788
+ throw new InvalidDataException();
789
+ }
790
+ string name = Marshal.PtrToStringUni(
791
+ IntPtr.Add(buffer, DIRECTORY_INFORMATION_NAME_OFFSET),
792
+ nameLength / 2);
793
+ if (name == "." || name == "..") continue;
794
+ ValidateTreeEntryName(name);
795
+ if (!names.Add(name)) throw new UnauthorizedAccessException();
796
+ TreeEntry entry = new TreeEntry();
797
+ entry.Name = name;
798
+ entry.Attributes = unchecked((uint)Marshal.ReadInt32(buffer, 56));
799
+ entry.FileId = unchecked((ulong)Marshal.ReadInt64(
800
+ buffer,
801
+ DIRECTORY_INFORMATION_FILE_ID_OFFSET));
802
+ if (entry.FileId == 0) throw new UnauthorizedAccessException();
803
+ entries.Add(entry);
804
+ if (entries.Count > MAX_TREE_ENTRIES)
805
+ throw new UnauthorizedAccessException();
806
+ }
807
+ }
808
+ finally
809
+ {
810
+ Marshal.FreeHGlobal(buffer);
811
+ }
812
+ entries.Sort(delegate(TreeEntry left, TreeEntry right)
813
+ {
814
+ int comparison = StringComparer.OrdinalIgnoreCase.Compare(left.Name, right.Name);
815
+ return comparison != 0 ? comparison : StringComparer.Ordinal.Compare(left.Name, right.Name);
816
+ });
817
+ return entries;
818
+ }
819
+
820
+ private static void VerifyUnchangedDirectoryEntries(
821
+ List<TreeEntry> expected,
822
+ List<TreeEntry> actual)
823
+ {
824
+ if (expected.Count != actual.Count) throw new UnauthorizedAccessException();
825
+ for (int index = 0; index < expected.Count; index++)
826
+ {
827
+ if (!String.Equals(expected[index].Name, actual[index].Name, StringComparison.Ordinal)
828
+ || expected[index].Attributes != actual[index].Attributes
829
+ || expected[index].FileId != actual[index].FileId)
830
+ {
831
+ throw new UnauthorizedAccessException();
832
+ }
833
+ }
834
+ }
835
+
836
+ private static string HardenTreeEntry(
837
+ SafeFileHandle handle,
838
+ string target,
839
+ string userSid,
840
+ bool directory,
841
+ HashSet<string> expectedSids)
842
+ {
843
+ ValidateHandle(handle, directory);
844
+ VerifyHandlePath(handle, target);
845
+ string owner = ValidateOwner(handle, expectedSids);
846
+ SetExactDacl(handle, userSid, directory);
847
+ VerifyExactDacl(handle, userSid, directory, owner);
848
+ ValidateHandle(handle, directory);
849
+ VerifyHandlePath(handle, target);
850
+ return owner;
851
+ }
852
+
853
+ private static void HardenTreeDirectory(
854
+ SafeFileHandle directoryHandle,
855
+ string root,
856
+ string directoryPath,
857
+ string userSid,
858
+ HashSet<string> expectedSids,
859
+ int depth,
860
+ ref int entryCount,
861
+ List<HeldTreeHandle> heldHandles)
862
+ {
863
+ if (depth > MAX_TREE_DEPTH) throw new UnauthorizedAccessException();
864
+ List<TreeEntry> snapshot = ReadDirectoryEntries(directoryHandle);
865
+ foreach (TreeEntry entry in snapshot)
866
+ {
867
+ entryCount++;
868
+ if (entryCount > MAX_TREE_ENTRIES) throw new UnauthorizedAccessException();
869
+ if ((entry.Attributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DEVICE)) != 0)
870
+ throw new UnauthorizedAccessException();
871
+ bool directory = (entry.Attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
872
+ string childPath = TreeChildPath(root, directoryPath, entry.Name);
873
+ SafeFileHandle childHandle = OpenExisting(childPath, directory, false);
874
+ try
875
+ {
876
+ HeldTreeHandle heldHandle = new HeldTreeHandle();
877
+ heldHandle.Handle = childHandle;
878
+ heldHandle.Path = childPath;
879
+ heldHandle.Directory = directory;
880
+ heldHandles.Add(heldHandle);
881
+ }
882
+ catch
883
+ {
884
+ childHandle.Dispose();
885
+ throw;
886
+ }
887
+ VerifyTreeHandleIdentity(childHandle, entry.FileId);
888
+ heldHandles[heldHandles.Count - 1].Owner = HardenTreeEntry(
889
+ childHandle,
890
+ childPath,
891
+ userSid,
892
+ directory,
893
+ expectedSids);
894
+ if (directory)
895
+ {
896
+ HardenTreeDirectory(
897
+ childHandle,
898
+ root,
899
+ childPath,
900
+ userSid,
901
+ expectedSids,
902
+ depth + 1,
903
+ ref entryCount,
904
+ heldHandles);
905
+ }
906
+ }
907
+ VerifyUnchangedDirectoryEntries(snapshot, ReadDirectoryEntries(directoryHandle));
908
+ ValidateHandle(directoryHandle, true);
909
+ VerifyHandlePath(directoryHandle, directoryPath);
910
+ }
911
+
912
+ public static void HardenTree(string target, string userSid)
913
+ {
914
+ string root = ValidateTreeRootPath(target);
915
+ HashSet<string> expectedSids = ExpectedSids(userSid);
916
+ List<HeldTreeHandle> heldHandles = new List<HeldTreeHandle>();
917
+ try
918
+ {
919
+ SafeFileHandle rootHandle = OpenExisting(root, true, false);
920
+ HeldTreeHandle heldRoot = new HeldTreeHandle();
921
+ heldRoot.Handle = rootHandle;
922
+ heldRoot.Path = root;
923
+ heldRoot.Directory = true;
924
+ heldHandles.Add(heldRoot);
925
+ heldRoot.Owner = HardenTreeEntry(rootHandle, root, userSid, true, expectedSids);
926
+ int entryCount = 1;
927
+ HardenTreeDirectory(
928
+ rootHandle,
929
+ root,
930
+ root,
931
+ userSid,
932
+ expectedSids,
933
+ 0,
934
+ ref entryCount,
935
+ heldHandles);
936
+ foreach (HeldTreeHandle heldHandle in heldHandles)
937
+ {
938
+ ValidateHandle(heldHandle.Handle, heldHandle.Directory);
939
+ VerifyExactDacl(
940
+ heldHandle.Handle,
941
+ userSid,
942
+ heldHandle.Directory,
943
+ heldHandle.Owner);
944
+ VerifyHandlePath(heldHandle.Handle, heldHandle.Path);
945
+ }
946
+ }
947
+ finally
948
+ {
949
+ for (int index = heldHandles.Count - 1; index >= 0; index--)
950
+ heldHandles[index].Handle.Dispose();
951
+ }
952
+ }
953
+
954
+ private static bool SetDeletePending(SafeFileHandle handle, bool deleteFile)
955
+ {
956
+ FileDispositionInfo information = new FileDispositionInfo();
957
+ information.DeleteFile = deleteFile;
958
+ return SetFileInformationByHandle(
959
+ handle,
960
+ FILE_DISPOSITION_INFO_CLASS,
961
+ ref information,
962
+ (uint)Marshal.SizeOf(typeof(FileDispositionInfo)));
963
+ }
964
+
965
+ private static SafeFileHandle OpenForWrite(string target, string userSid)
966
+ {
967
+ IntPtr descriptor = CreateExactDescriptor(userSid, false);
968
+ try
969
+ {
970
+ SecurityAttributes attributes = new SecurityAttributes();
971
+ attributes.Length = Marshal.SizeOf(typeof(SecurityAttributes));
972
+ attributes.SecurityDescriptor = descriptor;
973
+ attributes.InheritHandle = false;
974
+ SafeFileHandle handle = CreateFileNew(
975
+ target,
976
+ GENERIC_READ | GENERIC_WRITE | READ_CONTROL | WRITE_DAC | DELETE,
977
+ 0,
978
+ ref attributes,
979
+ CREATE_NEW,
980
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT,
981
+ IntPtr.Zero);
982
+ if (!handle.IsInvalid) return handle;
983
+
984
+ int error = Marshal.GetLastWin32Error();
985
+ handle.Dispose();
986
+ throw new Win32Exception(error);
987
+ }
988
+ finally
989
+ {
990
+ LocalFree(descriptor);
991
+ }
992
+ }
11
993
 
12
- if (platform !== 'win32') {
13
- fsImpl.chmodSync(targetPath, mode);
994
+ private static int FrameLength(byte[] frame)
995
+ {
996
+ if (frame == null || frame.Length < 8) throw new InvalidDataException();
997
+ ulong length = BitConverter.ToUInt64(frame, 0);
998
+ if (length > Int32.MaxValue || length != (ulong)(frame.Length - 8))
999
+ throw new InvalidDataException();
1000
+ return (int)length;
1001
+ }
1002
+
1003
+ public static void WritePrivateFile(string target, string userSid, byte[] frame)
1004
+ {
1005
+ int contentLength = FrameLength(frame);
1006
+ SafeFileHandle handle = OpenForWrite(target, userSid);
1007
+ FileStream stream = null;
1008
+ bool deletePending = false;
1009
+ try
1010
+ {
1011
+ ValidateHandle(handle, false);
1012
+ VerifyHandlePath(handle, target);
1013
+ if (!SetDeletePending(handle, true))
1014
+ throw new Win32Exception(Marshal.GetLastWin32Error());
1015
+ deletePending = true;
1016
+ HashSet<string> expectedSids = ExpectedSids(userSid);
1017
+ string owner = ValidateOwner(handle, expectedSids);
1018
+ SetExactDacl(handle, userSid, false);
1019
+ VerifyExactDacl(handle, userSid, false, owner);
1020
+
1021
+ stream = new FileStream(handle, FileAccess.ReadWrite);
1022
+ stream.SetLength(0);
1023
+ stream.Write(frame, 8, contentLength);
1024
+ stream.Flush(true);
1025
+ VerifyFreshFileState(handle, true, false);
1026
+ VerifyExactDacl(handle, userSid, false, owner);
1027
+ VerifyHandlePath(handle, target);
1028
+ if (!SetDeletePending(handle, false))
1029
+ throw new Win32Exception(Marshal.GetLastWin32Error());
1030
+ deletePending = false;
1031
+ ValidateHandle(handle, false);
1032
+ VerifyHandlePath(handle, target);
1033
+ }
1034
+ catch
1035
+ {
1036
+ try
1037
+ {
1038
+ if (stream != null)
1039
+ {
1040
+ stream.SetLength(0);
1041
+ stream.Flush(true);
1042
+ }
1043
+ }
1044
+ catch {}
1045
+ if (!deletePending)
1046
+ {
1047
+ try { deletePending = SetDeletePending(handle, true); }
1048
+ catch { deletePending = false; }
1049
+ }
1050
+ throw;
1051
+ }
1052
+ finally
1053
+ {
1054
+ Array.Clear(frame, 0, frame.Length);
1055
+ if (stream != null) stream.Dispose();
1056
+ else handle.Dispose();
1057
+ }
1058
+ }
1059
+ }`;
1060
+
1061
+ const WINDOWS_ACL_CSHARP_GZIP = gzipSync(
1062
+ Buffer.from(WINDOWS_ACL_CSHARP),
1063
+ { level: 9 },
1064
+ ).toString('base64');
1065
+
1066
+ const WINDOWS_ACL_SCRIPT = [
1067
+ "$ErrorActionPreference = 'Stop'",
1068
+ "$ProgressPreference = 'SilentlyContinue'",
1069
+ 'try {',
1070
+ 'Set-StrictMode -Version 3',
1071
+ "$target=[Environment]::GetEnvironmentVariable('BLUN_PRIVATE_ACL_TARGET','Process')",
1072
+ "$kind=[Environment]::GetEnvironmentVariable('BLUN_PRIVATE_ACL_KIND','Process')",
1073
+ "$sid=[Environment]::GetEnvironmentVariable('BLUN_PRIVATE_ACL_USER_SID','Process')",
1074
+ "if ([string]::IsNullOrWhiteSpace($target) -or -not [IO.Path]::IsPathRooted($target)) { throw 'Invalid target.' }",
1075
+ "if ($kind -ne 'directory' -and $kind -ne 'file' -and $kind -ne 'file-write' -and $kind -ne 'tree') { throw 'Invalid kind.' }",
1076
+ `$bytes=[Convert]::FromBase64String('${WINDOWS_ACL_CSHARP_GZIP}')`,
1077
+ '$zipin=[IO.MemoryStream]::new($bytes)',
1078
+ '$gzip=[IO.Compression.GZipStream]::new($zipin,[IO.Compression.CompressionMode]::Decompress)',
1079
+ '$reader=[IO.StreamReader]::new($gzip,[Text.Encoding]::UTF8)',
1080
+ 'try {',
1081
+ '$code=$reader.ReadToEnd()',
1082
+ '} finally {',
1083
+ '$reader.Dispose()',
1084
+ '$gzip.Dispose()',
1085
+ '$zipin.Dispose()',
1086
+ '[Array]::Clear($bytes,0,$bytes.Length)',
1087
+ '}',
1088
+ '$null=Add-Type -TypeDefinition $code -Language CSharp',
1089
+ "if ($kind -eq 'directory') {",
1090
+ '[BlunPrivateAclNative]::EnsurePrivateDirectory($target,$sid)',
1091
+ "} elseif ($kind -eq 'file') {",
1092
+ '[BlunPrivateAclNative]::HardenExisting($target,$sid,$false)',
1093
+ "} elseif ($kind -eq 'tree') {",
1094
+ '[BlunPrivateAclNative]::HardenTree($target,$sid)',
1095
+ '} else {',
1096
+ '$stdin=[Console]::OpenStandardInput()',
1097
+ '$mem=[IO.MemoryStream]::new()',
1098
+ '$frame=$null',
1099
+ 'try {',
1100
+ '$stdin.CopyTo($mem)',
1101
+ '$frame=$mem.ToArray()',
1102
+ '[BlunPrivateAclNative]::WritePrivateFile($target,$sid,$frame)',
1103
+ '} finally {',
1104
+ 'if ($null -ne $frame) { [Array]::Clear($frame,0,$frame.Length) }',
1105
+ '$mem.Dispose()',
1106
+ '}',
1107
+ '}',
1108
+ "[Console]::Out.Write('BLUN_PRIVATE_ACL_OK_V1')",
1109
+ 'exit 0',
1110
+ '} catch {',
1111
+ "[Console]::Error.Write('BLUN_PRIVATE_ACL_ERROR_V1')",
1112
+ 'exit 1',
1113
+ '}',
1114
+ ].join('\n');
1115
+
1116
+ const WINDOWS_ACL_ENCODED_COMMAND = Buffer.from(WINDOWS_ACL_SCRIPT, 'utf16le').toString('base64');
1117
+
1118
+ function windowsSystemDirectory(options) {
1119
+ const env = options.env || process.env;
1120
+ const systemRoot = env.SystemRoot || env.SYSTEMROOT || env.WINDIR || env.windir;
1121
+ const directory = options.systemDirectory
1122
+ || (systemRoot ? path.win32.join(systemRoot, 'System32') : '');
1123
+ if (!path.win32.isAbsolute(directory)
1124
+ || path.win32.basename(directory).toLowerCase() !== 'system32') {
1125
+ throw new Error('Cannot resolve the absolute Windows system directory for private ACLs.');
1126
+ }
1127
+ return path.win32.normalize(directory);
1128
+ }
1129
+
1130
+ function windowsHostEnvironment(systemDirectory) {
1131
+ const systemRoot = path.win32.dirname(systemDirectory);
1132
+ return { SystemRoot: systemRoot, WINDIR: systemRoot };
1133
+ }
1134
+
1135
+ function runWindowsCommand(executable, args, options, env, input) {
1136
+ const execFileSyncImpl = options.execFileSyncImpl || execFileSync;
1137
+ const hasInput = input !== undefined;
1138
+ return execFileSyncImpl(executable, [...args], {
1139
+ encoding: 'utf8',
1140
+ env,
1141
+ input,
1142
+ maxBuffer: 64 * 1024,
1143
+ shell: false,
1144
+ stdio: [hasInput ? 'pipe' : 'ignore', 'pipe', 'pipe'],
1145
+ timeout: 30_000,
1146
+ windowsHide: true,
1147
+ });
1148
+ }
1149
+
1150
+ function framedPrivateContents(contents) {
1151
+ const payload = Buffer.isBuffer(contents) ? Buffer.from(contents) : Buffer.from(String(contents));
1152
+ let frame;
1153
+ try {
1154
+ frame = Buffer.allocUnsafe(8 + payload.length);
1155
+ frame.writeBigUInt64LE(BigInt(payload.length), 0);
1156
+ payload.copy(frame, 8);
1157
+ return frame;
1158
+ } catch (error) {
1159
+ frame?.fill(0);
1160
+ throw error;
1161
+ } finally {
1162
+ payload.fill(0);
1163
+ }
1164
+ }
1165
+
1166
+ function resolveWindowsPrivateTarget(targetPath) {
1167
+ if (process.platform !== 'win32') return path.resolve(targetPath);
1168
+ try {
1169
+ if (typeof targetPath !== 'string'
1170
+ || !WINDOWS_LOCAL_DOS_PATH_PATTERN.test(targetPath)
1171
+ || !path.win32.isAbsolute(targetPath)
1172
+ || targetPath.includes('/')
1173
+ || targetPath.slice(2).includes(':')) {
1174
+ throw new Error('Invalid Windows private path.');
1175
+ }
1176
+ const normalizedTarget = path.win32.normalize(targetPath);
1177
+ if (normalizedTarget.toLowerCase() !== targetPath.toLowerCase()) {
1178
+ throw new Error('Invalid Windows private path.');
1179
+ }
1180
+ const segments = normalizedTarget.slice(3).split('\\').filter(Boolean);
1181
+ if (segments.some((segment) => segment.endsWith('.')
1182
+ || segment.endsWith(' ')
1183
+ || /[\u0000-\u001F<>"|?*]/u.test(segment)
1184
+ || WINDOWS_RESERVED_PATH_SEGMENT.test(segment))) {
1185
+ throw new Error('Invalid Windows private path.');
1186
+ }
1187
+ const absoluteTarget = path.resolve(normalizedTarget);
1188
+ if (!WINDOWS_LOCAL_DOS_PATH_PATTERN.test(absoluteTarget)
1189
+ || absoluteTarget.slice(2).includes(':')) {
1190
+ throw new Error('Invalid Windows private path.');
1191
+ }
1192
+ return absoluteTarget;
1193
+ } catch {
1194
+ throw new Error('Windows private ACL hardening or verification failed.');
1195
+ }
1196
+ }
1197
+
1198
+ function windowsAclController(options) {
1199
+ const systemDirectory = windowsSystemDirectory(options);
1200
+ const hostEnvironment = windowsHostEnvironment(systemDirectory);
1201
+ const whoami = path.win32.join(systemDirectory, 'whoami.exe');
1202
+ const powershell = path.win32.join(
1203
+ systemDirectory,
1204
+ 'WindowsPowerShell',
1205
+ 'v1.0',
1206
+ 'powershell.exe',
1207
+ );
1208
+ let userSid;
1209
+
1210
+ const currentUserSid = () => {
1211
+ if (userSid !== undefined) return userSid;
1212
+ let output;
1213
+ try {
1214
+ output = runWindowsCommand(
1215
+ whoami,
1216
+ ['/user', '/fo', 'csv', '/nh'],
1217
+ options,
1218
+ hostEnvironment,
1219
+ );
1220
+ } catch {
1221
+ throw new Error('Cannot determine the current Windows user SID for private launcher paths.');
1222
+ }
1223
+ const sids = [...new Set(String(output).match(WINDOWS_SID_PATTERN) || [])];
1224
+ if (sids.length !== 1) {
1225
+ throw new Error('Cannot determine the current Windows user SID for private launcher paths.');
1226
+ }
1227
+ userSid = sids[0].toUpperCase();
1228
+ return userSid;
1229
+ };
1230
+
1231
+ const invoke = (targetPath, kind, input) => {
1232
+ try {
1233
+ const absoluteTarget = resolveWindowsPrivateTarget(targetPath);
1234
+ const root = path.parse(absoluteTarget).root;
1235
+ if (!path.isAbsolute(absoluteTarget)
1236
+ || root.toLowerCase() === absoluteTarget.toLowerCase()
1237
+ || !['directory', 'file', 'file-write', 'tree'].includes(kind)) {
1238
+ throw new Error('Windows private ACL hardening or verification failed.');
1239
+ }
1240
+ const env = {
1241
+ ...hostEnvironment,
1242
+ BLUN_PRIVATE_ACL_KIND: kind,
1243
+ BLUN_PRIVATE_ACL_TARGET: absoluteTarget,
1244
+ BLUN_PRIVATE_ACL_USER_SID: currentUserSid(),
1245
+ };
1246
+ let output;
1247
+ try {
1248
+ output = runWindowsCommand(
1249
+ powershell,
1250
+ ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand',
1251
+ WINDOWS_ACL_ENCODED_COMMAND],
1252
+ options,
1253
+ env,
1254
+ input,
1255
+ );
1256
+ } catch {
1257
+ throw new Error('Windows private ACL hardening or verification failed.');
1258
+ }
1259
+ if (output !== WINDOWS_ACL_OK_SENTINEL) {
1260
+ throw new Error('Windows private ACL hardening or verification failed.');
1261
+ }
1262
+ } finally {
1263
+ input?.fill(0);
1264
+ }
1265
+ };
1266
+
1267
+ return {
1268
+ hardenAndVerify(targetPath, kind) {
1269
+ invoke(targetPath, kind);
1270
+ },
1271
+ hardenTree(targetPath) {
1272
+ invoke(targetPath, 'tree');
1273
+ },
1274
+ writeAndVerify(targetPath, contents) {
1275
+ invoke(targetPath, 'file-write', framedPrivateContents(contents));
1276
+ },
1277
+ };
1278
+ }
1279
+
1280
+ function sameFileIdentity(left, right) {
1281
+ return left !== undefined
1282
+ && right !== undefined
1283
+ && left.dev !== undefined
1284
+ && left.ino !== undefined
1285
+ && left.dev === right.dev
1286
+ && left.ino === right.ino;
1287
+ }
1288
+
1289
+ function validatePosixEntry(stat, directory) {
1290
+ if (stat.isSymbolicLink()
1291
+ || stat.isDirectory() !== directory
1292
+ || stat.isFile() === directory
1293
+ || (!directory && stat.nlink !== 1)) {
1294
+ throw new Error('Unsafe private path entry.');
1295
+ }
1296
+ if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
1297
+ throw new Error('Unsafe private path owner.');
1298
+ }
1299
+ }
1300
+
1301
+ function verifyPosixHandlePath(targetPath, descriptor, identity, directory, fsImpl) {
1302
+ const byHandle = fsImpl.fstatSync(descriptor);
1303
+ const byPath = fsImpl.lstatSync(targetPath);
1304
+ validatePosixEntry(byHandle, directory);
1305
+ validatePosixEntry(byPath, directory);
1306
+ if (!sameFileIdentity(identity, byHandle)
1307
+ || !sameFileIdentity(byHandle, byPath)) {
1308
+ throw new Error('Private path identity changed.');
1309
+ }
1310
+ return byHandle;
1311
+ }
1312
+
1313
+ function openVerifiedPosixEntry(targetPath, fsImpl, requireDirectory = false) {
1314
+ const before = fsImpl.lstatSync(targetPath);
1315
+ const directory = before.isDirectory();
1316
+ validatePosixEntry(before, directory);
1317
+ if (requireDirectory && !directory) throw new Error('Private path is not a directory.');
1318
+
1319
+ const noFollow = fs.constants.O_NOFOLLOW || 0;
1320
+ const directoryOnly = directory ? (fs.constants.O_DIRECTORY || 0) : 0;
1321
+ let descriptor;
1322
+ try {
1323
+ descriptor = fsImpl.openSync(
1324
+ targetPath,
1325
+ fs.constants.O_RDONLY | noFollow | directoryOnly,
1326
+ );
1327
+ verifyPosixHandlePath(targetPath, descriptor, before, directory, fsImpl);
1328
+ return { descriptor, directory, identity: before };
1329
+ } catch (error) {
1330
+ if (descriptor !== undefined) {
1331
+ try {
1332
+ fsImpl.closeSync(descriptor);
1333
+ } catch {
1334
+ // Preserve the verification failure.
1335
+ }
1336
+ }
1337
+ throw error;
1338
+ }
1339
+ }
1340
+
1341
+ function applyPosixModeByHandle(
1342
+ targetPath,
1343
+ descriptor,
1344
+ identity,
1345
+ directory,
1346
+ mode,
1347
+ fsImpl,
1348
+ options,
1349
+ ) {
1350
+ verifyPosixHandlePath(targetPath, descriptor, identity, directory, fsImpl);
1351
+ const emulatingPosixOnWindows = process.platform === 'win32'
1352
+ && options.platform !== 'win32';
1353
+ if (!emulatingPosixOnWindows) fsImpl.fchmodSync(descriptor, mode);
1354
+ const hardened = verifyPosixHandlePath(
1355
+ targetPath,
1356
+ descriptor,
1357
+ identity,
1358
+ directory,
1359
+ fsImpl,
1360
+ );
1361
+ if (!emulatingPosixOnWindows && (hardened.mode & 0o777) !== mode) {
1362
+ throw new Error('Private path mode verification failed.');
14
1363
  }
15
1364
  }
16
1365
 
17
1366
  function ensurePrivateDirectory(targetPath, options = {}) {
18
1367
  const fsImpl = options.fsImpl || fs;
19
- fsImpl.mkdirSync(targetPath, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
20
- applyPosixMode(targetPath, PRIVATE_DIRECTORY_MODE, options);
1368
+ const platform = options.platform || process.platform;
1369
+ if (platform === 'win32') {
1370
+ windowsAclController(options).hardenAndVerify(targetPath, 'directory');
1371
+ return;
1372
+ }
1373
+
1374
+ const absoluteTarget = path.resolve(targetPath);
1375
+ let opened;
1376
+ try {
1377
+ fsImpl.mkdirSync(absoluteTarget, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
1378
+ opened = openVerifiedPosixEntry(absoluteTarget, fsImpl, true);
1379
+ applyPosixModeByHandle(
1380
+ absoluteTarget,
1381
+ opened.descriptor,
1382
+ opened.identity,
1383
+ true,
1384
+ PRIVATE_DIRECTORY_MODE,
1385
+ fsImpl,
1386
+ options,
1387
+ );
1388
+ } catch {
1389
+ throw new Error('Private directory hardening or verification failed.');
1390
+ } finally {
1391
+ if (opened !== undefined) fsImpl.closeSync(opened.descriptor);
1392
+ }
1393
+ }
1394
+
1395
+ function scrubCreatedPosixFile(targetPath, descriptor, identity, fsImpl) {
1396
+ try {
1397
+ fsImpl.ftruncateSync(descriptor, 0);
1398
+ fsImpl.fsyncSync(descriptor);
1399
+ } catch {
1400
+ // Continue with identity-bound removal even if scrubbing was incomplete.
1401
+ }
1402
+ try {
1403
+ const byHandle = fsImpl.fstatSync(descriptor);
1404
+ const byPath = fsImpl.lstatSync(targetPath);
1405
+ if (sameFileIdentity(identity, byHandle)
1406
+ && sameFileIdentity(byHandle, byPath)
1407
+ && !byPath.isSymbolicLink()
1408
+ && byPath.isFile()) {
1409
+ fsImpl.unlinkSync(targetPath);
1410
+ }
1411
+ } catch {
1412
+ // Never delete a pathname whose identity cannot be proven.
1413
+ }
21
1414
  }
22
1415
 
23
1416
  function writePrivateFile(targetPath, contents, options = {}) {
24
1417
  const fsImpl = options.fsImpl || fs;
25
- fsImpl.writeFileSync(targetPath, contents, { mode: PRIVATE_FILE_MODE });
26
- applyPosixMode(targetPath, PRIVATE_FILE_MODE, options);
1418
+ const platform = options.platform || process.platform;
1419
+ if (platform === 'win32') {
1420
+ windowsAclController(options).writeAndVerify(targetPath, contents);
1421
+ return;
1422
+ }
1423
+
1424
+ const absoluteTarget = path.resolve(targetPath);
1425
+ const payload = Buffer.isBuffer(contents) ? Buffer.from(contents) : Buffer.from(String(contents));
1426
+ let descriptor;
1427
+ let identity;
1428
+ let completed = false;
1429
+ try {
1430
+ const noFollow = fs.constants.O_NOFOLLOW || 0;
1431
+ descriptor = fsImpl.openSync(
1432
+ absoluteTarget,
1433
+ fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow,
1434
+ PRIVATE_FILE_MODE,
1435
+ );
1436
+ identity = fsImpl.fstatSync(descriptor);
1437
+ validatePosixEntry(identity, false);
1438
+ if (identity.size > 0) throw new Error('New private file is not empty.');
1439
+ verifyPosixHandlePath(absoluteTarget, descriptor, identity, false, fsImpl);
1440
+ applyPosixModeByHandle(
1441
+ absoluteTarget,
1442
+ descriptor,
1443
+ identity,
1444
+ false,
1445
+ PRIVATE_FILE_MODE,
1446
+ fsImpl,
1447
+ options,
1448
+ );
1449
+ fsImpl.writeFileSync(descriptor, payload);
1450
+ fsImpl.fsyncSync(descriptor);
1451
+ verifyPosixHandlePath(absoluteTarget, descriptor, identity, false, fsImpl);
1452
+ completed = true;
1453
+ } catch {
1454
+ throw new Error('Private file hardening or verification failed.');
1455
+ } finally {
1456
+ if (descriptor !== undefined) {
1457
+ if (!completed && identity !== undefined) {
1458
+ scrubCreatedPosixFile(absoluteTarget, descriptor, identity, fsImpl);
1459
+ }
1460
+ try {
1461
+ fsImpl.closeSync(descriptor);
1462
+ } catch {
1463
+ completed = false;
1464
+ }
1465
+ }
1466
+ payload.fill(0);
1467
+ }
1468
+ if (!completed) throw new Error('Private file hardening or verification failed.');
27
1469
  }
28
1470
 
29
1471
  function securePrivateFile(targetPath, options = {}) {
30
- applyPosixMode(targetPath, PRIVATE_FILE_MODE, options);
1472
+ const fsImpl = options.fsImpl || fs;
1473
+ const platform = options.platform || process.platform;
1474
+ if (platform === 'win32') {
1475
+ windowsAclController(options).hardenAndVerify(targetPath, 'file');
1476
+ return;
1477
+ }
1478
+
1479
+ const absoluteTarget = path.resolve(targetPath);
1480
+ let opened;
1481
+ try {
1482
+ opened = openVerifiedPosixEntry(absoluteTarget, fsImpl);
1483
+ if (opened.directory) throw new Error('Private path is not a file.');
1484
+ applyPosixModeByHandle(
1485
+ absoluteTarget,
1486
+ opened.descriptor,
1487
+ opened.identity,
1488
+ false,
1489
+ PRIVATE_FILE_MODE,
1490
+ fsImpl,
1491
+ options,
1492
+ );
1493
+ } catch {
1494
+ throw new Error('Private file hardening or verification failed.');
1495
+ } finally {
1496
+ if (opened !== undefined) fsImpl.closeSync(opened.descriptor);
1497
+ }
1498
+ }
1499
+
1500
+ function securePosixPrivateTree(targetPath, fsImpl, options) {
1501
+ const root = path.resolve(targetPath);
1502
+ let entryCount = 0;
1503
+
1504
+ const visit = (currentPath, depth, rootEntry, parent) => {
1505
+ if (depth > PRIVATE_TREE_MAX_DEPTH
1506
+ || currentPath.length > PRIVATE_TREE_MAX_PATH_LENGTH
1507
+ || ++entryCount > PRIVATE_TREE_MAX_ENTRIES) {
1508
+ throw new Error('Private tree hardening or verification failed.');
1509
+ }
1510
+ const opened = openVerifiedPosixEntry(currentPath, fsImpl, rootEntry);
1511
+ try {
1512
+ if (parent !== undefined) {
1513
+ verifyPosixHandlePath(
1514
+ parent.path,
1515
+ parent.descriptor,
1516
+ parent.identity,
1517
+ true,
1518
+ fsImpl,
1519
+ );
1520
+ }
1521
+ applyPosixModeByHandle(
1522
+ currentPath,
1523
+ opened.descriptor,
1524
+ opened.identity,
1525
+ opened.directory,
1526
+ opened.directory ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE,
1527
+ fsImpl,
1528
+ options,
1529
+ );
1530
+
1531
+ if (opened.directory) {
1532
+ verifyPosixHandlePath(
1533
+ currentPath,
1534
+ opened.descriptor,
1535
+ opened.identity,
1536
+ true,
1537
+ fsImpl,
1538
+ );
1539
+ const names = fsImpl.readdirSync(currentPath).toSorted();
1540
+ verifyPosixHandlePath(
1541
+ currentPath,
1542
+ opened.descriptor,
1543
+ opened.identity,
1544
+ true,
1545
+ fsImpl,
1546
+ );
1547
+ for (const name of names) {
1548
+ if (name === '.' || name === '..' || name.includes('/') || name.includes('\\')) {
1549
+ throw new Error('Private tree hardening or verification failed.');
1550
+ }
1551
+ verifyPosixHandlePath(
1552
+ currentPath,
1553
+ opened.descriptor,
1554
+ opened.identity,
1555
+ true,
1556
+ fsImpl,
1557
+ );
1558
+ visit(path.join(currentPath, name), depth + 1, false, {
1559
+ descriptor: opened.descriptor,
1560
+ identity: opened.identity,
1561
+ path: currentPath,
1562
+ });
1563
+ }
1564
+ const afterNames = fsImpl.readdirSync(currentPath).toSorted();
1565
+ if (names.length !== afterNames.length
1566
+ || names.some((name, index) => name !== afterNames[index])) {
1567
+ throw new Error('Private tree hardening or verification failed.');
1568
+ }
1569
+ }
1570
+ verifyPosixHandlePath(
1571
+ currentPath,
1572
+ opened.descriptor,
1573
+ opened.identity,
1574
+ opened.directory,
1575
+ fsImpl,
1576
+ );
1577
+ } finally {
1578
+ fsImpl.closeSync(opened.descriptor);
1579
+ }
1580
+ };
1581
+
1582
+ try {
1583
+ visit(root, 0, true);
1584
+ } catch {
1585
+ throw new Error('Private tree hardening or verification failed.');
1586
+ }
1587
+ }
1588
+
1589
+ function securePrivateTree(targetPath, options = {}) {
1590
+ const fsImpl = options.fsImpl || fs;
1591
+ const platform = options.platform || process.platform;
1592
+ if (platform === 'win32') {
1593
+ windowsAclController(options).hardenTree(targetPath);
1594
+ return;
1595
+ }
1596
+ securePosixPrivateTree(targetPath, fsImpl, options);
31
1597
  }
32
1598
 
33
1599
  module.exports = {
@@ -35,5 +1601,6 @@ module.exports = {
35
1601
  PRIVATE_FILE_MODE,
36
1602
  ensurePrivateDirectory,
37
1603
  securePrivateFile,
1604
+ securePrivateTree,
38
1605
  writePrivateFile,
39
1606
  };