@openclaw/fs-safe 0.14.0 → 0.16.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 (370) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +38 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -1
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-merge.d.ts.map +1 -1
  10. package/dist/archive-merge.js +113 -46
  11. package/dist/archive-parser.wasm +0 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +10 -11
  14. package/dist/archive-tar-stream.d.ts +3 -0
  15. package/dist/archive-tar-stream.d.ts.map +1 -1
  16. package/dist/archive-tar-stream.js +56 -37
  17. package/dist/archive-tar-wasm.d.ts +16 -4
  18. package/dist/archive-tar-wasm.d.ts.map +1 -1
  19. package/dist/archive-tar-wasm.js +134 -34
  20. package/dist/archive-zip-directory.d.ts +4 -0
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +2 -0
  23. package/dist/archive-zip-entry.d.ts +6 -2
  24. package/dist/archive-zip-entry.d.ts.map +1 -1
  25. package/dist/archive-zip-entry.js +23 -8
  26. package/dist/archive-zip-integrity.d.ts.map +1 -1
  27. package/dist/archive-zip-integrity.js +3 -4
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +107 -31
  30. package/dist/archive-zip-names.d.ts +1 -0
  31. package/dist/archive-zip-names.d.ts.map +1 -1
  32. package/dist/archive-zip-names.js +6 -0
  33. package/dist/archive.d.ts.map +1 -1
  34. package/dist/archive.js +11 -11
  35. package/dist/bounded-read-stream.d.ts +0 -1
  36. package/dist/bounded-read-stream.d.ts.map +1 -1
  37. package/dist/bounded-read-stream.js +0 -6
  38. package/dist/clone-metadata.d.ts +1 -0
  39. package/dist/clone-metadata.d.ts.map +1 -1
  40. package/dist/clone-metadata.js +6 -2
  41. package/dist/copy-publication.d.ts +6 -0
  42. package/dist/copy-publication.d.ts.map +1 -1
  43. package/dist/copy-publication.js +3 -0
  44. package/dist/copy-tree-portable.d.ts.map +1 -1
  45. package/dist/copy-tree-portable.js +44 -24
  46. package/dist/copy.d.ts.map +1 -1
  47. package/dist/copy.js +29 -11
  48. package/dist/create-directory.d.ts +20 -0
  49. package/dist/create-directory.d.ts.map +1 -0
  50. package/dist/create-directory.js +130 -0
  51. package/dist/create-file-async.d.ts +7 -0
  52. package/dist/create-file-async.d.ts.map +1 -0
  53. package/dist/create-file-async.js +121 -0
  54. package/dist/create-file.d.ts +8 -0
  55. package/dist/create-file.d.ts.map +1 -0
  56. package/dist/create-file.js +190 -0
  57. package/dist/create-owned-file.d.ts +8 -0
  58. package/dist/create-owned-file.d.ts.map +1 -0
  59. package/dist/create-owned-file.js +16 -0
  60. package/dist/create.d.ts +4 -0
  61. package/dist/create.d.ts.map +1 -0
  62. package/dist/create.js +2 -0
  63. package/dist/creation-darwin.d.ts +7 -0
  64. package/dist/creation-darwin.d.ts.map +1 -0
  65. package/dist/creation-darwin.js +79 -0
  66. package/dist/creation-file-state.d.ts +19 -0
  67. package/dist/creation-file-state.d.ts.map +1 -0
  68. package/dist/creation-file-state.js +118 -0
  69. package/dist/creation-path.d.ts +21 -0
  70. package/dist/creation-path.d.ts.map +1 -0
  71. package/dist/creation-path.js +71 -0
  72. package/dist/creation-permissions.d.ts +19 -0
  73. package/dist/creation-permissions.d.ts.map +1 -0
  74. package/dist/creation-permissions.js +125 -0
  75. package/dist/directory-durability.d.ts +1 -1
  76. package/dist/directory-durability.d.ts.map +1 -1
  77. package/dist/directory-durability.js +22 -80
  78. package/dist/directory-guard.d.ts +3 -0
  79. package/dist/directory-guard.d.ts.map +1 -1
  80. package/dist/directory-mode-node.d.ts +2 -0
  81. package/dist/directory-mode-node.d.ts.map +1 -1
  82. package/dist/directory-mode-node.js +8 -0
  83. package/dist/directory-mode-owner.js +5 -5
  84. package/dist/directory-receipt.d.ts +24 -0
  85. package/dist/directory-receipt.d.ts.map +1 -0
  86. package/dist/directory-receipt.js +127 -0
  87. package/dist/file-cleanup.d.ts +19 -0
  88. package/dist/file-cleanup.d.ts.map +1 -0
  89. package/dist/file-cleanup.js +78 -0
  90. package/dist/file-handle-transfer.d.ts +2 -0
  91. package/dist/file-handle-transfer.d.ts.map +1 -1
  92. package/dist/file-handle-transfer.js +57 -2
  93. package/dist/file-identity.d.ts.map +1 -1
  94. package/dist/file-identity.js +18 -4
  95. package/dist/file-lock-sync-admission.d.ts +19 -0
  96. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  97. package/dist/file-lock-sync-admission.js +93 -0
  98. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  99. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  100. package/dist/file-lock-sync-root-acquire.js +370 -0
  101. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  102. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  103. package/dist/file-lock-sync-root-arbitration.js +66 -0
  104. package/dist/file-lock-sync-root-held.d.ts +34 -0
  105. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  106. package/dist/file-lock-sync-root-held.js +393 -0
  107. package/dist/file-lock-sync-root-io.d.ts +44 -0
  108. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  109. package/dist/file-lock-sync-root-io.js +209 -0
  110. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  111. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  112. package/dist/file-lock-sync-root-mutation.js +277 -0
  113. package/dist/file-lock-sync-root-options.d.ts +20 -0
  114. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  115. package/dist/file-lock-sync-root-options.js +58 -0
  116. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  117. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  118. package/dist/file-lock-sync-root-registration.js +90 -0
  119. package/dist/file-lock-sync-root.d.ts +36 -0
  120. package/dist/file-lock-sync-root.d.ts.map +1 -0
  121. package/dist/file-lock-sync-root.js +361 -0
  122. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  123. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  124. package/dist/file-lock-sync-stale-admission.js +205 -0
  125. package/dist/file-lock-sync.d.ts.map +1 -1
  126. package/dist/file-lock-sync.js +245 -205
  127. package/dist/file-observation.d.ts +1 -1
  128. package/dist/file-observation.d.ts.map +1 -1
  129. package/dist/file-store-boundary.d.ts +2 -6
  130. package/dist/file-store-boundary.d.ts.map +1 -1
  131. package/dist/file-store-boundary.js +3 -9
  132. package/dist/file-store-prune.d.ts.map +1 -1
  133. package/dist/file-store-prune.js +5 -1
  134. package/dist/file-store-sync-write.d.ts.map +1 -1
  135. package/dist/file-store-sync-write.js +5 -8
  136. package/dist/file-store.d.ts.map +1 -1
  137. package/dist/file-store.js +47 -12
  138. package/dist/guarded-mkdir.d.ts +1 -0
  139. package/dist/guarded-mkdir.d.ts.map +1 -1
  140. package/dist/guarded-mkdir.js +36 -7
  141. package/dist/json-document-store.d.ts.map +1 -1
  142. package/dist/json-document-store.js +22 -15
  143. package/dist/json-durable-queue-ownership.d.ts +0 -1
  144. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  145. package/dist/json-durable-queue-ownership.js +0 -6
  146. package/dist/move-path.js +1 -1
  147. package/dist/native-binding.d.ts +13 -1
  148. package/dist/native-binding.d.ts.map +1 -1
  149. package/dist/native-fallback-warning.d.ts +4 -0
  150. package/dist/native-fallback-warning.d.ts.map +1 -0
  151. package/dist/native-fallback-warning.js +11 -0
  152. package/dist/native-operations.d.ts +0 -2
  153. package/dist/native-operations.d.ts.map +1 -1
  154. package/dist/native-operations.js +0 -24
  155. package/dist/native-parent-admission.d.ts +5 -2
  156. package/dist/native-parent-admission.d.ts.map +1 -1
  157. package/dist/native-parent-admission.js +27 -7
  158. package/dist/native-pinned-write-windows.d.ts +1 -1
  159. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  160. package/dist/native-pinned-write-windows.js +174 -29
  161. package/dist/native-pinned-write.d.ts.map +1 -1
  162. package/dist/native-pinned-write.js +26 -14
  163. package/dist/native-policy-parent-windows.d.ts +14 -0
  164. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  165. package/dist/native-policy-parent-windows.js +209 -0
  166. package/dist/native-rename-outcome.d.ts +4 -0
  167. package/dist/native-rename-outcome.d.ts.map +1 -0
  168. package/dist/native-rename-outcome.js +8 -0
  169. package/dist/native-staged-file.d.ts +3 -2
  170. package/dist/native-staged-file.d.ts.map +1 -1
  171. package/dist/native-staged-file.js +121 -72
  172. package/dist/output.d.ts.map +1 -1
  173. package/dist/output.js +12 -8
  174. package/dist/owner-dacl.d.ts.map +1 -1
  175. package/dist/owner-dacl.js +10 -4
  176. package/dist/path-prefix.d.ts.map +1 -1
  177. package/dist/path-prefix.js +30 -8
  178. package/dist/path-suffix-aliases.d.ts +2 -0
  179. package/dist/path-suffix-aliases.d.ts.map +1 -1
  180. package/dist/path-suffix-aliases.js +25 -17
  181. package/dist/permission-exec.d.ts +2 -0
  182. package/dist/permission-exec.d.ts.map +1 -1
  183. package/dist/permission-exec.js +150 -21
  184. package/dist/permissions-windows.js +1 -1
  185. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  186. package/dist/pinned-mutation-admission.js +10 -5
  187. package/dist/pinned-mutation-observation.d.ts +0 -1
  188. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  189. package/dist/pinned-mutation-observation.js +0 -19
  190. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  191. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  192. package/dist/pinned-mutation-shared-route.js +1 -1
  193. package/dist/pinned-write-input.d.ts +4 -0
  194. package/dist/pinned-write-input.d.ts.map +1 -0
  195. package/dist/pinned-write-input.js +25 -0
  196. package/dist/pinned-write-mode.d.ts +5 -0
  197. package/dist/pinned-write-mode.d.ts.map +1 -0
  198. package/dist/pinned-write-mode.js +24 -0
  199. package/dist/pinned-write-staged.d.ts +6 -0
  200. package/dist/pinned-write-staged.d.ts.map +1 -0
  201. package/dist/pinned-write-staged.js +187 -0
  202. package/dist/pinned-write-types.d.ts +5 -0
  203. package/dist/pinned-write-types.d.ts.map +1 -1
  204. package/dist/pinned-write.d.ts.map +1 -1
  205. package/dist/pinned-write.js +35 -145
  206. package/dist/private-directory.d.ts.map +1 -1
  207. package/dist/private-directory.js +18 -4
  208. package/dist/private-producer-handoff-sync.d.ts +14 -0
  209. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  210. package/dist/private-producer-handoff-sync.js +114 -0
  211. package/dist/private-producer-handoff.d.ts +22 -4
  212. package/dist/private-producer-handoff.d.ts.map +1 -1
  213. package/dist/private-producer-handoff.js +140 -77
  214. package/dist/private-temp-workspace.d.ts.map +1 -1
  215. package/dist/private-temp-workspace.js +75 -121
  216. package/dist/publish-copy-stage.d.ts +2 -1
  217. package/dist/publish-copy-stage.d.ts.map +1 -1
  218. package/dist/publish-copy-stage.js +16 -7
  219. package/dist/publish-file.d.ts.map +1 -1
  220. package/dist/publish-file.js +2 -2
  221. package/dist/regular-file.d.ts.map +1 -1
  222. package/dist/regular-file.js +1 -15
  223. package/dist/replace-directory.d.ts.map +1 -1
  224. package/dist/replace-directory.js +256 -18
  225. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  226. package/dist/replace-file-copy-fallback.js +62 -70
  227. package/dist/replace-file-copy-source.d.ts.map +1 -1
  228. package/dist/replace-file-copy-source.js +10 -12
  229. package/dist/replace-file-temp-owner.d.ts +5 -9
  230. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  231. package/dist/replace-file-temp-owner.js +56 -72
  232. package/dist/replace-file.js +6 -6
  233. package/dist/retained-directory-replacement.d.ts +26 -0
  234. package/dist/retained-directory-replacement.d.ts.map +1 -0
  235. package/dist/retained-directory-replacement.js +193 -0
  236. package/dist/root-boundary.d.ts +1 -0
  237. package/dist/root-boundary.d.ts.map +1 -1
  238. package/dist/root-boundary.js +4 -0
  239. package/dist/root-context.d.ts +0 -8
  240. package/dist/root-context.d.ts.map +1 -1
  241. package/dist/root-context.js +0 -3
  242. package/dist/root-create-input.d.ts +8 -1
  243. package/dist/root-create-input.d.ts.map +1 -1
  244. package/dist/root-create-input.js +17 -4
  245. package/dist/root-directory-creation.d.ts +3 -3
  246. package/dist/root-directory-creation.d.ts.map +1 -1
  247. package/dist/root-directory-creation.js +15 -3
  248. package/dist/root-directory-list.d.ts +1 -0
  249. package/dist/root-directory-list.d.ts.map +1 -1
  250. package/dist/root-directory-list.js +1 -0
  251. package/dist/root-impl.d.ts.map +1 -1
  252. package/dist/root-impl.js +46 -17
  253. package/dist/root-move-noreplace.d.ts.map +1 -1
  254. package/dist/root-move-noreplace.js +24 -15
  255. package/dist/root-options.d.ts +12 -4
  256. package/dist/root-options.d.ts.map +1 -1
  257. package/dist/root-path-errors.d.ts +1 -0
  258. package/dist/root-path-errors.d.ts.map +1 -1
  259. package/dist/root-path-errors.js +11 -2
  260. package/dist/root-path-existing.d.ts.map +1 -1
  261. package/dist/root-path-existing.js +11 -35
  262. package/dist/root-path-stat.d.ts.map +1 -1
  263. package/dist/root-path-stat.js +59 -7
  264. package/dist/root-path.js +1 -13
  265. package/dist/root-remove.d.ts +1 -0
  266. package/dist/root-remove.d.ts.map +1 -1
  267. package/dist/root-remove.js +4 -0
  268. package/dist/root-walk.d.ts +1 -1
  269. package/dist/root-walk.d.ts.map +1 -1
  270. package/dist/root-walk.js +17 -2
  271. package/dist/root-write-admission.d.ts +0 -2
  272. package/dist/root-write-admission.d.ts.map +1 -1
  273. package/dist/root-write-admission.js +1 -15
  274. package/dist/root-write-complete-parent.d.ts.map +1 -1
  275. package/dist/root-write-complete-parent.js +7 -23
  276. package/dist/root-write-publication.js +1 -1
  277. package/dist/root-write-verification.d.ts.map +1 -1
  278. package/dist/root-write-verification.js +29 -42
  279. package/dist/secret-file.d.ts.map +1 -1
  280. package/dist/secret-file.js +3 -24
  281. package/dist/secret-read-async.d.ts.map +1 -1
  282. package/dist/secret-read-async.js +3 -24
  283. package/dist/secret-read-policy.d.ts +6 -2
  284. package/dist/secret-read-policy.d.ts.map +1 -1
  285. package/dist/secret-read-policy.js +26 -2
  286. package/dist/secure-file-windows.d.ts +6 -0
  287. package/dist/secure-file-windows.d.ts.map +1 -1
  288. package/dist/secure-file-windows.js +34 -117
  289. package/dist/secure-file.js +2 -2
  290. package/dist/sibling-temp.d.ts.map +1 -1
  291. package/dist/sibling-temp.js +23 -12
  292. package/dist/sidecar-lock-acquire.d.ts +2 -28
  293. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  294. package/dist/sidecar-lock-acquire.js +288 -199
  295. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  296. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  297. package/dist/sidecar-lock-admission-context.js +60 -0
  298. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  299. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  300. package/dist/sidecar-lock-admission-parser.js +113 -0
  301. package/dist/sidecar-lock-admission.d.ts +35 -0
  302. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  303. package/dist/sidecar-lock-admission.js +7 -0
  304. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  305. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  306. package/dist/sidecar-lock-reclaim.js +80 -25
  307. package/dist/sidecar-lock-root.d.ts.map +1 -1
  308. package/dist/sidecar-lock-root.js +2 -1
  309. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  310. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  311. package/dist/sidecar-lock-stale-admission.js +232 -0
  312. package/dist/sidecar-lock-target.d.ts +8 -0
  313. package/dist/sidecar-lock-target.d.ts.map +1 -0
  314. package/dist/sidecar-lock-target.js +55 -0
  315. package/dist/sidecar-lock.d.ts.map +1 -1
  316. package/dist/sidecar-lock.js +100 -16
  317. package/dist/staged-directory.d.ts.map +1 -1
  318. package/dist/staged-directory.js +6 -6
  319. package/dist/staged-file-settlement.d.ts +17 -0
  320. package/dist/staged-file-settlement.d.ts.map +1 -0
  321. package/dist/staged-file-settlement.js +57 -0
  322. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  323. package/dist/temp-workspace-descriptor.js +9 -27
  324. package/dist/temp-workspace-owner.d.ts.map +1 -1
  325. package/dist/temp-workspace-owner.js +8 -8
  326. package/dist/walk.d.ts +5 -1
  327. package/dist/walk.d.ts.map +1 -1
  328. package/dist/walk.js +19 -6
  329. package/dist/windows-owner.d.ts.map +1 -1
  330. package/dist/windows-owner.js +4 -3
  331. package/dist/windows-security-bridge.cs +336 -0
  332. package/dist/windows-security-bridge.ps1 +15 -0
  333. package/dist/windows-security-command.d.ts +26 -0
  334. package/dist/windows-security-command.d.ts.map +1 -0
  335. package/dist/windows-security-command.js +363 -0
  336. package/dist/windows-security-facts.d.ts +6 -0
  337. package/dist/windows-security-facts.d.ts.map +1 -0
  338. package/dist/windows-security-facts.js +108 -0
  339. package/docs/advanced.md +4 -2
  340. package/docs/archive.md +97 -46
  341. package/docs/atomic.md +85 -8
  342. package/docs/config.md +6 -2
  343. package/docs/contributing.md +44 -4
  344. package/docs/copy.md +37 -0
  345. package/docs/creation.md +128 -0
  346. package/docs/durability.md +24 -0
  347. package/docs/file-store.md +19 -0
  348. package/docs/install.md +31 -7
  349. package/docs/json-store.md +5 -0
  350. package/docs/migrating-to-0.5.md +15 -6
  351. package/docs/migrating-to-0.6.md +9 -4
  352. package/docs/native-helper.md +32 -12
  353. package/docs/native.md +38 -7
  354. package/docs/output.md +6 -0
  355. package/docs/path-prefix.md +10 -0
  356. package/docs/path-suffix-aliases.md +51 -6
  357. package/docs/permissions.md +50 -14
  358. package/docs/public-api.md +3 -2
  359. package/docs/root.md +56 -3
  360. package/docs/secret-file.md +11 -2
  361. package/docs/secure-file.md +9 -4
  362. package/docs/sidecar-lock.md +114 -8
  363. package/docs/staged-file.md +12 -3
  364. package/docs/temp.md +20 -3
  365. package/docs/walk.md +67 -1
  366. package/docs/writing.md +76 -6
  367. package/package.json +19 -16
  368. package/dist/darwin-acl.d.ts +0 -4
  369. package/dist/darwin-acl.d.ts.map +0 -1
  370. package/dist/darwin-acl.js +0 -24
@@ -185,18 +185,15 @@ export class TempWorkspaceRetainedChild {
185
185
  return descriptor;
186
186
  }
187
187
  async #assertProcAuthority(fd, ownerUid) {
188
- if ((await fs.statfs("/proc/self/fd", { bigint: true })).type !== 0x9fa0n) {
189
- throw new FsSafeError("path-mismatch", "directory mode requires a trusted procfs fd namespace");
190
- }
191
- const procPath = `/proc/self/fd/${fd}`;
192
- const opened = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), this.#identity);
193
- const followed = inspectFileIdentitySync(() => fsSync.statSync(procPath, { bigint: true }), this.#identity);
194
- assertOwnedDirectory(opened, followed);
195
- assertTempWorkspaceChildState(opened, ownerUid);
196
- assertTempWorkspaceChildState(followed, ownerUid);
188
+ const filesystem = await fs.statfs("/proc/self/fd", { bigint: true });
189
+ this.#assertProcDescriptorAuthority(fd, ownerUid, filesystem.type);
197
190
  }
198
191
  #assertProcAuthoritySync(fd, ownerUid) {
199
- if (fsSync.statfsSync("/proc/self/fd", { bigint: true }).type !== 0x9fa0n) {
192
+ const filesystem = fsSync.statfsSync("/proc/self/fd", { bigint: true });
193
+ this.#assertProcDescriptorAuthority(fd, ownerUid, filesystem.type);
194
+ }
195
+ #assertProcDescriptorAuthority(fd, ownerUid, filesystemType) {
196
+ if (filesystemType !== 0x9fa0n) {
200
197
  throw new FsSafeError("path-mismatch", "directory mode requires a trusted procfs fd namespace");
201
198
  }
202
199
  const procPath = `/proc/self/fd/${fd}`;
@@ -309,13 +306,7 @@ export class TempWorkspaceRetainedChild {
309
306
  catch (error) {
310
307
  // The readable replacement has not been installed. Give it exactly one
311
308
  // close attempt and leave neither indeterminate descriptor owned here.
312
- try {
313
- fsSync.closeSync(fd);
314
- }
315
- catch (closeError) {
316
- throw new AggregateError([error, closeError], "temp workspace child descriptor replacement close failed");
317
- }
318
- throw error;
309
+ closeAfterAdmissionFailure(fd, error, "temp workspace child descriptor replacement close failed");
319
310
  }
320
311
  this.#fd = fd;
321
312
  this.#access = "read";
@@ -334,16 +325,7 @@ export class TempWorkspaceRetainedChild {
334
325
  const fd = this.#fd;
335
326
  this.#fd = undefined;
336
327
  if (retainDescriptor && !this.canEnumerate) {
337
- try {
338
- fsSync.closeSync(fd);
339
- }
340
- catch (closeError) {
341
- throw new AggregateError([
342
- new FsSafeError("helper-unavailable", "temp workspace cleanup requires a readable child descriptor"),
343
- closeError,
344
- ], "temp workspace child descriptor rejection and close failed");
345
- }
346
- throw new FsSafeError("helper-unavailable", "temp workspace cleanup requires a readable child descriptor");
328
+ closeAfterAdmissionFailure(fd, new FsSafeError("helper-unavailable", "temp workspace cleanup requires a readable child descriptor"), "temp workspace child descriptor rejection and close failed");
347
329
  }
348
330
  if (!retainDescriptor) {
349
331
  fsSync.closeSync(fd);
@@ -1 +1 @@
1
- {"version":3,"file":"temp-workspace-owner.d.ts","sourceRoot":"","sources":["../src/temp-workspace-owner.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAEL,0BAA0B,EAE1B,KAAK,iBAAiB,EACvB,MAAM,gCAAgC,CAAC;AAGxC,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,GAAG,eAAe,CAAC;AACvG,MAAM,MAAM,0BAA0B,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAsB1E,qBAAa,8BAA8B;;IACzC,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAM/C,YACE,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,0BAA0B,EAClC,SAAS,EAAE,0BAA0B,EACrC,OAAO,EAAE,MAAM,EAuEhB;IAED,IAAI,kBAAkB,IAAI,OAAO,CAGhC;IAED,oBAAoB,IAAI,IAAI,CAiB3B;IAUD,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAenD;IAED,aAAa,IAAI,IAAI,CAEpB;IAED,qBAAqB,IAAI,IAAI,CAE5B;IAED,KAAK,IAAI,IAAI,CAIZ;CACF;AAED,qBAAa,yBAAyB;;IAWpC,YACE,QAAQ,EAAE,0BAA0B,EACpC,UAAU,EAAE,8BAA8B,EAC1C,gBAAgB,EAAE,OAAO,EAO1B;IAkLD,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAM7C;IAED,WAAW,IAAI,0BAA0B,CAexC;CACF"}
1
+ {"version":3,"file":"temp-workspace-owner.d.ts","sourceRoot":"","sources":["../src/temp-workspace-owner.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAEL,0BAA0B,EAE1B,KAAK,iBAAiB,EACvB,MAAM,gCAAgC,CAAC;AAGxC,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,GAAG,eAAe,CAAC;AACvG,MAAM,MAAM,0BAA0B,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAuB1E,qBAAa,8BAA8B;;IACzC,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAM/C,YACE,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,0BAA0B,EAClC,SAAS,EAAE,0BAA0B,EACrC,OAAO,EAAE,MAAM,EAuEhB;IAED,IAAI,kBAAkB,IAAI,OAAO,CAGhC;IAED,oBAAoB,IAAI,IAAI,CAiB3B;IAUD,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAenD;IAED,aAAa,IAAI,IAAI,CAEpB;IAED,qBAAqB,IAAI,IAAI,CAE5B;IAED,KAAK,IAAI,IAAI,CAIZ;CACF;AAED,qBAAa,yBAAyB;;IAWpC,YACE,QAAQ,EAAE,0BAA0B,EACpC,UAAU,EAAE,8BAA8B,EAC1C,gBAAgB,EAAE,OAAO,EAO1B;IAkLD,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAM7C;IAED,WAAW,IAAI,0BAA0B,CAexC;CACF"}
@@ -294,22 +294,22 @@ export class TempWorkspaceCleanupOwner {
294
294
  await beforeNativeRemoval(quarantine.path);
295
295
  return this.#mapRemoval(await this.#capability.binding.removeOwnedTree(this.#capability.parent.fd, quarantine.name, this.#directory.fd));
296
296
  }
297
- let removalError;
297
+ let removalFailure;
298
298
  try {
299
299
  this.#assertQuarantine(quarantine);
300
300
  try {
301
301
  await fs.rm(quarantine.path, { recursive: true, force: true });
302
302
  }
303
303
  catch (error) {
304
- removalError = error;
304
+ removalFailure = { error };
305
305
  throw error;
306
306
  }
307
307
  this.#capability.assertCurrent();
308
308
  return "removed";
309
309
  }
310
310
  catch (error) {
311
- if (error === removalError)
312
- throw error;
311
+ if (removalFailure !== undefined)
312
+ throw removalFailure.error;
313
313
  return "indeterminate";
314
314
  }
315
315
  }
@@ -318,22 +318,22 @@ export class TempWorkspaceCleanupOwner {
318
318
  getFsSafeTestHooks()?.beforeTempWorkspaceNativeRemovalSync?.(quarantine.path);
319
319
  return this.#mapRemoval(this.#capability.binding.removeOwnedTreeSync(this.#capability.parent.fd, quarantine.name, this.#directory.fd));
320
320
  }
321
- let removalError;
321
+ let removalFailure;
322
322
  try {
323
323
  this.#assertQuarantine(quarantine);
324
324
  try {
325
325
  fsSync.rmSync(quarantine.path, { recursive: true, force: true });
326
326
  }
327
327
  catch (error) {
328
- removalError = error;
328
+ removalFailure = { error };
329
329
  throw error;
330
330
  }
331
331
  this.#capability.assertCurrent();
332
332
  return "removed";
333
333
  }
334
334
  catch (error) {
335
- if (error === removalError)
336
- throw error;
335
+ if (removalFailure !== undefined)
336
+ throw removalFailure.error;
337
337
  return "indeterminate";
338
338
  }
339
339
  }
package/dist/walk.d.ts CHANGED
@@ -16,6 +16,10 @@ export type WalkDirectoryOptions = {
16
16
  include?: (entry: WalkDirectoryEntry) => boolean;
17
17
  descend?: (entry: WalkDirectoryEntry) => boolean;
18
18
  };
19
+ export type AsyncWalkDirectoryOptions = Omit<WalkDirectoryOptions, "include" | "descend"> & {
20
+ include?: (entry: WalkDirectoryEntry) => boolean | Promise<boolean>;
21
+ descend?: (entry: WalkDirectoryEntry) => boolean | Promise<boolean>;
22
+ };
19
23
  export type WalkDirectoryFailure = {
20
24
  path: string;
21
25
  relativePath: string;
@@ -32,6 +36,6 @@ type WalkDirectoryResultWithFailures = WalkDirectoryResult & {
32
36
  failedDirs: WalkDirectoryFailure[];
33
37
  };
34
38
  export declare function walkDirectorySync(rootDir: string, options?: WalkDirectoryOptions): WalkDirectoryResultWithFailures;
35
- export declare function walkDirectory(rootDir: string, options?: WalkDirectoryOptions): Promise<WalkDirectoryResultWithFailures>;
39
+ export declare function walkDirectory(rootDir: string, options?: AsyncWalkDirectoryOptions): Promise<WalkDirectoryResultWithFailures>;
36
40
  export {};
37
41
  //# sourceMappingURL=walk.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAS7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AA+EF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CA2DjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CA2D1C"}
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAS7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,oBAAoB,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG;IAC1F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AAoFF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CA4DjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,+BAA+B,CAAC,CAiE1C"}
package/dist/walk.js CHANGED
@@ -16,6 +16,10 @@ function validateWalkOptions(options) {
16
16
  throw new TypeError(`invalid walk symlink policy: ${String(options.symlinks)}`);
17
17
  }
18
18
  }
19
+ function isObjectResult(result) {
20
+ return result !== null &&
21
+ (typeof result === "object" || typeof result === "function");
22
+ }
19
23
  function kindForDirent(dirent) {
20
24
  if (dirent.isDirectory())
21
25
  return "directory";
@@ -85,6 +89,8 @@ export function walkDirectorySync(rootDir, options = {}) {
85
89
  let realDir;
86
90
  const operationPath = pathForWindowsFilesystem(dir);
87
91
  try {
92
+ if (depth > 1 && symlinks !== "follow" && fsSync.lstatSync(operationPath).isSymbolicLink())
93
+ return;
88
94
  realDir = realpathSync(operationPath);
89
95
  }
90
96
  catch (error) {
@@ -146,6 +152,8 @@ export async function walkDirectory(rootDir, options = {}) {
146
152
  let realDir;
147
153
  const operationPath = pathForWindowsFilesystem(dir);
148
154
  try {
155
+ if (depth > 1 && symlinks !== "follow" && fsSync.lstatSync(operationPath).isSymbolicLink())
156
+ return;
149
157
  realDir = realpathSync.native(operationPath);
150
158
  }
151
159
  catch (error) {
@@ -175,15 +183,20 @@ export async function walkDirectory(rootDir, options = {}) {
175
183
  continue;
176
184
  const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
177
185
  const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
178
- if (options.include?.(entry) ?? true) {
186
+ const include = options.include;
187
+ const included = include == null ? true : Reflect.apply(include, options, [entry]);
188
+ if ((isObjectResult(included) ? await included : included) ?? true) {
179
189
  result.entries.push(entry);
180
190
  }
181
191
  if (kind === "directory" &&
182
- (options.maxDepth === undefined || depth < options.maxDepth) &&
183
- (options.descend?.(entry) ?? true)) {
184
- await visit(fullPath, relativePath, depth + 1);
185
- if (result.truncated)
186
- return;
192
+ (options.maxDepth === undefined || depth < options.maxDepth)) {
193
+ const descend = options.descend;
194
+ const descended = descend == null ? true : Reflect.apply(descend, options, [entry]);
195
+ if ((isObjectResult(descended) ? await descended : descended) ?? true) {
196
+ await visit(fullPath, relativePath, depth + 1);
197
+ if (result.truncated)
198
+ return;
199
+ }
187
200
  }
188
201
  }
189
202
  }
@@ -1 +1 @@
1
- {"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAqDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAmD/B"}
1
+ {"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAsDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAmD/B"}
@@ -1,4 +1,4 @@
1
- import { formatPermissionErrorDetail, getPermissionCommandFailure, } from "./permission-exec.js";
1
+ import { formatCaughtPermissionFailure, formatPermissionErrorDetail, getPermissionCommandFailure, } from "./permission-exec.js";
2
2
  import { resolveWindowsSystemCommand } from "./windows-command.js";
3
3
  import { hasWindowsPathAlias } from "./windows-path-alias.js";
4
4
  const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
@@ -14,6 +14,7 @@ function windowsOwnerQueryCommand(targetPath) {
14
14
  const encodedPath = Buffer.from(targetPath, "utf8").toString("base64");
15
15
  return [
16
16
  "$ErrorActionPreference='Stop'",
17
+ "$env:PSModulePath=[IO.Path]::Combine($PSHOME,'Modules')",
17
18
  `$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPath}'))`,
18
19
  "$sections=[System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner",
19
20
  "$acl=if([IO.Directory]::Exists($p)){[IO.Directory]::GetAccessControl($p,$sections)}else{[IO.File]::GetAccessControl($p,$sections)}",
@@ -29,7 +30,7 @@ function windowsOwnerQueryCommand(targetPath) {
29
30
  "$raw=[System.Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(),0)",
30
31
  "$dacl=$raw.DiscretionaryAcl;$complete=$true",
31
32
  "$aces=@(foreach($ace in $dacl){if($ace -isnot [System.Security.AccessControl.CommonAce] -or $ace.IsCallback -or [int]$ace.AceType -notin @(0,1)){$complete=$false;continue};@{sid=$ace.SecurityIdentifier.Value;mask=([long]$ace.AccessMask -band 4294967295);deny=([int]$ace.AceType -eq 1);inheritOnly=([int]$ace.AceFlags -band 8) -ne 0}})",
32
- "@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|ConvertTo-Json -Depth 4 -Compress",
33
+ "@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|Microsoft.PowerShell.Utility\\ConvertTo-Json -Depth 4 -Compress",
33
34
  ].join(";");
34
35
  }
35
36
  function parseWindowsAclFacts(parsed) {
@@ -89,7 +90,7 @@ export async function inspectWindowsOwner(params) {
89
90
  }
90
91
  catch (err) {
91
92
  return {
92
- error: formatPermissionErrorDetail(String(err)),
93
+ error: formatCaughtPermissionFailure(err),
93
94
  errorDetail: getPermissionCommandFailure(err, command, performance.now() - startedAt),
94
95
  errorCause: err,
95
96
  };
@@ -0,0 +1,336 @@
1
+ // Executed only by the built-in Windows PowerShell/.NET host. The raw security
2
+ // descriptor never passes through CommonSecurityDescriptor's ACE normalization.
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.ComponentModel;
6
+ using System.IO;
7
+ using System.Runtime.InteropServices;
8
+ using System.Security.AccessControl;
9
+ using System.Security.Principal;
10
+ using System.Text;
11
+ using Microsoft.Win32.SafeHandles;
12
+
13
+ public static partial class FsSafeWindowsBridge {
14
+ [StructLayout(LayoutKind.Sequential)] struct IoStatus { public IntPtr Status; public UIntPtr Information; }
15
+ [StructLayout(LayoutKind.Sequential)] struct UnicodeString { public ushort Length, MaximumLength; public IntPtr Buffer; }
16
+ [StructLayout(LayoutKind.Sequential)] struct ObjectAttributes {
17
+ public uint Length; public IntPtr RootDirectory, ObjectName; public uint Attributes;
18
+ public IntPtr SecurityDescriptor, SecurityQualityOfService;
19
+ }
20
+ [StructLayout(LayoutKind.Sequential)] struct FileInfo {
21
+ public uint Attributes, CreationLow, CreationHigh, AccessLow, AccessHigh, WriteLow, WriteHigh;
22
+ public uint Volume, SizeHigh, SizeLow, Links, IndexHigh, IndexLow;
23
+ }
24
+ [StructLayout(LayoutKind.Sequential)] struct FileId {
25
+ public ulong Volume; [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)] public byte[] Id;
26
+ }
27
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true)] static extern SafeFileHandle CreateFileW(string path, uint access, uint share, IntPtr security, uint disposition, uint flags, IntPtr template);
28
+ [DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr GetStdHandle(int standard);
29
+ [DllImport("kernel32.dll", SetLastError=true)] static extern uint GetFileType(SafeFileHandle handle);
30
+ [DllImport("kernel32.dll", SetLastError=true)] static extern bool GetFileInformationByHandle(SafeFileHandle handle, out FileInfo information);
31
+ [DllImport("kernel32.dll", SetLastError=true)] static extern bool GetFileInformationByHandleEx(SafeFileHandle handle, int kind, out FileId information, uint size);
32
+ [DllImport("kernel32.dll", SetLastError=true)] static extern bool SetFileInformationByHandle(SafeFileHandle handle, int kind, ref uint information, uint size);
33
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true)] static extern uint GetFinalPathNameByHandleW(SafeFileHandle handle, StringBuilder path, uint capacity, uint flags);
34
+ [DllImport("kernel32.dll")] static extern IntPtr LocalFree(IntPtr memory);
35
+ [DllImport("advapi32.dll")] static extern uint GetSecurityInfo(SafeFileHandle handle, int kind, uint sections, out IntPtr owner, out IntPtr group, out IntPtr dacl, out IntPtr sacl, out IntPtr descriptor);
36
+ [DllImport("advapi32.dll")] static extern uint GetSecurityDescriptorLength(IntPtr descriptor);
37
+ [DllImport("advapi32.dll", SetLastError=true)] static extern bool GetSecurityDescriptorDacl(IntPtr descriptor, out bool present, out IntPtr dacl, out bool defaulted);
38
+ [DllImport("advapi32.dll", SetLastError=true)] static extern bool GetSecurityDescriptorOwner(IntPtr descriptor, out IntPtr owner, out bool defaulted);
39
+ [DllImport("advapi32.dll")] static extern uint SetSecurityInfo(SafeFileHandle handle, int kind, uint sections, IntPtr owner, IntPtr group, IntPtr dacl, IntPtr sacl);
40
+ [DllImport("ntdll.dll")] static extern int NtQueryInformationFile(SafeFileHandle handle, out IoStatus io, [Out] byte[] information, uint length, int kind);
41
+ [DllImport("ntdll.dll")] static extern int NtCreateFile(out SafeFileHandle handle, uint access, ref ObjectAttributes attributes, out IoStatus io, IntPtr allocation, uint flags, uint share, uint disposition, uint options, IntPtr ea, uint eaLength);
42
+ [DllImport("ntdll.dll")] static extern uint RtlNtStatusToDosError(int status);
43
+
44
+ sealed class Failure : Exception {
45
+ public readonly string Code;
46
+ public Failure(string code, string message) : base(message) { Code=code; }
47
+ }
48
+ static Failure OsFailure(uint error, string operation, bool identity=false, bool fullIdentity=false) {
49
+ // Native security calls retain EACCES; shared handle identity calls use EPERM.
50
+ string code=error==5 ? (identity ? "EPERM" : "EACCES") : error==80 || error==183 ? "EEXIST" : error==2 || error==3 ? "ENOENT" :
51
+ identity && (error==112 || error==39) ? "ENOSPC" : identity && (error==32 || error==33) ? "EBUSY" :
52
+ fullIdentity && (error==1 || error==50 || error==87) ? "ENOTSUP" : "EIO";
53
+ return new Failure(code, operation+" failed with Windows error "+error);
54
+ }
55
+ static Failure CreateFailure(uint error) {
56
+ var failure=OsFailure(error,"create private directory",true);
57
+ return failure.Code=="EPERM" ? new Failure("EACCES",failure.Message) : failure;
58
+ }
59
+ static void Require(bool condition, string code, string message) {
60
+ if (!condition) throw new Failure(code,message);
61
+ }
62
+ static Dictionary<string,object> Row(params object[] fields) {
63
+ var result=new Dictionary<string,object>();
64
+ for(int i=0;i<fields.Length;i+=2) result.Add((string)fields[i],fields[i+1]);
65
+ return result;
66
+ }
67
+ static SafeFileHandle Open(string path, uint access, bool noFollow) {
68
+ var handle=CreateFileW(path,access,7,IntPtr.Zero,3,0x02000000u | (noFollow ? 0x00200000u : 0),IntPtr.Zero);
69
+ if(handle.IsInvalid) { uint error=(uint)Marshal.GetLastWin32Error(); handle.Dispose(); throw OsFailure(error,"open security handle"); }
70
+ return handle;
71
+ }
72
+ static FileInfo Information(SafeFileHandle handle) {
73
+ FileInfo info;
74
+ if(!GetFileInformationByHandle(handle,out info)) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect handle identity",true);
75
+ return info;
76
+ }
77
+ static string Identity(SafeFileHandle handle) {
78
+ var info=Information(handle);
79
+ return info.Volume.ToString("x8")+":"+(((ulong)info.IndexHigh<<32)|info.IndexLow).ToString("x16");
80
+ }
81
+ static string DirectoryIdentity(SafeFileHandle handle, bool requireLocal=false) {
82
+ var info=Information(handle);
83
+ Require((info.Attributes&0x400)==0,"ELOOP","private directory must not be a reparse point");
84
+ Require((info.Attributes&0x10)!=0,"ENOTDIR","private directory handle must name a directory");
85
+ if(requireLocal) Require(IsLocal(handle),"ENOTSUP","private directories require a local filesystem");
86
+ return FullIdentity(handle);
87
+ }
88
+ static string FullIdentity(SafeFileHandle handle) {
89
+ FileId id;
90
+ if(!GetFileInformationByHandleEx(handle,18,out id,24)) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect 128-bit file identity",true,true);
91
+ Require(id.Id!=null && id.Id.Length==16,"EIO","file identity is incomplete");
92
+ return id.Volume.ToString("x16")+":"+BitConverter.ToString(id.Id).Replace("-","").ToLowerInvariant();
93
+ }
94
+ static string FileIdentity(SafeFileHandle handle, uint expectedLinks) {
95
+ var info=Information(handle);
96
+ Require((info.Attributes&0x400)==0,"ELOOP","private file must not be a reparse point");
97
+ Require((info.Attributes&0x10)==0 && GetFileType(handle)==1,"EINVAL","private file must be a regular disk file");
98
+ Require(info.Links==expectedLinks,"EIO","private file link count changed");
99
+ return FullIdentity(handle);
100
+ }
101
+ static string FinalPath(SafeFileHandle handle, uint flags) {
102
+ int capacity=512;
103
+ for(int attempt=0;attempt<4;attempt++) {
104
+ var path=new StringBuilder(capacity);
105
+ uint length=GetFinalPathNameByHandleW(handle,path,(uint)capacity,flags);
106
+ if(length==0) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect handle locality");
107
+ if(length<capacity) return path.ToString();
108
+ Require(length<32768,"EIO","final handle path exceeds the inspection budget");
109
+ capacity=(int)length+1;
110
+ }
111
+ throw new Failure("EIO","final handle path changed during inspection");
112
+ }
113
+ static bool IsVolumeGuidPath(string path) {
114
+ return path.Length>=49 && path.StartsWith(@"\\?\Volume{",StringComparison.OrdinalIgnoreCase) &&
115
+ path[47]=='}' && path[48]=='\\' &&
116
+ System.Text.RegularExpressions.Regex.IsMatch(path.Substring(11,36),@"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
117
+ }
118
+ static bool IsLocalFinalPath(string path) {
119
+ return !path.StartsWith(@"\\?\UNC\",StringComparison.Ordinal) &&
120
+ (!path.StartsWith(@"\\",StringComparison.Ordinal) || path.StartsWith(@"\\?\",StringComparison.Ordinal));
121
+ }
122
+ static bool IsLocal(SafeFileHandle handle) {
123
+ IoStatus io; var remote=new byte[]{255};
124
+ int status=NtQueryInformationFile(handle,out io,remote,1,51);
125
+ if(status==0 && io.Information.ToUInt64()==1 && remote[0]<=1) return remote[0]==0;
126
+ try {
127
+ if(IsVolumeGuidPath(FinalPath(handle,9))) return true;
128
+ } catch(Failure) { }
129
+ return IsLocalFinalPath(FinalPath(handle,0));
130
+ }
131
+ static string Sid(SecurityIdentifier sid) {
132
+ Require(sid!=null,"EIO","security descriptor SID is missing");
133
+ return sid.Value.ToLowerInvariant();
134
+ }
135
+ static Dictionary<string,object> Security(SafeFileHandle handle, bool requireLocality=true) {
136
+ IntPtr owner,group,dacl,sacl,descriptor;
137
+ uint error=GetSecurityInfo(handle,1,5,out owner,out group,out dacl,out sacl,out descriptor);
138
+ if(error!=0) throw OsFailure(error,"read owner and DACL");
139
+ try {
140
+ Require(descriptor!=IntPtr.Zero && owner!=IntPtr.Zero,"EIO","security descriptor is incomplete");
141
+ uint length=GetSecurityDescriptorLength(descriptor);
142
+ Require(length>=20 && length<=1024*1024,"EIO","security descriptor exceeds the inspection budget");
143
+ var bytes=new byte[length]; Marshal.Copy(descriptor,bytes,0,(int)length);
144
+ var raw=new RawSecurityDescriptor(bytes,0);
145
+ string ownerSid=Sid(raw.Owner), currentSid;
146
+ using(var current=WindowsIdentity.GetCurrent()) currentSid=Sid(current.User);
147
+ var aces=new List<object>(); var unsupported=new List<int>();
148
+ if(raw.DiscretionaryAcl!=null) foreach(GenericAce ace in raw.DiscretionaryAcl) {
149
+ var basic=ace as CommonAce;
150
+ if(basic==null || basic.IsCallback || (ace.AceType!=AceType.AccessAllowed && ace.AceType!=AceType.AccessDenied)) {
151
+ unsupported.Add((int)ace.AceType); continue;
152
+ }
153
+ byte flags=(byte)ace.AceFlags;
154
+ aces.Add(Row("sid",Sid(basic.SecurityIdentifier),"mask",unchecked((uint)basic.AccessMask),
155
+ "aceType",ace.AceType==AceType.AccessAllowed ? "allow" : "deny",
156
+ "flags",Row("raw",flags,"objectInherit",(flags&1)!=0,"containerInherit",(flags&2)!=0,
157
+ "noPropagateInherit",(flags&4)!=0,"inheritOnly",(flags&8)!=0,"inherited",(flags&16)!=0,
158
+ "successfulAccess",(flags&64)!=0,"failedAccess",(flags&128)!=0)));
159
+ }
160
+ bool isLocal;
161
+ try { isLocal=IsLocal(handle); }
162
+ catch(Failure) { if(requireLocality) throw; isLocal=false; }
163
+ return Row("ownerSid",ownerSid,"currentUserSid",currentSid,"daclPresent",raw.DiscretionaryAcl!=null,
164
+ "daclProtected",(raw.ControlFlags&ControlFlags.DiscretionaryAclProtected)!=0,
165
+ "isLocal",isLocal,"aceListComplete",unsupported.Count==0,"unsupportedAceTypes",unsupported.ToArray(),"aces",aces.ToArray());
166
+ } finally { if(descriptor!=IntPtr.Zero) LocalFree(descriptor); }
167
+ }
168
+ static byte[] PrivateSecurity(bool inherit=true) {
169
+ SecurityIdentifier current;
170
+ using(var identity=WindowsIdentity.GetCurrent()) current=identity.User;
171
+ var acl=new RawAcl(2,3);
172
+ var principals=new[]{current,new SecurityIdentifier("S-1-5-18"),new SecurityIdentifier("S-1-5-32-544")};
173
+ var flags=inherit ? AceFlags.ObjectInherit|AceFlags.ContainerInherit : AceFlags.None;
174
+ foreach(var principal in principals) acl.InsertAce(acl.Count,new CommonAce(flags,AceQualifier.AccessAllowed,0x1f01ff,principal,false,null));
175
+ var descriptor=new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent|ControlFlags.DiscretionaryAclProtected|ControlFlags.SelfRelative,current,null,null,acl);
176
+ var bytes=new byte[descriptor.BinaryLength]; descriptor.GetBinaryForm(bytes,0); return bytes;
177
+ }
178
+ static SafeFileHandle CreateRelative(SafeFileHandle parent,string name) {
179
+ byte[] security=PrivateSecurity();
180
+ var pin=GCHandle.Alloc(security,GCHandleType.Pinned);
181
+ IntPtr nameBuffer=Marshal.StringToHGlobalUni(name), unicodeBuffer=IntPtr.Zero;
182
+ try {
183
+ int byteLength=checked(name.Length*2);
184
+ Require(byteLength>0 && byteLength<=65532,"EINVAL","private directory name is invalid");
185
+ var unicode=new UnicodeString { Length=(ushort)byteLength, MaximumLength=(ushort)(byteLength+2), Buffer=nameBuffer };
186
+ unicodeBuffer=Marshal.AllocHGlobal(Marshal.SizeOf(typeof(UnicodeString))); Marshal.StructureToPtr(unicode,unicodeBuffer,false);
187
+ var attributes=new ObjectAttributes { Length=(uint)Marshal.SizeOf(typeof(ObjectAttributes)),RootDirectory=parent.DangerousGetHandle(),
188
+ ObjectName=unicodeBuffer,Attributes=0x40|0x1000,SecurityDescriptor=pin.AddrOfPinnedObject() };
189
+ IoStatus io; SafeFileHandle created;
190
+ int status=NtCreateFile(out created,0x00130180,ref attributes,out io,IntPtr.Zero,0,7,2,0x00200021,IntPtr.Zero,0);
191
+ if(status<0) { if(created!=null) created.Dispose(); throw CreateFailure(RtlNtStatusToDosError(status)); }
192
+ return created;
193
+ } finally { if(unicodeBuffer!=IntPtr.Zero) Marshal.FreeHGlobal(unicodeBuffer); Marshal.FreeHGlobal(nameBuffer); pin.Free(); }
194
+ }
195
+ static void RequirePrivate(Dictionary<string,object> facts, bool requireProtected=true, bool requirePrivateChildren=false, bool allowAdministratorsOwner=false) {
196
+ string current=(string)facts["currentUserSid"], owner=(string)facts["ownerSid"];
197
+ Require((bool)facts["isLocal"] && (bool)facts["daclPresent"] && (!requireProtected || (bool)facts["daclProtected"]) &&
198
+ (bool)facts["aceListComplete"] && (owner==current || (allowAdministratorsOwner && owner=="s-1-5-32-544")),
199
+ "EACCES","private object security was not enforced");
200
+ foreach(Dictionary<string,object> ace in (object[])facts["aces"]) {
201
+ var flags=(Dictionary<string,object>)ace["flags"];
202
+ string sid=(string)ace["sid"];
203
+ if((string)ace["aceType"]=="deny" || sid==current || sid=="s-1-5-18" || sid=="s-1-5-32-544") continue;
204
+ if((bool)flags["inheritOnly"] && (!requirePrivateChildren || (!(bool)flags["objectInherit"] && !(bool)flags["containerInherit"]))) continue;
205
+ uint mask=(uint)ace["mask"];
206
+ Require((mask&0xd00d01dfu)==0,"EACCES","private object permits untrusted access");
207
+ }
208
+ }
209
+ static void SplitParent(string path, out string parentPath, out string name) {
210
+ // JavaScript has already admitted and resolved this spelling. Keep extended
211
+ // Win32 paths out of .NET Framework's separate pathname normalization.
212
+ int separator=path.LastIndexOf('\\');
213
+ Require(separator>0 && separator<path.Length-1,"EINVAL","private object must name a child");
214
+ parentPath=path.Substring(0,separator); name=path.Substring(separator+1);
215
+ if(parentPath.EndsWith(":")) parentPath+="\\";
216
+ }
217
+ static string ExpectedIdentity(string variable, bool required=true) {
218
+ string identity=Environment.GetEnvironmentVariable(variable) ?? "";
219
+ Require((!required && identity.Length==0) || System.Text.RegularExpressions.Regex.IsMatch(identity,@"\A[0-9a-f]{16}:[0-9a-f]{32}\z"),
220
+ "EINVAL","complete expected Windows identity is required");
221
+ return identity;
222
+ }
223
+ static void CheckParent(SafeFileHandle parent, string path, string identity) {
224
+ Require(DirectoryIdentity(parent,true)==identity,"EIO","retained private parent changed");
225
+ using(var named=Open(path,0x80,true)) Require(DirectoryIdentity(named,true)==identity,"EIO","private parent pathname changed");
226
+ }
227
+ static object InspectDirectory(string path) {
228
+ bool requirePrivate=Environment.GetEnvironmentVariable("FS_SAFE_WINDOWS_SECURITY_REQUIRE_PRIVATE")=="1";
229
+ using(var handle=Open(path,requirePrivate ? 0x00020080u : 0x80u,true)) {
230
+ string identity=DirectoryIdentity(handle,true);
231
+ if(requirePrivate) RequirePrivate(Security(handle),true,true);
232
+ return Row("identity",identity);
233
+ }
234
+ }
235
+ static void ProtectFileSecurity(SafeFileHandle handle) {
236
+ byte[] security=PrivateSecurity(false);
237
+ var pin=GCHandle.Alloc(security,GCHandleType.Pinned);
238
+ try {
239
+ bool present,defaulted; IntPtr dacl,owner;
240
+ if(!GetSecurityDescriptorDacl(pin.AddrOfPinnedObject(),out present,out dacl,out defaulted)) {
241
+ throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect private file DACL");
242
+ }
243
+ Require(present && dacl!=IntPtr.Zero,"EIO","private file DACL is incomplete");
244
+ if(!GetSecurityDescriptorOwner(pin.AddrOfPinnedObject(),out owner,out defaulted)) {
245
+ throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect private file owner");
246
+ }
247
+ Require(owner!=IntPtr.Zero,"EIO","private file owner is incomplete");
248
+ uint error=SetSecurityInfo(handle,1,0x80000005u,owner,IntPtr.Zero,dacl,IntPtr.Zero);
249
+ if(error!=0) throw OsFailure(error,"protect private file owner and DACL");
250
+ } finally { pin.Free(); }
251
+ }
252
+ static void CheckFileAssociation(SafeFileHandle held, string path, string identity, uint links, SafeFileHandle parent, string parentPath, string parentIdentity) {
253
+ Require(FileIdentity(held,links)==identity,"EIO","retained private file changed");
254
+ using(var named=Open(path,0x80,true)) Require(FileIdentity(named,links)==identity,"EIO","private file pathname changed");
255
+ CheckParent(parent,parentPath,parentIdentity);
256
+ }
257
+ static object PrivateFile(string path, bool protect) {
258
+ string expectedParent=ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_PARENT_IDENTITY");
259
+ string expectedFile=protect ? "" : ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_FILE_IDENTITY");
260
+ uint links=1;
261
+ if(!protect) Require(uint.TryParse(Environment.GetEnvironmentVariable("FS_SAFE_WINDOWS_SECURITY_EXPECTED_LINKS"),
262
+ System.Globalization.NumberStyles.None,System.Globalization.CultureInfo.InvariantCulture,out links) && links>0,
263
+ "EINVAL","private file expected link count is invalid");
264
+ string parentPath,name; SplitParent(path,out parentPath,out name);
265
+ using(var held=new SafeFileHandle(GetStdHandle(-10),false)) {
266
+ Require(!held.IsInvalid,"EBADF","inherited file handle is unavailable");
267
+ string identity=FileIdentity(held,links);
268
+ if(!protect) Require(identity==expectedFile,"EIO","private file identity changed");
269
+ // Windows can use Administrators as the creating token's default owner.
270
+ // The inherited ACL must already be private: later changes cannot revoke
271
+ // an earlier reader handle.
272
+ RequirePrivate(Security(held),requireProtected:!protect,allowAdministratorsOwner:protect);
273
+ using(var parent=Open(parentPath,0x80,true)) {
274
+ CheckFileAssociation(held,path,identity,links,parent,parentPath,expectedParent);
275
+ if(protect) using(var writable=Open(path,0x000e0080,true)) {
276
+ Require(FileIdentity(writable,links)==identity,"EIO","private file changed before owner and DACL protection");
277
+ RequirePrivate(Security(writable),requireProtected:false,allowAdministratorsOwner:true);
278
+ CheckParent(parent,parentPath,expectedParent);
279
+ ProtectFileSecurity(writable);
280
+ RequirePrivate(Security(writable));
281
+ }
282
+ RequirePrivate(Security(held));
283
+ CheckFileAssociation(held,path,identity,links,parent,parentPath,expectedParent);
284
+ return Row("identity",identity);
285
+ }
286
+ }
287
+ }
288
+ static object CreatePrivate(string path) {
289
+ string parentPath,name; SplitParent(path,out parentPath,out name);
290
+ string expectedParent=ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_PARENT_IDENTITY",false);
291
+ using(var parent=Open(parentPath,0xa4,true)) {
292
+ string parentId=DirectoryIdentity(parent,true);
293
+ if(expectedParent.Length!=0) Require(parentId==expectedParent,"EIO","private parent changed before creation");
294
+ using(var created=CreateRelative(parent,name)) {
295
+ try {
296
+ string createdId=DirectoryIdentity(created);
297
+ RequirePrivate(Security(created));
298
+ Require(DirectoryIdentity(parent)==parentId,"EIO","retained private parent changed");
299
+ using(var namedParent=Open(parentPath,0x80,true)) Require(DirectoryIdentity(namedParent,true)==parentId,"EIO","private parent pathname changed");
300
+ using(var named=Open(path,0x80,true)) {
301
+ Require(DirectoryIdentity(named,true)==createdId,"EIO","private directory pathname changed");
302
+ }
303
+ return Row("created",true,"identity",createdId);
304
+ } catch(Exception primary) {
305
+ uint flags=0x13;
306
+ if(!SetFileInformationByHandle(created,21,ref flags,4)) {
307
+ var failure=primary as Failure;
308
+ throw new Failure(failure==null ? "EIO" : failure.Code,
309
+ (failure==null ? "private directory validation failed" : failure.Message)+
310
+ "; owned-handle cleanup failed with Windows error "+Marshal.GetLastWin32Error());
311
+ }
312
+ throw;
313
+ }
314
+ }
315
+ }
316
+ }
317
+ public static object Execute(string operation,string path) {
318
+ try {
319
+ object result;
320
+ if(operation=="create") result=CreatePrivate(path);
321
+ else if(operation=="directory") result=InspectDirectory(path);
322
+ else if(operation=="protect-file" || operation=="verify-file") result=PrivateFile(path,operation=="protect-file");
323
+ else if(operation=="descriptor") {
324
+ using(var handle=new SafeFileHandle(GetStdHandle(-10),false)) {
325
+ Require(!handle.IsInvalid,"EBADF","inherited file handle is unavailable");
326
+ result=Row("identity",Identity(handle),"security",Security(handle));
327
+ }
328
+ } else if(operation=="path") {
329
+ // Raw reporting retains ACL facts when locality is unknown; admission remains strict.
330
+ using(var handle=Open(path,0x00020080,false)) result=Security(handle,false);
331
+ } else throw new Failure("EINVAL","unknown Windows security operation");
332
+ return Row("ok",true,"result",result);
333
+ } catch(Failure error) { return Row("ok",false,"code",error.Code,"message",error.Message); }
334
+ catch(Exception) { return Row("ok",false,"code","EIO","message","Windows security descriptor processing failed"); }
335
+ }
336
+ }
@@ -0,0 +1,15 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [ValidateSet('path', 'descriptor', 'create', 'directory', 'protect-file', 'verify-file')]
4
+ [string] $Operation
5
+ )
6
+
7
+ $ErrorActionPreference = 'Stop'
8
+ $ProgressPreference = 'SilentlyContinue'
9
+ # Avoid scanning unrelated installed modules before loading system cmdlets.
10
+ $env:PSModulePath = [IO.Path]::Combine($PSHOME, 'Modules')
11
+ [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
12
+
13
+ Microsoft.PowerShell.Utility\Add-Type -LiteralPath ([IO.Path]::Combine($PSScriptRoot, 'windows-security-bridge.cs'))
14
+ $targetPath = [Environment]::GetEnvironmentVariable('FS_SAFE_WINDOWS_SECURITY_PATH')
15
+ [FsSafeWindowsBridge]::Execute($Operation, $targetPath) | Microsoft.PowerShell.Utility\ConvertTo-Json -Depth 8 -Compress
@@ -0,0 +1,26 @@
1
+ import type { NativeWindowsDescriptorSecurityFacts, NativeWindowsSecurityFacts } from "./native-binding.js";
2
+ /** Cleanup must preserve stages still reachable by an unsettled command. */
3
+ export declare function hasUnsettledWindowsSecurityCommand(error: unknown): boolean;
4
+ export declare function readWindowsSecurityFactsCommand(targetPath: string): NativeWindowsSecurityFacts;
5
+ export declare function inspectWindowsDescriptorCommand(fd: number): Promise<NativeWindowsDescriptorSecurityFacts>;
6
+ export declare function createPrivateWindowsDirectoryCommand(targetPath: string, expectedParentIdentity?: string): Promise<{
7
+ identity: string;
8
+ }>;
9
+ export declare function createPrivateWindowsDirectoryCommandSync(targetPath: string, expectedParentIdentity?: string): {
10
+ identity: string;
11
+ };
12
+ export declare function inspectWindowsDirectoryCommandSync(targetPath: string, requirePrivate: boolean): {
13
+ identity: string;
14
+ };
15
+ export declare function inspectWindowsDirectoryCommand(targetPath: string, requirePrivate: boolean): Promise<{
16
+ identity: string;
17
+ }>;
18
+ export declare function protectPrivateWindowsFileCommandSync(fd: number, targetPath: string, expectedParentIdentity: string): {
19
+ identity: string;
20
+ };
21
+ export declare function protectPrivateWindowsFileCommand(fd: number, targetPath: string, expectedParentIdentity: string): Promise<{
22
+ identity: string;
23
+ }>;
24
+ export declare function verifyPrivateWindowsFileCommandSync(fd: number, targetPath: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks?: number): void;
25
+ export declare function verifyPrivateWindowsFileCommand(fd: number, targetPath: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks?: number): Promise<void>;
26
+ //# sourceMappingURL=windows-security-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows-security-command.d.ts","sourceRoot":"","sources":["../src/windows-security-command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oCAAoC,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AA2J5G,4EAA4E;AAC5E,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAqB1E;AAwHD,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,MAAM,GAAG,0BAA0B,CAE9F;AAED,wBAAsB,+BAA+B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oCAAoC,CAAC,CAM/G;AAED,wBAAsB,oCAAoC,CAAC,UAAU,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAE7I;AAED,wBAAgB,wCAAwC,CAAC,UAAU,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAElI;AAYD,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEpH;AAED,wBAAsB,8BAA8B,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAE/H;AAED,wBAAgB,oCAAoC,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAGzI;AAED,wBAAsB,gCAAgC,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAGpJ;AAED,wBAAgB,mCAAmC,CACjD,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,SAAI,GAC9G,IAAI,CAMN;AAED,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,SAAI,GAC9G,OAAO,CAAC,IAAI,CAAC,CAMf"}