@openclaw/fs-safe 0.18.0 → 0.18.1
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.
- package/CHANGELOG.md +10 -0
- package/dist/advanced.d.ts +2 -1
- package/dist/archive-durability.d.ts +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-plan.d.ts +64 -3
- package/dist/archive-plan.js +55 -1
- package/dist/archive-tar-wasm.d.ts +1 -1
- package/dist/archive-zip-directory.d.ts +1 -1
- package/dist/archive-zip-entry.d.ts +1 -1
- package/dist/archive-zip-entry.js +1 -1
- package/dist/archive.d.ts +8 -5
- package/dist/archive.js +122 -9
- package/dist/atomic.d.ts +3 -1
- package/dist/copy-publication.d.ts +1 -1
- package/dist/create.d.ts +16 -2
- package/dist/create.js +388 -2
- package/dist/creation-boundary.d.ts +60 -0
- package/dist/creation-boundary.js +371 -0
- package/dist/directory-mode-node.js +12 -2
- package/dist/durability.d.ts +2 -1
- package/dist/file-lock-sync-acquisition.js +2 -2
- package/dist/file-lock-sync-root-acquire.js +2 -6
- package/dist/file-lock-sync-root.d.ts +2 -1
- package/dist/file-lock-sync-stale-admission.js +3 -10
- package/dist/file-lock-sync.d.ts +4 -21
- package/dist/file-store-boundary.d.ts +7 -0
- package/dist/file-store-boundary.js +58 -1
- package/dist/file-store-sync-write.js +2 -3
- package/dist/file-store.js +1 -6
- package/dist/guarded-mkdir.d.ts +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/json-durable-queue-read.js +7 -3
- package/dist/json-durable-queue.js +1 -2
- package/dist/json-store.js +1 -2
- package/dist/json.js +1 -2
- package/dist/move-path.js +1 -2
- package/dist/native-operations.js +0 -1
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.js +1 -1
- package/dist/native-pinned-write.d.ts +1 -1
- package/dist/native-pinned-write.js +3 -6
- package/dist/native-policy-parent-windows.d.ts +1 -1
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.js +1 -2
- package/dist/pinned-mutation-admission.d.ts +1 -1
- package/dist/pinned-open.js +6 -1
- package/dist/pinned-operation.d.ts +1 -1
- package/dist/pinned-operation.js +3 -16
- package/dist/pinned-write-mode.js +1 -1
- package/dist/pinned-write-staged.js +1 -2
- package/dist/pinned-write.d.ts +0 -1
- package/dist/pinned-write.js +3 -5
- package/dist/private-producer-handoff-sync.d.ts +2 -12
- package/dist/private-producer-handoff-sync.js +1 -1
- package/dist/private-producer-handoff.d.ts +3 -3
- package/dist/private-temp-workspace.js +1 -2
- package/dist/publish-copy-stage.d.ts +1 -1
- package/dist/publish-file.d.ts +0 -1
- package/dist/publish-file.js +1 -2
- package/dist/replace-directory.js +1 -2
- package/dist/replace-file-rename-policy.d.ts +1 -2
- package/dist/replace-file.d.ts +3 -3
- package/dist/replace-file.js +1 -1
- package/dist/root-context.js +3 -7
- package/dist/root-create-input.d.ts +1 -1
- package/dist/root-directory-creation.d.ts +1 -1
- package/dist/root-directory-creation.js +1 -1
- package/dist/root-directory-list.js +3 -7
- package/dist/root-entries.js +2 -2
- package/dist/root-errors.d.ts +4 -0
- package/dist/root-errors.js +5 -3
- package/dist/root-impl.d.ts +0 -5
- package/dist/root-impl.js +22 -21
- package/dist/root-move-noreplace.js +4 -10
- package/dist/root-observed-path.js +2 -4
- package/dist/root-options.d.ts +1 -1
- package/dist/root-path-existing.js +2 -3
- package/dist/root-path-stat.js +2 -4
- package/dist/root-public-types.d.ts +11 -0
- package/dist/root-remove-identity.js +2 -4
- package/dist/root-remove.js +2 -4
- package/dist/root-write-admission.d.ts +1 -1
- package/dist/root-write-admission.js +3 -7
- package/dist/root-write-complete-parent.js +2 -3
- package/dist/root-write-verification.d.ts +1 -1
- package/dist/root.d.ts +2 -6
- package/dist/secret-file.js +0 -1
- package/dist/secure-temp-repair.js +5 -1
- package/dist/sidecar-lock-acquire.js +7 -10
- package/dist/sidecar-lock-policy.d.ts +1 -0
- package/dist/sidecar-lock-policy.js +7 -0
- package/dist/sidecar-lock-stale-admission.js +2 -6
- package/dist/sidecar-lock-types.d.ts +22 -16
- package/dist/temp-workspace-admission.js +1 -3
- package/dist/temp-workspace-child-admission.d.ts +16 -2
- package/dist/temp-workspace-child-admission.js +71 -3
- package/dist/temp-workspace-descriptor.d.ts +1 -1
- package/dist/temp-workspace-descriptor.js +1 -2
- package/dist/text-atomic.js +1 -1
- package/dist/windows-owner.js +1 -1
- package/dist/windows-path-alias.d.ts +1 -0
- package/dist/windows-path-alias.js +5 -0
- package/docs/advanced.md +3 -1
- package/docs/secret-file.md +4 -0
- package/docs/sidecar-lock.md +4 -0
- package/docs/store.md +6 -0
- package/docs/temp.md +8 -1
- package/docs/writing.md +5 -0
- package/package.json +8 -8
- package/dist/archive-native.d.ts +0 -10
- package/dist/archive-native.js +0 -73
- package/dist/archive-options.d.ts +0 -30
- package/dist/archive-policy.d.ts +0 -21
- package/dist/archive-policy.js +0 -38
- package/dist/archive-tar-inspect.d.ts +0 -6
- package/dist/archive-tar-inspect.js +0 -62
- package/dist/archive-tar.d.ts +0 -18
- package/dist/archive-tar.js +0 -21
- package/dist/create-directory.d.ts +0 -18
- package/dist/create-directory.js +0 -111
- package/dist/create-file-async.d.ts +0 -6
- package/dist/create-file-async.js +0 -121
- package/dist/create-file.d.ts +0 -7
- package/dist/create-file.js +0 -179
- package/dist/creation-darwin.d.ts +0 -5
- package/dist/creation-darwin.js +0 -70
- package/dist/creation-file-state.d.ts +0 -18
- package/dist/creation-file-state.js +0 -118
- package/dist/creation-path.d.ts +0 -22
- package/dist/creation-path.js +0 -83
- package/dist/creation-permissions.d.ts +0 -18
- package/dist/creation-permissions.js +0 -115
- package/dist/file-store-copy-source.d.ts +0 -4
- package/dist/file-store-copy-source.js +0 -31
- package/dist/file-store-limit.d.ts +0 -1
- package/dist/file-store-limit.js +0 -7
- package/dist/file-store-path.d.ts +0 -2
- package/dist/file-store-path.js +0 -30
- package/dist/standalone-publication-path.d.ts +0 -1
- package/dist/standalone-publication-path.js +0 -6
- package/dist/temp-workspace-identity.d.ts +0 -14
- package/dist/temp-workspace-identity.js +0 -41
- package/dist/temp-workspace-permissions.d.ts +0 -3
- package/dist/temp-workspace-permissions.js +0 -32
- /package/dist/{archive-options.js → root-public-types.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.18.1 - 2026-09-22
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- **Durable queue errors:** preserve the original read, validation, callback, or migration failure when closing the read descriptor also fails.
|
|
10
|
+
- **Secure temporary directories:** stop rejecting valid directories when Node reports negative device or inode IDs. Thanks @vincentkoc. ([#586](https://github.com/openclaw/fs-safe/pull/586))
|
|
11
|
+
- **Root write hardening:** prevent extra properties in wider options objects from enabling private writer controls or applying streamed-create byte limits to buffered writes. ([#571](https://github.com/openclaw/fs-safe/pull/571))
|
|
12
|
+
- **Error reporting:** preserve original errors from directory checks, secret reads, and Root opens when descriptor cleanup also fails. ([#574](https://github.com/openclaw/fs-safe/pull/574))
|
|
13
|
+
- **Lock timeouts:** keep async and sync retry deadlines stable when the system clock changes. Preserve wall-clock lock timestamps and stale-age checks.
|
|
14
|
+
|
|
5
15
|
## 0.18.0 - 2026-09-21
|
|
6
16
|
|
|
7
17
|
### Highlights
|
package/dist/advanced.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export { probePathCaseInsensitiveSync, type ProbePathCaseOptions } from "./path-
|
|
|
7
7
|
export { resolvePathPrefixSync, type ResolvedPathPrefix } from "./path-prefix.js";
|
|
8
8
|
export { probePathSuffixAliasesSync, type ProbePathSuffixAliasesOptions } from "./path-suffix-aliases.js";
|
|
9
9
|
export { readDirectoryIdentity, assertDirectoryIdentitySync, type DirectoryIdentity, } from "./directory-guard.js";
|
|
10
|
-
export { stageFileInDirectory,
|
|
10
|
+
export { stageFileInDirectory, } from "./native-staged-file.js";
|
|
11
|
+
export type { StagedFile, StagedFileReceipt, PublishedFileReceipt, StagedFilePublication, StagedFileCleanupReceipt, StagedFileFailureDetails, } from "./staged-file-types.js";
|
|
11
12
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
12
13
|
export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOptions, } from "./positional-read.js";
|
|
13
14
|
export { writeFileWindowFully, type WriteFileWindowOptions } from "./write-file-handle.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ExtractionDeadline } from "./archive-deadline.js";
|
|
2
2
|
import { type ArchiveDirectoryGuard } from "./archive-staging.js";
|
|
3
|
-
import type { PublishedWriteIdentity } from "./pinned-write.js";
|
|
3
|
+
import type { PublishedWriteIdentity } from "./pinned-write-types.js";
|
|
4
4
|
import type { Root } from "./root.js";
|
|
5
5
|
export type ArchivePublishedFile = {
|
|
6
6
|
relativePath: string;
|
package/dist/archive-parser.wasm
CHANGED
|
Binary file
|
package/dist/archive-plan.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ArchiveKind } from "./archive-kind.js";
|
|
2
|
-
import { type ResolvedArchiveExtractLimits } from "./archive-limits.js";
|
|
3
|
-
import type {
|
|
4
|
-
import { type ArchiveEntryKind } from "./archive-policy.js";
|
|
2
|
+
import { type ResolvedArchiveExtractLimits, type ArchiveExtractLimits } from "./archive-limits.js";
|
|
3
|
+
import type { ExtractionDeadline } from "./archive-deadline.js";
|
|
5
4
|
export type ArchiveMemberKind = "file" | "directory" | "symlink" | "hardlink" | "blocked" | "sparse" | "other";
|
|
6
5
|
export type ArchivePlanEntry = {
|
|
7
6
|
path: string;
|
|
@@ -27,3 +26,65 @@ export declare function createArchiveEntryPlanner(params: ArchivePlanOptions, ar
|
|
|
27
26
|
size: number;
|
|
28
27
|
mode?: number;
|
|
29
28
|
}) => ArchivePlanEntry | null;
|
|
29
|
+
export type ArchiveEntryKind = "file" | "directory" | "symlink" | "other";
|
|
30
|
+
export type ArchiveEntryModePolicy = "clamp" | "preserve";
|
|
31
|
+
export type ArchiveFilteredEntryPolicy = "reject-archive" | "skip-entry";
|
|
32
|
+
export type ArchiveEntryFilter = (entry: {
|
|
33
|
+
/** Validated canonical archive path before stripping: / separators, no empty or . components. */
|
|
34
|
+
path: string;
|
|
35
|
+
kind: ArchiveEntryKind;
|
|
36
|
+
size: number;
|
|
37
|
+
}) => "extract" | "skip";
|
|
38
|
+
export declare function archiveEntryKindFromTarType(type: string): ArchiveEntryKind;
|
|
39
|
+
export declare function resolveArchiveEntryMode(params: {
|
|
40
|
+
kind: "file" | "directory";
|
|
41
|
+
archivedMode?: number | null;
|
|
42
|
+
policy?: ArchiveEntryModePolicy;
|
|
43
|
+
}): number;
|
|
44
|
+
export declare function resolveArchiveFilteredEntryPolicy(value: unknown): ArchiveFilteredEntryPolicy;
|
|
45
|
+
export declare function shouldExtractArchiveEntry(params: {
|
|
46
|
+
filter?: ArchiveEntryFilter;
|
|
47
|
+
onFiltered?: ArchiveFilteredEntryPolicy;
|
|
48
|
+
entry: Parameters<ArchiveEntryFilter>[0];
|
|
49
|
+
}): boolean;
|
|
50
|
+
export type TarEntryInfo = {
|
|
51
|
+
path: string;
|
|
52
|
+
type: string;
|
|
53
|
+
size: number;
|
|
54
|
+
mode?: number;
|
|
55
|
+
};
|
|
56
|
+
export declare function createTarEntryPlanner(params: ArchivePlanOptions): (entry: TarEntryInfo) => ArchivePlanEntry | null;
|
|
57
|
+
export declare function createTarEntryPreflightChecker(params: {
|
|
58
|
+
rootDir: string;
|
|
59
|
+
stripComponents?: number;
|
|
60
|
+
limits?: ArchiveExtractLimits;
|
|
61
|
+
escapeLabel?: string;
|
|
62
|
+
entryFilter?: ArchiveEntryFilter;
|
|
63
|
+
onFiltered?: ArchiveFilteredEntryPolicy;
|
|
64
|
+
}): (entry: TarEntryInfo) => boolean;
|
|
65
|
+
export type ArchiveLogger = {
|
|
66
|
+
info?: (message: string) => void;
|
|
67
|
+
warn?: (message: string) => void;
|
|
68
|
+
};
|
|
69
|
+
export type ExtractArchiveOptions = {
|
|
70
|
+
archivePath: string;
|
|
71
|
+
destDir: string;
|
|
72
|
+
timeoutMs: number;
|
|
73
|
+
/** Sync published files and directories before returning. Defaults to false. */
|
|
74
|
+
durable?: boolean;
|
|
75
|
+
kind?: ArchiveKind;
|
|
76
|
+
stripComponents?: number;
|
|
77
|
+
tarGzip?: boolean;
|
|
78
|
+
limits?: ArchiveExtractLimits;
|
|
79
|
+
logger?: ArchiveLogger;
|
|
80
|
+
entryModes?: ArchiveEntryModePolicy;
|
|
81
|
+
/** Remove these rwx bits from final entry modes. Defaults to zero. */
|
|
82
|
+
entryUmask?: number;
|
|
83
|
+
entryFilter?: ArchiveEntryFilter;
|
|
84
|
+
onFiltered?: ArchiveFilteredEntryPolicy;
|
|
85
|
+
};
|
|
86
|
+
/** Private executors receive owned options and an already-staged archive path. */
|
|
87
|
+
export type StagedArchiveExtractOptions = Pick<ExtractArchiveOptions, "archivePath" | "destDir" | "durable" | "stripComponents" | "entryModes" | "entryUmask" | "entryFilter" | "onFiltered"> & {
|
|
88
|
+
limits: ResolvedArchiveExtractLimits;
|
|
89
|
+
deadline: ExtractionDeadline;
|
|
90
|
+
};
|
package/dist/archive-plan.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
|
|
2
2
|
import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
3
3
|
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, resolveExtractLimits, } from "./archive-limits.js";
|
|
4
|
-
import { resolveArchiveEntryMode, shouldExtractArchiveEntry } from "./archive-policy.js";
|
|
5
4
|
import { formatErrorDetail } from "./error-detail.js";
|
|
6
5
|
// Portable ZIP selects each entry immediately before streaming its payload;
|
|
7
6
|
// manifest-backed routes add declared-byte admission through the planner below.
|
|
@@ -63,3 +62,58 @@ export function createArchiveEntryPlanner(params, archiveKind) {
|
|
|
63
62
|
mode: resolveArchiveEntryMode({ kind, archivedMode: entry.mode, policy: params.entryModes }) };
|
|
64
63
|
};
|
|
65
64
|
}
|
|
65
|
+
export function archiveEntryKindFromTarType(type) {
|
|
66
|
+
if (type === "Directory" || type === "GNUDumpDir")
|
|
67
|
+
return "directory";
|
|
68
|
+
if (type === "File" || type === "OldFile" || type === "ContiguousFile")
|
|
69
|
+
return "file";
|
|
70
|
+
if (type === "SymbolicLink" || type === "Link")
|
|
71
|
+
return "symlink";
|
|
72
|
+
return "other";
|
|
73
|
+
}
|
|
74
|
+
export function resolveArchiveEntryMode(params) {
|
|
75
|
+
const archivedMode = (params.archivedMode ?? 0) & 0o777;
|
|
76
|
+
if (params.policy === "preserve") {
|
|
77
|
+
return params.archivedMode == null ? (params.kind === "directory" ? 0o755 : 0o644) : archivedMode;
|
|
78
|
+
}
|
|
79
|
+
if (params.kind === "directory") {
|
|
80
|
+
return 0o755;
|
|
81
|
+
}
|
|
82
|
+
return archivedMode & 0o100 ? 0o755 : 0o644;
|
|
83
|
+
}
|
|
84
|
+
export function resolveArchiveFilteredEntryPolicy(value) {
|
|
85
|
+
if (value === undefined || value === "reject-archive")
|
|
86
|
+
return "reject-archive";
|
|
87
|
+
if (value === "skip-entry")
|
|
88
|
+
return "skip-entry";
|
|
89
|
+
throw new RangeError('archive onFiltered must be "reject-archive" or "skip-entry"');
|
|
90
|
+
}
|
|
91
|
+
export function shouldExtractArchiveEntry(params) {
|
|
92
|
+
const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
|
|
93
|
+
if (!params.filter || params.filter(params.entry) === "extract") {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (onFiltered === "reject-archive") {
|
|
97
|
+
throw new ArchiveSecurityError("entry-filtered", `archive entry rejected by filter: ${formatErrorDetail(params.entry.path)}`);
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const BLOCKED_TAR_ENTRY_TYPES = new Set([
|
|
102
|
+
"BlockDevice",
|
|
103
|
+
"CharacterDevice",
|
|
104
|
+
"FIFO",
|
|
105
|
+
"Socket",
|
|
106
|
+
]);
|
|
107
|
+
export function createTarEntryPlanner(params) {
|
|
108
|
+
// Preserve inherited/getter-backed public options and per-entry policy reads.
|
|
109
|
+
const plan = createArchiveEntryPlanner(params, "tar");
|
|
110
|
+
return (entry) => {
|
|
111
|
+
const kind = BLOCKED_TAR_ENTRY_TYPES.has(entry.type) ? "blocked" : archiveEntryKindFromTarType(entry.type);
|
|
112
|
+
// The public checker accepts structural entry objects, including class getters.
|
|
113
|
+
return plan({ path: entry.path, kind, size: entry.size, mode: entry.mode });
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function createTarEntryPreflightChecker(params) {
|
|
117
|
+
const plan = createTarEntryPlanner(params);
|
|
118
|
+
return (entry) => plan(entry) !== null;
|
|
119
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transform, type TransformCallback } from "node:stream";
|
|
2
2
|
import type { TarMeterLimits } from "./archive-limits.js";
|
|
3
|
-
import type { TarEntryInfo } from "./archive-
|
|
3
|
+
import type { TarEntryInfo } from "./archive-plan.js";
|
|
4
4
|
export type AdmittedTarMember = TarEntryInfo & {
|
|
5
5
|
offset: number;
|
|
6
6
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ArchiveEntryModePolicy, type ArchiveEntryKind } from "./archive-
|
|
1
|
+
import { type ArchiveEntryModePolicy, type ArchiveEntryKind } from "./archive-plan.js";
|
|
2
2
|
import type { ZipDirectoryEntry } from "./archive-zip-directory.js";
|
|
3
3
|
export type ZipEntry = {
|
|
4
4
|
name: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveArchiveEntryMode, } from "./archive-
|
|
1
|
+
import { resolveArchiveEntryMode, } from "./archive-plan.js";
|
|
2
2
|
const admittedMetadata = new WeakMap();
|
|
3
3
|
/** Internal: register only after the complete decoder/admission association. */
|
|
4
4
|
export function registerAdmittedZipEntry(entry, physical) {
|
package/dist/archive.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export type { ArchiveLogger, ExtractArchiveOptions } from "./archive-
|
|
3
|
-
export type { ArchiveEntryFilter, ArchiveEntryKind, ArchiveEntryModePolicy, ArchiveFilteredEntryPolicy, } from "./archive-
|
|
1
|
+
import { type ExtractArchiveOptions, type ArchivePlanEntry } from "./archive-plan.js";
|
|
2
|
+
export type { ArchiveLogger, ExtractArchiveOptions } from "./archive-plan.js";
|
|
3
|
+
export type { ArchiveEntryFilter, ArchiveEntryKind, ArchiveEntryModePolicy, ArchiveFilteredEntryPolicy, } from "./archive-plan.js";
|
|
4
4
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
5
5
|
export { resolveArchiveKind, resolvePackedRootDir, type ArchiveKind } from "./archive-kind.js";
|
|
6
6
|
export { readArchiveEntry } from "./archive-read.js";
|
|
7
|
-
export { inspectTarArchive, type InspectTarArchiveOptions, type InspectedTarEntry } from "./archive-tar-inspect.js";
|
|
8
7
|
export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, type ArchiveExtractLimits, type ArchiveLimitErrorCode, } from "./archive-limits.js";
|
|
9
8
|
export { ArchiveFormatError, type ArchiveFormatErrorCode } from "./archive-errors.js";
|
|
10
9
|
export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-staging.js";
|
|
11
10
|
export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
12
|
-
export { createTarEntryPreflightChecker, type TarEntryInfo } from "./archive-
|
|
11
|
+
export { createTarEntryPreflightChecker, type TarEntryInfo } from "./archive-plan.js";
|
|
13
12
|
export { loadZipArchiveWithPreflight, readZipCentralDirectoryEntryCount, type ZipArchiveWithFiles, } from "./archive-zip-preflight.js";
|
|
14
13
|
export declare function extractArchive(params: ExtractArchiveOptions): Promise<void>;
|
|
14
|
+
export type InspectTarArchiveOptions = Pick<ExtractArchiveOptions, "archivePath" | "timeoutMs" | "limits" | "entryFilter" | "onFiltered">;
|
|
15
|
+
export type InspectedTarEntry = Readonly<Pick<ArchivePlanEntry, "path" | "kind" | "size">>;
|
|
16
|
+
/** Complete TAR/gzip admission and zero-strip extraction policy, without output writes. */
|
|
17
|
+
export declare function inspectTarArchive(params: InspectTarArchiveOptions): Promise<readonly InspectedTarEntry[]>;
|
package/dist/archive.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { createTarEntryPlanner } from "./archive-
|
|
1
|
+
import { createTarEntryPlanner, createArchiveEntrySelector, resolveArchiveFilteredEntryPolicy, createArchiveEntryPlanner, } from "./archive-plan.js";
|
|
2
2
|
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
3
3
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
4
4
|
import { constants as fsConstants } from "node:fs";
|
|
5
|
-
import fs from "node:fs/promises";
|
|
5
|
+
import fs, {} from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
import { pipeline } from "node:stream/promises";
|
|
9
|
-
import { createArchiveEntrySelector } from "./archive-plan.js";
|
|
10
9
|
import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
|
|
11
10
|
import { assertArchiveEntryCountWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, resolveTarMeterLimits, } from "./archive-limits.js";
|
|
12
11
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
@@ -16,22 +15,23 @@ import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
|
16
15
|
import { admittedZipEntries } from "./archive-zip-loader.js";
|
|
17
16
|
import { zipEntryKind, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
|
|
18
17
|
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
19
|
-
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
20
|
-
import { extractNativeArchive } from "./archive-native.js";
|
|
18
|
+
import { ArchiveSecurityError, ArchiveFormatError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
21
19
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
22
|
-
import { getNativeBinding } from "./native.js";
|
|
23
|
-
import { resolveArchiveFilteredEntryPolicy } from "./archive-policy.js";
|
|
20
|
+
import { getNativeBinding, } from "./native.js";
|
|
24
21
|
import { writeSiblingTempFile } from "./sibling-temp.js";
|
|
25
22
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
23
|
+
import { classifyArchiveParserError } from "./archive-parser-errors.js";
|
|
24
|
+
import { validateArchiveEntryPath } from "./archive-entry.js";
|
|
25
|
+
import { admitZipFile } from "./archive-zip-admission.js";
|
|
26
|
+
import { validateNativeZipManifest } from "./archive-zip-manifest.js";
|
|
26
27
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
27
28
|
export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
|
|
28
29
|
export { readArchiveEntry } from "./archive-read.js";
|
|
29
|
-
export { inspectTarArchive } from "./archive-tar-inspect.js";
|
|
30
30
|
export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
|
|
31
31
|
export { ArchiveFormatError } from "./archive-errors.js";
|
|
32
32
|
export { ArchiveSecurityError } from "./archive-staging.js";
|
|
33
33
|
export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
34
|
-
export { createTarEntryPreflightChecker } from "./archive-
|
|
34
|
+
export { createTarEntryPreflightChecker } from "./archive-plan.js";
|
|
35
35
|
export { loadZipArchiveWithPreflight, readZipCentralDirectoryEntryCount, } from "./archive-zip-preflight.js";
|
|
36
36
|
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
|
|
37
37
|
const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
|
|
@@ -214,3 +214,116 @@ async function extractWasmTar(params) {
|
|
|
214
214
|
return accepted;
|
|
215
215
|
});
|
|
216
216
|
}
|
|
217
|
+
function throwMappedNativeArchiveError(error) {
|
|
218
|
+
if (error instanceof Error) {
|
|
219
|
+
const mapped = classifyArchiveParserError(error.message, { cause: error });
|
|
220
|
+
if (mapped)
|
|
221
|
+
throw mapped;
|
|
222
|
+
if (isArchiveFormatErrorMessage(error.message)) {
|
|
223
|
+
throw new ArchiveFormatError(error.message, { cause: error });
|
|
224
|
+
}
|
|
225
|
+
if (error.code === "InvalidArg") {
|
|
226
|
+
throw new ArchiveFormatError(`invalid archive: ${error.message}`, { cause: error });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
async function extractNativeArchive(params) {
|
|
232
|
+
const { archivePath, limits, tarLimits, deadline } = params;
|
|
233
|
+
const zipEntries = [];
|
|
234
|
+
if (params.kind === "zip") {
|
|
235
|
+
await admitZipFile(archivePath, limits, deadline, (entry) => { zipEntries.push(entry); });
|
|
236
|
+
}
|
|
237
|
+
const destinationGuard = await prepareArchiveDestinationGuard(params.destDir);
|
|
238
|
+
await withStagedArchivePublication({ ...params, destinationGuard }, async (stagingDir) => {
|
|
239
|
+
deadline.check();
|
|
240
|
+
// N-API retains completed task state on its signal; each pass needs its own.
|
|
241
|
+
const manifest = await params.binding
|
|
242
|
+
.inspectArchiveNative(archivePath, params.kind, tarLimits, AbortSignal.any([deadline.signal]))
|
|
243
|
+
.catch(throwMappedNativeArchiveError);
|
|
244
|
+
deadline.check();
|
|
245
|
+
assertArchiveEntryCountWithinLimit(manifest.length, limits);
|
|
246
|
+
if (params.kind === "zip") {
|
|
247
|
+
validateNativeZipManifest(manifest, zipEntries);
|
|
248
|
+
}
|
|
249
|
+
// Recheck the native manifest before any caller callback observes an entry.
|
|
250
|
+
if (params.kind !== "zip") {
|
|
251
|
+
for (const entry of manifest)
|
|
252
|
+
validateArchiveEntryPath(entry.path);
|
|
253
|
+
}
|
|
254
|
+
const planEntry = createArchiveEntryPlanner({ ...params, rootDir: stagingDir }, params.kind);
|
|
255
|
+
const plan = [];
|
|
256
|
+
for (const entry of manifest) {
|
|
257
|
+
deadline.check();
|
|
258
|
+
const mode = params.kind === "zip"
|
|
259
|
+
? zipEntries[entry.index].creatorSystem === 3
|
|
260
|
+
? zipEntries[entry.index].externalAttributes >>> 16
|
|
261
|
+
: undefined
|
|
262
|
+
: entry.mode;
|
|
263
|
+
const accepted = planEntry({ ...entry, mode });
|
|
264
|
+
if (accepted)
|
|
265
|
+
plan.push({ ...accepted, index: entry.index });
|
|
266
|
+
}
|
|
267
|
+
const directory = await fs.open(stagingDir, fsConstants.O_RDONLY |
|
|
268
|
+
(typeof fsConstants.O_DIRECTORY === "number" ? fsConstants.O_DIRECTORY : 0));
|
|
269
|
+
try {
|
|
270
|
+
deadline.check();
|
|
271
|
+
await params.binding.extractArchiveNative(archivePath, params.kind, directory.fd, plan.map((entry) => ({ ...entry, mode: entry.kind === "directory" ? 0o700 : 0o600 })), tarLimits, AbortSignal.any([deadline.signal])).catch(throwMappedNativeArchiveError);
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
await directory.close().catch(() => undefined);
|
|
275
|
+
}
|
|
276
|
+
return plan;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/** Complete TAR/gzip admission and zero-strip extraction policy, without output writes. */
|
|
280
|
+
export async function inspectTarArchive(params) {
|
|
281
|
+
const archivePath = params.archivePath;
|
|
282
|
+
const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
|
|
283
|
+
const limits = resolveExtractLimits(params.limits);
|
|
284
|
+
const tarLimits = resolveTarMeterLimits(limits);
|
|
285
|
+
const native = getNativeBinding();
|
|
286
|
+
assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
|
|
287
|
+
return await withExtractionDeadline(params.timeoutMs, "inspect tar", async (deadline) => {
|
|
288
|
+
const staged = await stageArchiveFileForExtraction({ archivePath, limits, deadline });
|
|
289
|
+
try {
|
|
290
|
+
// Staging closes descriptors asynchronously; expiry there must not start a decoder.
|
|
291
|
+
deadline.check();
|
|
292
|
+
const entries = [];
|
|
293
|
+
const append = (entry) => {
|
|
294
|
+
if (entry)
|
|
295
|
+
entries.push(Object.freeze({ path: entry.path, kind: entry.kind, size: entry.size }));
|
|
296
|
+
};
|
|
297
|
+
const policy = { limits, entryFilter: params.entryFilter, onFiltered };
|
|
298
|
+
if (native) {
|
|
299
|
+
const manifest = await native.inspectArchiveNative(staged.path, "tar", tarLimits, deadline.signal)
|
|
300
|
+
.catch(throwMappedNativeArchiveError);
|
|
301
|
+
deadline.check();
|
|
302
|
+
// Match extraction's whole-manifest validation before caller policy runs.
|
|
303
|
+
for (const entry of manifest)
|
|
304
|
+
validateArchiveEntryPath(entry.path);
|
|
305
|
+
const planEntry = createArchiveEntryPlanner(policy, "tar");
|
|
306
|
+
for (const entry of manifest) {
|
|
307
|
+
deadline.check();
|
|
308
|
+
append(planEntry(entry));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const manifest = [];
|
|
313
|
+
await inspectTar({ archivePath: staged.path, limits: tarLimits, signal: deadline.signal,
|
|
314
|
+
onMember: (entry) => { manifest.push(entry); } });
|
|
315
|
+
const planEntry = createTarEntryPlanner(policy);
|
|
316
|
+
for (const entry of manifest) {
|
|
317
|
+
deadline.check();
|
|
318
|
+
append(planEntry(entry));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
deadline.check();
|
|
322
|
+
// This is bounded evidence about the staged bytes, not a reusable write plan.
|
|
323
|
+
return Object.freeze(entries);
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
await staged.cleanup();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
package/dist/atomic.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { replaceFileAtomic, replaceFileAtomicSync, type
|
|
1
|
+
export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
2
|
+
export type { RenameIdentityPolicy } from "./pinned-write-types.js";
|
|
3
|
+
export type { ReplaceFileAtomicRestoreCleanup, ReplaceFileAtomicRestoreFailureDetails, ReplaceFileCopyFallbackRestorePolicy, ReplaceFileDestinationHardlinkPolicy, } from "./replace-file-copy-fallback.js";
|
|
2
4
|
export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
|
|
3
5
|
export { replaceDirectoryAtomic, type ReplaceDirectoryAtomicOptions } from "./replace-directory.js";
|
|
4
6
|
export { movePathWithCopyFallback, type MovePathPublicationReceipt, type MovePathWithCopyFallbackOptions, } from "./move-path.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
|
-
import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write.js";
|
|
2
|
+
import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write-types.js";
|
|
3
3
|
export type RootCopyPublicationReceipt = Readonly<{
|
|
4
4
|
path: string;
|
|
5
5
|
dev: bigint;
|
package/dist/create.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { type FileHandle } from "node:fs/promises";
|
|
2
|
+
import { type OwnedFileDescriptorSync } from "./create-owned-file.js";
|
|
3
|
+
import { type CreationPermissions, type CreationParentIdentity } from "./creation-boundary.js";
|
|
3
4
|
export type { OwnedFileDescriptorSync } from "./create-owned-file.js";
|
|
5
|
+
export type CreateFileOptions = CreateDirectoryOptions;
|
|
6
|
+
export declare function createFileSync(targetPath: string, options?: CreateFileOptions): OwnedFileDescriptorSync;
|
|
7
|
+
/** Internal async writer; Windows preparation uses the nonblocking stage owner. */
|
|
8
|
+
export declare function createFileHandle(targetPath: string, options?: CreateFileOptions, admission?: CreationAdmission): Promise<FileHandle>;
|
|
9
|
+
export type CreateDirectoryOptions = CreationPermissions & {
|
|
10
|
+
assertBeforeMutation?: () => void;
|
|
11
|
+
};
|
|
12
|
+
export type CreationAdmission = {
|
|
13
|
+
expectedParentIdentity?: CreationParentIdentity;
|
|
14
|
+
};
|
|
15
|
+
export declare function createDirectory(targetPath: string, options?: CreateDirectoryOptions): Promise<void>;
|
|
16
|
+
export declare function createDirectoryWithAdmission(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): Promise<void>;
|
|
17
|
+
export declare function createDirectorySync(targetPath: string, options?: CreateDirectoryOptions): void;
|