@openclaw/fs-safe 0.18.0 → 0.18.2

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 (183) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/advanced.d.ts +4 -2
  3. package/dist/advanced.js +1 -1
  4. package/dist/archive-durability.d.ts +1 -1
  5. package/dist/archive-merge.js +0 -1
  6. package/dist/archive-parser.wasm +0 -0
  7. package/dist/archive-plan.d.ts +64 -3
  8. package/dist/archive-plan.js +55 -1
  9. package/dist/archive-staging.d.ts +1 -2
  10. package/dist/archive-staging.js +0 -2
  11. package/dist/archive-tar-wasm.d.ts +1 -1
  12. package/dist/archive-zip-directory.d.ts +1 -1
  13. package/dist/archive-zip-entry.d.ts +1 -1
  14. package/dist/archive-zip-entry.js +1 -1
  15. package/dist/archive-zip-preflight.d.ts +0 -2
  16. package/dist/archive-zip-preflight.js +0 -1
  17. package/dist/archive.d.ts +14 -8
  18. package/dist/archive.js +127 -12
  19. package/dist/atomic.d.ts +3 -1
  20. package/dist/copy-publication.d.ts +1 -1
  21. package/dist/create.d.ts +16 -2
  22. package/dist/create.js +388 -2
  23. package/dist/creation-boundary.d.ts +60 -0
  24. package/dist/creation-boundary.js +371 -0
  25. package/dist/directory-guard.d.ts +0 -1
  26. package/dist/directory-guard.js +0 -3
  27. package/dist/directory-mode-node.d.ts +20 -2
  28. package/dist/directory-mode-node.js +89 -3
  29. package/dist/durability.d.ts +2 -1
  30. package/dist/file-lock-sync-acquisition.js +3 -3
  31. package/dist/file-lock-sync-root-acquire.js +2 -6
  32. package/dist/file-lock-sync-root-options.d.ts +2 -10
  33. package/dist/file-lock-sync-root.d.ts +2 -1
  34. package/dist/file-lock-sync-stale-admission.js +3 -10
  35. package/dist/file-lock-sync.d.ts +4 -21
  36. package/dist/file-store-boundary.d.ts +7 -0
  37. package/dist/file-store-boundary.js +58 -1
  38. package/dist/file-store-sync-write.js +2 -3
  39. package/dist/file-store.js +1 -6
  40. package/dist/guarded-mkdir.d.ts +1 -1
  41. package/dist/index.d.ts +9 -6
  42. package/dist/json-durable-queue-ownership.d.ts +1 -5
  43. package/dist/json-durable-queue-read.js +7 -3
  44. package/dist/json-durable-queue.d.ts +0 -1
  45. package/dist/json-durable-queue.js +1 -3
  46. package/dist/json-store.js +1 -2
  47. package/dist/json.js +1 -2
  48. package/dist/move-path.js +1 -2
  49. package/dist/native-operations.js +0 -1
  50. package/dist/native-pinned-write-windows.d.ts +1 -1
  51. package/dist/native-pinned-write-windows.js +1 -1
  52. package/dist/native-pinned-write.d.ts +1 -1
  53. package/dist/native-pinned-write.js +3 -6
  54. package/dist/native-policy-parent-windows.d.ts +1 -1
  55. package/dist/native-staged-file.d.ts +2 -2
  56. package/dist/native-staged-file.js +1 -2
  57. package/dist/permission-exec.d.ts +6 -0
  58. package/dist/permissions-public.d.ts +2 -1
  59. package/dist/permissions-windows.d.ts +3 -6
  60. package/dist/permissions.d.ts +3 -10
  61. package/dist/permissions.js +0 -1
  62. package/dist/pinned-mutation-admission.d.ts +1 -1
  63. package/dist/pinned-open.d.ts +1 -0
  64. package/dist/pinned-open.js +7 -2
  65. package/dist/pinned-operation.d.ts +1 -1
  66. package/dist/pinned-operation.js +3 -16
  67. package/dist/pinned-write-mode.js +1 -1
  68. package/dist/pinned-write-staged.js +1 -2
  69. package/dist/pinned-write.d.ts +0 -1
  70. package/dist/pinned-write.js +3 -5
  71. package/dist/private-producer-handoff-sync.d.ts +2 -12
  72. package/dist/private-producer-handoff-sync.js +1 -1
  73. package/dist/private-producer-handoff.d.ts +3 -3
  74. package/dist/private-temp-workspace.js +1 -2
  75. package/dist/publish-copy-stage.d.ts +1 -1
  76. package/dist/publish-file.d.ts +0 -1
  77. package/dist/publish-file.js +1 -2
  78. package/dist/replace-directory.js +6 -19
  79. package/dist/replace-file-copy-fallback.d.ts +1 -8
  80. package/dist/replace-file-descriptor.d.ts +3 -11
  81. package/dist/replace-file-descriptor.js +1 -1
  82. package/dist/replace-file-rename-policy.d.ts +1 -2
  83. package/dist/replace-file.d.ts +3 -3
  84. package/dist/replace-file.js +1 -1
  85. package/dist/retained-directory-replacement.d.ts +1 -0
  86. package/dist/retained-directory-replacement.js +1 -1
  87. package/dist/root-context.js +3 -7
  88. package/dist/root-create-input.d.ts +1 -1
  89. package/dist/root-directory-creation.d.ts +1 -1
  90. package/dist/root-directory-creation.js +1 -1
  91. package/dist/root-directory-list.js +3 -7
  92. package/dist/root-entries.js +2 -2
  93. package/dist/root-errors.d.ts +4 -0
  94. package/dist/root-errors.js +5 -3
  95. package/dist/root-file-final-admission.d.ts +1 -1
  96. package/dist/root-file-final-admission.js +1 -6
  97. package/dist/root-file.js +2 -5
  98. package/dist/root-impl.d.ts +0 -5
  99. package/dist/root-impl.js +22 -21
  100. package/dist/root-move-noreplace.js +4 -10
  101. package/dist/root-observed-path.js +2 -4
  102. package/dist/root-options.d.ts +1 -1
  103. package/dist/root-path-existing.js +2 -3
  104. package/dist/root-path-stat.js +2 -4
  105. package/dist/root-public-types.d.ts +11 -0
  106. package/dist/root-remove-identity.js +2 -4
  107. package/dist/root-remove.js +2 -4
  108. package/dist/root-write-admission.d.ts +1 -1
  109. package/dist/root-write-admission.js +3 -7
  110. package/dist/root-write-complete-parent.js +2 -3
  111. package/dist/root-write-verification.d.ts +1 -1
  112. package/dist/root.d.ts +2 -6
  113. package/dist/secret-file.js +11 -17
  114. package/dist/secret-read-policy.js +1 -1
  115. package/dist/secure-file-windows.js +4 -9
  116. package/dist/secure-temp-dir.js +2 -7
  117. package/dist/secure-temp-repair.js +5 -1
  118. package/dist/sidecar-lock-acquire.js +8 -11
  119. package/dist/sidecar-lock-policy.d.ts +1 -0
  120. package/dist/sidecar-lock-policy.js +7 -0
  121. package/dist/sidecar-lock-reclaim.js +5 -6
  122. package/dist/sidecar-lock-stale-admission.js +2 -6
  123. package/dist/sidecar-lock-types.d.ts +22 -16
  124. package/dist/stat-observation.js +3 -10
  125. package/dist/strict-file-identity.d.ts +2 -0
  126. package/dist/strict-file-identity.js +6 -6
  127. package/dist/temp-target.js +3 -8
  128. package/dist/temp-workspace-admission.js +7 -15
  129. package/dist/temp-workspace-child-admission.d.ts +16 -2
  130. package/dist/temp-workspace-child-admission.js +65 -3
  131. package/dist/temp-workspace-descriptor.d.ts +1 -1
  132. package/dist/temp-workspace-descriptor.js +2 -4
  133. package/dist/text-atomic.js +1 -1
  134. package/dist/windows-owner.d.ts +3 -6
  135. package/dist/windows-owner.js +1 -1
  136. package/dist/windows-path-alias.d.ts +1 -0
  137. package/dist/windows-path-alias.js +5 -0
  138. package/dist/windows-security-bridge.cs +0 -2
  139. package/docs/advanced.md +3 -1
  140. package/docs/secret-file.md +13 -0
  141. package/docs/sidecar-lock.md +6 -0
  142. package/docs/store.md +6 -0
  143. package/docs/temp.md +8 -1
  144. package/docs/writing.md +5 -0
  145. package/package.json +8 -8
  146. package/dist/archive-native.d.ts +0 -10
  147. package/dist/archive-native.js +0 -73
  148. package/dist/archive-options.d.ts +0 -30
  149. package/dist/archive-policy.d.ts +0 -21
  150. package/dist/archive-policy.js +0 -38
  151. package/dist/archive-tar-inspect.d.ts +0 -6
  152. package/dist/archive-tar-inspect.js +0 -62
  153. package/dist/archive-tar.d.ts +0 -18
  154. package/dist/archive-tar.js +0 -21
  155. package/dist/create-directory.d.ts +0 -18
  156. package/dist/create-directory.js +0 -111
  157. package/dist/create-file-async.d.ts +0 -6
  158. package/dist/create-file-async.js +0 -121
  159. package/dist/create-file.d.ts +0 -7
  160. package/dist/create-file.js +0 -179
  161. package/dist/creation-darwin.d.ts +0 -5
  162. package/dist/creation-darwin.js +0 -70
  163. package/dist/creation-file-state.d.ts +0 -18
  164. package/dist/creation-file-state.js +0 -118
  165. package/dist/creation-path.d.ts +0 -22
  166. package/dist/creation-path.js +0 -83
  167. package/dist/creation-permissions.d.ts +0 -18
  168. package/dist/creation-permissions.js +0 -115
  169. package/dist/directory-mode-owner.d.ts +0 -20
  170. package/dist/directory-mode-owner.js +0 -78
  171. package/dist/file-store-copy-source.d.ts +0 -4
  172. package/dist/file-store-copy-source.js +0 -31
  173. package/dist/file-store-limit.d.ts +0 -1
  174. package/dist/file-store-limit.js +0 -7
  175. package/dist/file-store-path.d.ts +0 -2
  176. package/dist/file-store-path.js +0 -30
  177. package/dist/standalone-publication-path.d.ts +0 -1
  178. package/dist/standalone-publication-path.js +0 -6
  179. package/dist/temp-workspace-identity.d.ts +0 -14
  180. package/dist/temp-workspace-identity.js +0 -41
  181. package/dist/temp-workspace-permissions.d.ts +0 -3
  182. package/dist/temp-workspace-permissions.js +0 -32
  183. /package/dist/{archive-options.js → root-public-types.js} +0 -0
@@ -1,38 +0,0 @@
1
- import { ArchiveSecurityError } from "./archive-errors.js";
2
- import { formatErrorDetail } from "./error-detail.js";
3
- export function archiveEntryKindFromTarType(type) {
4
- if (type === "Directory" || type === "GNUDumpDir")
5
- return "directory";
6
- if (type === "File" || type === "OldFile" || type === "ContiguousFile")
7
- return "file";
8
- if (type === "SymbolicLink" || type === "Link")
9
- return "symlink";
10
- return "other";
11
- }
12
- export function resolveArchiveEntryMode(params) {
13
- const archivedMode = (params.archivedMode ?? 0) & 0o777;
14
- if (params.policy === "preserve") {
15
- return params.archivedMode == null ? (params.kind === "directory" ? 0o755 : 0o644) : archivedMode;
16
- }
17
- if (params.kind === "directory") {
18
- return 0o755;
19
- }
20
- return archivedMode & 0o100 ? 0o755 : 0o644;
21
- }
22
- export function resolveArchiveFilteredEntryPolicy(value) {
23
- if (value === undefined || value === "reject-archive")
24
- return "reject-archive";
25
- if (value === "skip-entry")
26
- return "skip-entry";
27
- throw new RangeError('archive onFiltered must be "reject-archive" or "skip-entry"');
28
- }
29
- export function shouldExtractArchiveEntry(params) {
30
- const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
31
- if (!params.filter || params.filter(params.entry) === "extract") {
32
- return true;
33
- }
34
- if (onFiltered === "reject-archive") {
35
- throw new ArchiveSecurityError("entry-filtered", `archive entry rejected by filter: ${formatErrorDetail(params.entry.path)}`);
36
- }
37
- return false;
38
- }
@@ -1,6 +0,0 @@
1
- import type { ExtractArchiveOptions } from "./archive-options.js";
2
- import { type ArchivePlanEntry } from "./archive-plan.js";
3
- export type InspectTarArchiveOptions = Pick<ExtractArchiveOptions, "archivePath" | "timeoutMs" | "limits" | "entryFilter" | "onFiltered">;
4
- export type InspectedTarEntry = Readonly<Pick<ArchivePlanEntry, "path" | "kind" | "size">>;
5
- /** Complete TAR/gzip admission and zero-strip extraction policy, without output writes. */
6
- export declare function inspectTarArchive(params: InspectTarArchiveOptions): Promise<readonly InspectedTarEntry[]>;
@@ -1,62 +0,0 @@
1
- import { withExtractionDeadline } from "./archive-deadline.js";
2
- import { validateArchiveEntryPath } from "./archive-entry.js";
3
- import { stageArchiveFileForExtraction } from "./archive-input.js";
4
- import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
5
- import { throwMappedNativeArchiveError } from "./archive-native.js";
6
- import { createArchiveEntryPlanner } from "./archive-plan.js";
7
- import { resolveArchiveFilteredEntryPolicy } from "./archive-policy.js";
8
- import { createTarEntryPlanner } from "./archive-tar.js";
9
- import { inspectTar } from "./archive-tar-stream.js";
10
- import { getNativeBinding } from "./native.js";
11
- import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
12
- /** Complete TAR/gzip admission and zero-strip extraction policy, without output writes. */
13
- export async function inspectTarArchive(params) {
14
- const archivePath = params.archivePath;
15
- const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
16
- const limits = resolveExtractLimits(params.limits);
17
- const tarLimits = resolveTarMeterLimits(limits);
18
- const native = getNativeBinding();
19
- assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
20
- return await withExtractionDeadline(params.timeoutMs, "inspect tar", async (deadline) => {
21
- const staged = await stageArchiveFileForExtraction({ archivePath, limits, deadline });
22
- try {
23
- // Staging closes descriptors asynchronously; expiry there must not start a decoder.
24
- deadline.check();
25
- const entries = [];
26
- const append = (entry) => {
27
- if (entry)
28
- entries.push(Object.freeze({ path: entry.path, kind: entry.kind, size: entry.size }));
29
- };
30
- const policy = { limits, entryFilter: params.entryFilter, onFiltered };
31
- if (native) {
32
- const manifest = await native.inspectArchiveNative(staged.path, "tar", tarLimits, deadline.signal)
33
- .catch(throwMappedNativeArchiveError);
34
- deadline.check();
35
- // Match extraction's whole-manifest validation before caller policy runs.
36
- for (const entry of manifest)
37
- validateArchiveEntryPath(entry.path);
38
- const planEntry = createArchiveEntryPlanner(policy, "tar");
39
- for (const entry of manifest) {
40
- deadline.check();
41
- append(planEntry(entry));
42
- }
43
- }
44
- else {
45
- const manifest = [];
46
- await inspectTar({ archivePath: staged.path, limits: tarLimits, signal: deadline.signal,
47
- onMember: (entry) => { manifest.push(entry); } });
48
- const planEntry = createTarEntryPlanner(policy);
49
- for (const entry of manifest) {
50
- deadline.check();
51
- append(planEntry(entry));
52
- }
53
- }
54
- deadline.check();
55
- // This is bounded evidence about the staged bytes, not a reusable write plan.
56
- return Object.freeze(entries);
57
- }
58
- finally {
59
- await staged.cleanup();
60
- }
61
- });
62
- }
@@ -1,18 +0,0 @@
1
- import type { ArchiveExtractLimits } from "./archive-limits.js";
2
- import { type ArchivePlanEntry, type ArchivePlanOptions } from "./archive-plan.js";
3
- import { type ArchiveEntryFilter, type ArchiveFilteredEntryPolicy } from "./archive-policy.js";
4
- export type TarEntryInfo = {
5
- path: string;
6
- type: string;
7
- size: number;
8
- mode?: number;
9
- };
10
- export declare function createTarEntryPlanner(params: ArchivePlanOptions): (entry: TarEntryInfo) => ArchivePlanEntry | null;
11
- export declare function createTarEntryPreflightChecker(params: {
12
- rootDir: string;
13
- stripComponents?: number;
14
- limits?: ArchiveExtractLimits;
15
- escapeLabel?: string;
16
- entryFilter?: ArchiveEntryFilter;
17
- onFiltered?: ArchiveFilteredEntryPolicy;
18
- }): (entry: TarEntryInfo) => boolean;
@@ -1,21 +0,0 @@
1
- import { createArchiveEntryPlanner, } from "./archive-plan.js";
2
- import { archiveEntryKindFromTarType, } from "./archive-policy.js";
3
- const BLOCKED_TAR_ENTRY_TYPES = new Set([
4
- "BlockDevice",
5
- "CharacterDevice",
6
- "FIFO",
7
- "Socket",
8
- ]);
9
- export function createTarEntryPlanner(params) {
10
- // Preserve inherited/getter-backed public options and per-entry policy reads.
11
- const plan = createArchiveEntryPlanner(params, "tar");
12
- return (entry) => {
13
- const kind = BLOCKED_TAR_ENTRY_TYPES.has(entry.type) ? "blocked" : archiveEntryKindFromTarType(entry.type);
14
- // The public checker accepts structural entry objects, including class getters.
15
- return plan({ path: entry.path, kind, size: entry.size, mode: entry.mode });
16
- };
17
- }
18
- export function createTarEntryPreflightChecker(params) {
19
- const plan = createTarEntryPlanner(params);
20
- return (entry) => plan(entry) !== null;
21
- }
@@ -1,18 +0,0 @@
1
- import { type BigIntStats } from "node:fs";
2
- import { type CreationPermissions } from "./creation-permissions.js";
3
- import { type CreationParentIdentity } from "./creation-path.js";
4
- export type CreateDirectoryOptions = CreationPermissions & {
5
- assertBeforeMutation?: () => void;
6
- };
7
- export type CreationAdmission = {
8
- expectedParentIdentity?: CreationParentIdentity;
9
- };
10
- export type CreationDirectoryReceipt = {
11
- stat: BigIntStats;
12
- windowsIdentity?: string;
13
- };
14
- export declare function createDirectory(targetPath: string, options?: CreateDirectoryOptions): Promise<void>;
15
- export declare function createDirectoryWithAdmission(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): Promise<void>;
16
- export declare function createDirectoryWithReceipt(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): Promise<CreationDirectoryReceipt>;
17
- export declare function createDirectorySync(targetPath: string, options?: CreateDirectoryOptions): void;
18
- export declare function createDirectoryWithReceiptSync(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): CreationDirectoryReceipt;
@@ -1,111 +0,0 @@
1
- import fsSync, {} from "node:fs";
2
- import fs from "node:fs/promises";
3
- import { FsSafeError } from "./errors.js";
4
- import { getNativeBinding } from "./native.js";
5
- import { getFsSafeNativeConfig } from "./native-config.js";
6
- import { assertPrivateDirectory, assertPrivateDirectorySync, inspectCreationDirectory, inspectCreationDirectorySync, resolveCreationPermissions, } from "./creation-permissions.js";
7
- import { assertBeforeCreation, creationCollision, prepareCreationPath } from "./creation-path.js";
8
- import { createPrivateWindowsDirectoryCommand, createPrivateWindowsDirectoryCommandSync, } from "./windows-security-command.js";
9
- import { warnNativeFallback } from "./native-fallback-warning.js";
10
- import { inspectDirectoryIdentitySync } from "./directory-guard.js";
11
- function directoryAdoptionFailure(error, target, windowsIdentity) {
12
- return new FsSafeError("helper-failed", "created directory admission failed", {
13
- cause: error,
14
- details: {
15
- publication: { status: "published" }, path: target, cleanup: "preserved",
16
- ...(windowsIdentity === undefined ? {} : { windowsIdentity }),
17
- },
18
- });
19
- }
20
- function privateDirectoryBackend() {
21
- const binding = getNativeBinding();
22
- if (typeof binding?.createPrivateDirectoryWithParentIdentity === "function") {
23
- return binding.createPrivateDirectoryWithParentIdentity.bind(binding);
24
- }
25
- if (getFsSafeNativeConfig().mode === "require") {
26
- throw new FsSafeError("helper-unavailable", "private directory creation requires an up-to-date native parent identity capability");
27
- }
28
- warnNativeFallback("windows-private-directory", "Private Windows directory creation uses a slower built-in system command.");
29
- return undefined;
30
- }
31
- export async function createDirectory(targetPath, options = {}) {
32
- await createDirectoryWithAdmission(targetPath, options, {});
33
- }
34
- export async function createDirectoryWithAdmission(targetPath, options = {}, admission = {}) {
35
- await createDirectoryWithReceipt(targetPath, options, admission);
36
- }
37
- export async function createDirectoryWithReceipt(targetPath, options = {}, admission = {}) {
38
- const permissions = resolveCreationPermissions(options, true);
39
- const assertion = options.assertBeforeMutation;
40
- const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
41
- const backend = permissions.private && process.platform === "win32" ? privateDirectoryBackend() : undefined;
42
- const parentIdentity = permissions.private && process.platform === "win32"
43
- ? await inspectCreationDirectory(selected.parent.dir, false) : undefined;
44
- assertBeforeCreation(selected, permissions, assertion, "directory");
45
- let created = false;
46
- let windowsIdentity;
47
- try {
48
- if (parentIdentity !== undefined) {
49
- const receipt = backend
50
- ? backend(selected.target, parentIdentity)
51
- : await createPrivateWindowsDirectoryCommand(selected.target, parentIdentity);
52
- created = true;
53
- windowsIdentity = receipt.identity;
54
- if ((await inspectCreationDirectory(selected.target, true)) !== receipt.identity) {
55
- throw new FsSafeError("path-mismatch", "created private directory changed before adoption");
56
- }
57
- }
58
- else {
59
- await fs.mkdir(selected.target, { mode: permissions.mode });
60
- created = true;
61
- if (permissions.private)
62
- await assertPrivateDirectory(selected.target);
63
- }
64
- selected.assertParent();
65
- return { stat: inspectDirectoryIdentitySync(selected.target), windowsIdentity };
66
- }
67
- catch (error) {
68
- if (created)
69
- throw directoryAdoptionFailure(error, selected.target, windowsIdentity);
70
- throw creationCollision(error);
71
- }
72
- }
73
- export function createDirectorySync(targetPath, options = {}) {
74
- createDirectoryWithReceiptSync(targetPath, options, {});
75
- }
76
- export function createDirectoryWithReceiptSync(targetPath, options = {}, admission = {}) {
77
- const permissions = resolveCreationPermissions(options, true);
78
- const assertion = options.assertBeforeMutation;
79
- const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
80
- const backend = permissions.private && process.platform === "win32" ? privateDirectoryBackend() : undefined;
81
- const parentIdentity = permissions.private && process.platform === "win32"
82
- ? inspectCreationDirectorySync(selected.parent.dir, false) : undefined;
83
- assertBeforeCreation(selected, permissions, assertion, "directory");
84
- let created = false;
85
- let windowsIdentity;
86
- try {
87
- if (parentIdentity !== undefined) {
88
- const receipt = backend
89
- ? backend(selected.target, parentIdentity)
90
- : createPrivateWindowsDirectoryCommandSync(selected.target, parentIdentity);
91
- created = true;
92
- windowsIdentity = receipt.identity;
93
- if (inspectCreationDirectorySync(selected.target, true) !== receipt.identity) {
94
- throw new FsSafeError("path-mismatch", "created private directory changed before adoption");
95
- }
96
- }
97
- else {
98
- fsSync.mkdirSync(selected.target, { mode: permissions.mode });
99
- created = true;
100
- if (permissions.private)
101
- assertPrivateDirectorySync(selected.target);
102
- }
103
- selected.assertParent();
104
- return { stat: inspectDirectoryIdentitySync(selected.target), windowsIdentity };
105
- }
106
- catch (error) {
107
- if (created)
108
- throw directoryAdoptionFailure(error, selected.target, windowsIdentity);
109
- throw creationCollision(error);
110
- }
111
- }
@@ -1,6 +0,0 @@
1
- import { type FileHandle } from "node:fs/promises";
2
- import { type CreationPath } from "./creation-path.js";
3
- export declare function createPrivateWindowsFileHandle(selected: CreationPath, options: {
4
- mode: number;
5
- assertBeforeMutation?: () => void;
6
- }): Promise<FileHandle>;
@@ -1,121 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import fsSync, {} from "node:fs";
3
- import fs, {} from "node:fs/promises";
4
- import path from "node:path";
5
- import { createDirectoryWithReceipt } from "./create-directory.js";
6
- import { assertDirectoryIdentitySync } from "./directory-guard.js";
7
- import { FsSafeError } from "./errors.js";
8
- import { assertSynchronousCallbackResult } from "./mutation-authority.js";
9
- import { assertCreationFile, creationPublicationAfterFailure, privateFileSettlementFailure, rethrowPrivateStageCreationFailure, removeRecordedCreationFile, } from "./creation-file-state.js";
10
- import { inspectCreationDirectory, protectCreatedFile, verifyCreatedFile } from "./creation-permissions.js";
11
- import { creationCollision, removeCreationDirectoryAsync } from "./creation-path.js";
12
- import { handoffCreatedFile } from "./private-producer-handoff.js";
13
- import { resolveReadOpenFlags } from "./read-open-flags.js";
14
- import { hasUnsettledWindowsSecurityCommand } from "./windows-security-command.js";
15
- export async function createPrivateWindowsFileHandle(selected, options) {
16
- const assertion = options.assertBeforeMutation;
17
- const existing = await fs.lstat(selected.target).catch((error) => {
18
- if (error.code !== "ENOENT")
19
- throw error;
20
- return undefined;
21
- });
22
- selected.assertParent();
23
- if (existing)
24
- throw new FsSafeError("already-exists", "creation target already exists");
25
- const parentIdentity = await inspectCreationDirectory(selected.parent.dir, false);
26
- selected.assertParent();
27
- const stageDirectory = path.join(selected.parent.dir, `.fs-safe-create-${randomUUID()}`);
28
- let stage;
29
- try {
30
- stage = await createDirectoryWithReceipt(stageDirectory, { private: true, assertBeforeMutation: assertion }, {
31
- expectedParentIdentity: selected.parent.stat,
32
- });
33
- }
34
- catch (error) {
35
- rethrowPrivateStageCreationFailure(error, selected.target, stageDirectory);
36
- }
37
- const stageIdentity = stage.windowsIdentity;
38
- const stagePath = path.join(stageDirectory, "file");
39
- const assertStage = () => {
40
- selected.assertParent();
41
- assertDirectoryIdentitySync(stageDirectory, stage.stat);
42
- };
43
- let file;
44
- let identity;
45
- let publication = "not-published";
46
- try {
47
- if (await inspectCreationDirectory(stageDirectory, true) !== stageIdentity) {
48
- throw new FsSafeError("path-mismatch", "private staging directory changed before file creation");
49
- }
50
- assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
51
- assertStage();
52
- file = await fs.open(stagePath, fsSync.constants.O_RDWR | fsSync.constants.O_CREAT |
53
- fsSync.constants.O_EXCL | resolveReadOpenFlags(), 0o600);
54
- assertStage();
55
- identity = assertCreationFile(file.fd, stagePath);
56
- assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
57
- assertStage();
58
- assertCreationFile(file.fd, stagePath, identity);
59
- const windowsIdentity = await protectCreatedFile(file.fd, stagePath, stageIdentity);
60
- assertStage();
61
- assertCreationFile(file.fd, stagePath, identity);
62
- const source = file;
63
- file = undefined;
64
- // The handoff consumes the source pin on both success and failure.
65
- file = await handoffCreatedFile({
66
- source, sourcePath: stagePath, targetPath: selected.target, identity,
67
- assertSourceParent: assertStage, assertTargetParent: selected.assertParent,
68
- assertBeforeMutation: assertion,
69
- verifyDescriptor: (fd, pathname, links) => verifyCreatedFile(fd, pathname, windowsIdentity, pathname === stagePath ? stageIdentity : parentIdentity, links),
70
- onPublished: () => { publication = "published"; },
71
- });
72
- assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
73
- selected.assertParent();
74
- assertCreationFile(file.fd, selected.target, identity);
75
- await verifyCreatedFile(file.fd, selected.target, windowsIdentity, parentIdentity);
76
- selected.assertParent();
77
- assertCreationFile(file.fd, selected.target, identity);
78
- await file.chmod(options.mode);
79
- await verifyCreatedFile(file.fd, selected.target, windowsIdentity, parentIdentity);
80
- selected.assertParent();
81
- assertCreationFile(file.fd, selected.target, identity);
82
- await removeCreationDirectoryAsync(stageDirectory, stage.stat, selected.assertParent);
83
- selected.assertParent();
84
- assertCreationFile(file.fd, selected.target, identity);
85
- return file;
86
- }
87
- catch (primary) {
88
- publication = creationPublicationAfterFailure(primary, publication);
89
- const preserved = publication === "indeterminate" || hasUnsettledWindowsSecurityCommand(primary);
90
- const cleanup = [];
91
- if (file) {
92
- const owned = file;
93
- file = undefined;
94
- try {
95
- await owned.close();
96
- }
97
- catch (error) {
98
- cleanup.push(error);
99
- }
100
- }
101
- if (!preserved) {
102
- if (identity) {
103
- try {
104
- await removeRecordedCreationFile(stagePath, identity, assertStage);
105
- }
106
- catch (error) {
107
- cleanup.push(error);
108
- }
109
- }
110
- try {
111
- await removeCreationDirectoryAsync(stageDirectory, stage.stat, selected.assertParent);
112
- }
113
- catch (error) {
114
- cleanup.push(error);
115
- }
116
- }
117
- if (publication === "not-published" && cleanup.length === 0 && !preserved)
118
- throw creationCollision(primary);
119
- throw privateFileSettlementFailure({ primary, cleanup, publication, path: selected.target, stageDirectory, preserved });
120
- }
121
- }
@@ -1,7 +0,0 @@
1
- import { type FileHandle } from "node:fs/promises";
2
- import { type CreateDirectoryOptions, type CreationAdmission } from "./create-directory.js";
3
- import { type OwnedFileDescriptorSync } from "./create-owned-file.js";
4
- export type CreateFileOptions = CreateDirectoryOptions;
5
- export declare function createFileSync(targetPath: string, options?: CreateFileOptions): OwnedFileDescriptorSync;
6
- /** Internal async writer; Windows preparation uses the nonblocking stage owner. */
7
- export declare function createFileHandle(targetPath: string, options?: CreateFileOptions, admission?: CreationAdmission): Promise<FileHandle>;
@@ -1,179 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import fs, {} from "node:fs";
3
- import fsAsync, {} from "node:fs/promises";
4
- import path from "node:path";
5
- import { createDirectoryWithReceiptSync } from "./create-directory.js";
6
- import { ownFileDescriptorSync } from "./create-owned-file.js";
7
- import { assertDirectoryIdentitySync } from "./directory-guard.js";
8
- import { FsSafeError } from "./errors.js";
9
- import { assertSynchronousCallbackResult } from "./mutation-authority.js";
10
- import { assertPrivateFileCreationAvailable, inspectCreationDirectorySync, protectCreatedFileSync, resolveCreationPermissions, verifyCreatedFileSync, } from "./creation-permissions.js";
11
- import { assertBeforeCreation, creationCollision, prepareCreationPath, removeCreationDirectory } from "./creation-path.js";
12
- import { handoffCreatedFileSync } from "./private-producer-handoff-sync.js";
13
- import { resolveReadOpenFlags } from "./read-open-flags.js";
14
- import { assertCreationFile, assertPrivateCreationFile, creationPublicationAfterFailure, privateFileSettlementFailure, rethrowPrivateStageCreationFailure, removeRecordedCreationFileSync, } from "./creation-file-state.js";
15
- import { createPrivateWindowsFileHandle } from "./create-file-async.js";
16
- function createPrivateWindowsFile(selected, options, mode) {
17
- const assertion = options.assertBeforeMutation;
18
- if (fs.lstatSync(selected.target, { throwIfNoEntry: false })) {
19
- throw new FsSafeError("already-exists", "creation target already exists");
20
- }
21
- const parentIdentity = inspectCreationDirectorySync(selected.parent.dir, false);
22
- selected.assertParent();
23
- const stageDirectory = path.join(selected.parent.dir, `.fs-safe-create-${randomUUID()}`);
24
- let stage;
25
- try {
26
- stage = createDirectoryWithReceiptSync(stageDirectory, { private: true, assertBeforeMutation: assertion }, {
27
- expectedParentIdentity: selected.parent.stat,
28
- });
29
- }
30
- catch (error) {
31
- rethrowPrivateStageCreationFailure(error, selected.target, stageDirectory);
32
- }
33
- const stageIdentity = stage.windowsIdentity;
34
- const stagePath = path.join(stageDirectory, "file");
35
- const assertStage = () => {
36
- selected.assertParent();
37
- assertDirectoryIdentitySync(stageDirectory, stage.stat);
38
- };
39
- let file;
40
- let identity;
41
- let windowsIdentity;
42
- let publication = "not-published";
43
- try {
44
- if (inspectCreationDirectorySync(stageDirectory, true) !== stageIdentity) {
45
- throw new FsSafeError("path-mismatch", "private staging directory changed before file creation");
46
- }
47
- assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
48
- assertStage();
49
- file = ownFileDescriptorSync(fs.openSync(stagePath, fs.constants.O_RDWR | fs.constants.O_CREAT |
50
- fs.constants.O_EXCL | resolveReadOpenFlags(), 0o600));
51
- identity = assertCreationFile(file.fd, stagePath);
52
- // The inherited ACL must already be restrictive. This freezes that policy;
53
- // the bridge refuses to repair broad access on an exposed file.
54
- assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
55
- assertStage();
56
- assertCreationFile(file.fd, stagePath, identity);
57
- windowsIdentity = protectCreatedFileSync(file.fd, stagePath, stageIdentity);
58
- assertStage();
59
- file = handoffCreatedFileSync({
60
- source: file, sourcePath: stagePath, targetPath: selected.target, identity,
61
- assertSourceParent: assertStage, assertTargetParent: selected.assertParent,
62
- assertBeforeMutation: assertion,
63
- verifyDescriptor: (fd, pathname, links) => verifyCreatedFileSync(fd, pathname, windowsIdentity, pathname === stagePath ? stageIdentity : parentIdentity, links),
64
- onPublished: () => { publication = "published"; },
65
- });
66
- // Windows may clear the read-only attribute while retiring the stage name.
67
- // Finalize it only after the published-name descriptor owns the file.
68
- fs.fchmodSync(file.fd, mode);
69
- selected.assertParent();
70
- identity = assertCreationFile(file.fd, selected.target, identity);
71
- verifyCreatedFileSync(file.fd, selected.target, windowsIdentity, parentIdentity);
72
- removeCreationDirectory(stageDirectory, stage.stat, selected.assertParent);
73
- return file;
74
- }
75
- catch (primary) {
76
- publication = creationPublicationAfterFailure(primary, publication);
77
- const cleanup = [];
78
- if (file) {
79
- try {
80
- file.close();
81
- }
82
- catch (error) {
83
- cleanup.push(error);
84
- }
85
- }
86
- if (publication !== "indeterminate") {
87
- if (identity) {
88
- try {
89
- removeRecordedCreationFileSync(stagePath, identity, assertStage);
90
- }
91
- catch (error) {
92
- cleanup.push(error);
93
- }
94
- }
95
- try {
96
- removeCreationDirectory(stageDirectory, stage.stat, selected.assertParent);
97
- }
98
- catch (error) {
99
- cleanup.push(error);
100
- }
101
- }
102
- if (publication === "not-published" && cleanup.length === 0)
103
- throw creationCollision(primary);
104
- throw privateFileSettlementFailure({ primary, cleanup, publication, path: selected.target, stageDirectory });
105
- }
106
- }
107
- function createFileCore(targetPath, options, admission) {
108
- const permissions = resolveCreationPermissions(options, false);
109
- const assertion = options.assertBeforeMutation;
110
- const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
111
- if (permissions.private && process.platform === "win32") {
112
- assertPrivateFileCreationAvailable();
113
- return createPrivateWindowsFile(selected, { ...permissions, assertBeforeMutation: assertion }, permissions.mode);
114
- }
115
- assertBeforeCreation(selected, permissions, assertion, "file");
116
- let file;
117
- try {
118
- file = ownFileDescriptorSync(fs.openSync(selected.target, fs.constants.O_RDWR | fs.constants.O_CREAT |
119
- fs.constants.O_EXCL | resolveReadOpenFlags(), permissions.mode ?? 0o666));
120
- }
121
- catch (error) {
122
- throw creationCollision(error);
123
- }
124
- try {
125
- const identity = assertCreationFile(file.fd, selected.target);
126
- selected.assertParent();
127
- if (permissions.private)
128
- assertPrivateCreationFile(identity, file.fd);
129
- return file;
130
- }
131
- catch (primary) {
132
- let failure = primary;
133
- try {
134
- file.close();
135
- }
136
- catch (closeError) {
137
- failure = new AggregateError([primary, closeError], "created file admission and close failed");
138
- }
139
- throw new FsSafeError("helper-failed", "created file admission failed", {
140
- cause: failure, details: { publication: { status: "published" }, path: selected.target, cleanup: "preserved" },
141
- });
142
- }
143
- }
144
- export function createFileSync(targetPath, options = {}) {
145
- return createFileCore(targetPath, options, {});
146
- }
147
- /** Internal async writer; Windows preparation uses the nonblocking stage owner. */
148
- export async function createFileHandle(targetPath, options = {}, admission = {}) {
149
- const permissions = resolveCreationPermissions(options, false);
150
- const assertion = options.assertBeforeMutation;
151
- if (process.platform !== "win32" || !permissions.private) {
152
- const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
153
- assertBeforeCreation(selected, permissions, assertion, "file");
154
- const handle = await fsAsync.open(selected.target, fs.constants.O_RDWR | fs.constants.O_CREAT |
155
- fs.constants.O_EXCL | resolveReadOpenFlags(), permissions.mode ?? 0o666)
156
- .catch(error => { throw creationCollision(error); });
157
- try {
158
- const identity = assertCreationFile(handle.fd, selected.target);
159
- selected.assertParent();
160
- if (permissions.private)
161
- assertPrivateCreationFile(identity, handle.fd);
162
- return handle;
163
- }
164
- catch (primary) {
165
- let failure = primary;
166
- try {
167
- await handle.close();
168
- }
169
- catch (closeError) {
170
- failure = new AggregateError([primary, closeError], "created file admission and close failed");
171
- }
172
- throw new FsSafeError("helper-failed", "created file handle admission failed", {
173
- cause: failure, details: { publication: { status: "published" }, path: selected.target, cleanup: "preserved" },
174
- });
175
- }
176
- }
177
- assertPrivateFileCreationAvailable();
178
- return await createPrivateWindowsFileHandle(prepareCreationPath(targetPath, admission.expectedParentIdentity), { mode: permissions.mode, assertBeforeMutation: assertion });
179
- }
@@ -1,5 +0,0 @@
1
- import { type BigIntStats } from "node:fs";
2
- export declare function assertDarwinPrivateCreationAvailable(): void;
3
- export declare function assertDarwinPrivateDirectoryMode(mode: number): void;
4
- export declare function assertDarwinCreationAcl(fd: number, inheritanceTarget?: "file" | "directory"): void;
5
- export declare function assertDarwinCreationDirectoryAcl(pathname: string, expected: BigIntStats, inheritanceTarget?: "file" | "directory"): void;