@openclaw/fs-safe 0.4.7 → 0.5.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.
- package/CHANGELOG.md +45 -0
- package/README.md +82 -43
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +5 -4
- package/dist/archive-errors.d.ts +11 -0
- package/dist/archive-errors.d.ts.map +1 -0
- package/dist/archive-errors.js +16 -0
- package/dist/archive-input.d.ts +12 -0
- package/dist/archive-input.d.ts.map +1 -0
- package/dist/archive-input.js +72 -0
- package/dist/archive-kind.d.ts +1 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +18 -0
- package/dist/archive-limits.d.ts +10 -0
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +18 -0
- package/dist/archive-native.d.ts +18 -0
- package/dist/archive-native.d.ts.map +1 -0
- package/dist/archive-native.js +115 -0
- package/dist/archive-options.d.ts +21 -0
- package/dist/archive-options.d.ts.map +1 -0
- package/dist/archive-options.js +1 -0
- package/dist/archive-policy.d.ts +20 -0
- package/dist/archive-policy.d.ts.map +1 -0
- package/dist/archive-policy.js +29 -0
- package/dist/archive-read.d.ts +6 -0
- package/dist/archive-read.d.ts.map +1 -0
- package/dist/archive-read.js +200 -0
- package/dist/archive-staging.d.ts +2 -5
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +19 -10
- package/dist/archive-tar-meta.d.ts +6 -0
- package/dist/archive-tar-meta.d.ts.map +1 -0
- package/dist/archive-tar-meta.js +143 -0
- package/dist/archive-tar-runtime.d.ts +43 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -0
- package/dist/archive-tar-runtime.js +8 -0
- package/dist/archive-tar.d.ts +5 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +24 -6
- package/dist/archive-zip-entry.d.ts +15 -0
- package/dist/archive-zip-entry.d.ts.map +1 -0
- package/dist/archive-zip-entry.js +17 -0
- package/dist/archive.d.ts +7 -17
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +111 -108
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/containment.d.ts +2 -0
- package/dist/containment.d.ts.map +1 -0
- package/dist/containment.js +1 -0
- package/dist/durability.d.ts +2 -0
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +2 -0
- package/dist/errors.d.ts +4 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/file-hash.d.ts +10 -0
- package/dist/file-hash.d.ts.map +1 -0
- package/dist/file-hash.js +73 -0
- package/dist/file-lock-sync.d.ts +35 -0
- package/dist/file-lock-sync.d.ts.map +1 -0
- package/dist/file-lock-sync.js +233 -0
- package/dist/file-lock.d.ts +2 -0
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +4 -1
- package/dist/guarded-mkdir.d.ts +7 -1
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +45 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +30 -15
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +101 -17
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
- package/dist/native-binding.d.ts +79 -0
- package/dist/native-binding.d.ts.map +1 -0
- package/dist/native-binding.js +1 -0
- package/dist/native-config.d.ts +17 -0
- package/dist/native-config.d.ts.map +1 -0
- package/dist/native-config.js +71 -0
- package/dist/native-operations.d.ts +21 -0
- package/dist/native-operations.d.ts.map +1 -0
- package/dist/native-operations.js +115 -0
- package/dist/native-pinned-write.d.ts +5 -0
- package/dist/native-pinned-write.d.ts.map +1 -0
- package/dist/native-pinned-write.js +115 -0
- package/dist/native.d.ts +14 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +204 -0
- package/dist/output-sibling.d.ts +8 -0
- package/dist/output-sibling.d.ts.map +1 -0
- package/dist/output-sibling.js +122 -0
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +32 -5
- package/dist/owner-dacl.d.ts +31 -0
- package/dist/owner-dacl.d.ts.map +1 -0
- package/dist/owner-dacl.js +27 -0
- package/dist/path-policy.js +1 -1
- package/dist/permissions-public.d.ts +2 -0
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-public.js +2 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +6 -0
- package/dist/pinned-operation.d.ts +2 -0
- package/dist/pinned-operation.d.ts.map +1 -0
- package/dist/pinned-operation.js +35 -0
- package/dist/pinned-write.d.ts +3 -16
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +20 -85
- package/dist/private-directory.d.ts +5 -0
- package/dist/private-directory.d.ts.map +1 -0
- package/dist/private-directory.js +13 -0
- package/dist/private-temp-workspace.d.ts +6 -2
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +40 -13
- package/dist/publish-file-failure.d.ts +27 -0
- package/dist/publish-file-failure.d.ts.map +1 -0
- package/dist/publish-file-failure.js +21 -0
- package/dist/publish-file.d.ts +21 -0
- package/dist/publish-file.d.ts.map +1 -0
- package/dist/publish-file.js +351 -0
- package/dist/read-opened-file.d.ts +3 -0
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +1 -0
- package/dist/replace-file-copy-fallback.d.ts +34 -0
- package/dist/replace-file-copy-fallback.d.ts.map +1 -0
- package/dist/replace-file-copy-fallback.js +339 -0
- package/dist/replace-file.d.ts +6 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +39 -90
- package/dist/root-context.d.ts +4 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +20 -1
- package/dist/root-impl.d.ts +5 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +128 -225
- package/dist/root-path-existing.d.ts +3 -0
- package/dist/root-path-existing.d.ts.map +1 -0
- package/dist/root-path-existing.js +69 -0
- package/dist/root-path.d.ts +1 -2
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +67 -81
- package/dist/root-walk.d.ts +41 -0
- package/dist/root-walk.d.ts.map +1 -0
- package/dist/root-walk.js +114 -0
- package/dist/root.d.ts +2 -0
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts +5 -2
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +20 -2
- package/dist/secret-read-async.d.ts +4 -0
- package/dist/secret-read-async.d.ts.map +1 -0
- package/dist/secret-read-async.js +95 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +1 -0
- package/dist/sidecar-lock-handle.d.ts +20 -0
- package/dist/sidecar-lock-handle.d.ts.map +1 -0
- package/dist/sidecar-lock-handle.js +28 -0
- package/dist/sidecar-lock-policy.d.ts +10 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -0
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts +22 -6
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +106 -20
- package/dist/sidecar-lock-types.d.ts +56 -0
- package/dist/sidecar-lock-types.d.ts.map +1 -0
- package/dist/sidecar-lock-types.js +1 -0
- package/dist/sidecar-lock.d.ts +2 -45
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +94 -55
- package/dist/temp-cleanup.d.ts +7 -1
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +35 -5
- package/dist/temp.d.ts +2 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +3 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/windows-permissions-native.d.ts +9 -0
- package/dist/windows-permissions-native.d.ts.map +1 -0
- package/dist/windows-permissions-native.js +31 -0
- package/docs/archive.md +131 -13
- package/docs/atomic.md +30 -1
- package/docs/config.md +51 -21
- package/docs/durability.md +211 -1
- package/docs/errors.md +36 -6
- package/docs/filename.md +0 -0
- package/docs/index.md +11 -8
- package/docs/install.md +27 -26
- package/docs/json-store.md +18 -3
- package/docs/migrating-to-0.5.md +196 -0
- package/docs/native-helper.md +85 -0
- package/docs/native.md +134 -0
- package/docs/output.md +37 -11
- package/docs/path.md +1 -1
- package/docs/permissions.md +84 -1
- package/docs/quickstart.md +5 -2
- package/docs/reading.md +4 -4
- package/docs/root.md +31 -15
- package/docs/secret-file.md +50 -1
- package/docs/security-model.md +29 -8
- package/docs/sidecar-lock.md +92 -7
- package/docs/temp.md +24 -1
- package/docs/test-hooks.md +22 -2
- package/docs/testing.md +18 -7
- package/docs/types.md +3 -1
- package/docs/walk.md +55 -0
- package/docs/writing.md +3 -3
- package/package.json +8 -2
- package/dist/pinned-helper.d.ts +0 -9
- package/dist/pinned-helper.d.ts.map +0 -1
- package/dist/pinned-helper.js +0 -19
- package/dist/pinned-path.d.ts +0 -7
- package/dist/pinned-path.d.ts.map +0 -1
- package/dist/pinned-path.js +0 -21
- package/dist/pinned-python-config.d.ts +0 -9
- package/dist/pinned-python-config.d.ts.map +0 -1
- package/dist/pinned-python-config.js +0 -38
- package/dist/pinned-python.d.ts +0 -12
- package/dist/pinned-python.d.ts.map +0 -1
- package/dist/pinned-python.js +0 -688
- package/docs/python-helper.md +0 -103
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NativeBinding } from "./native-binding.js";
|
|
2
|
+
export type { NativeBinding } from "./native-binding.js";
|
|
3
|
+
export declare function __loadBundledNativeForTest(): NativeBinding;
|
|
4
|
+
export declare function __nativeLoaderDetectorsForTest(): {
|
|
5
|
+
report: boolean | undefined;
|
|
6
|
+
filesystem: boolean | undefined;
|
|
7
|
+
elfInterpreter: boolean | undefined;
|
|
8
|
+
};
|
|
9
|
+
export declare function __nativeTargetForTest(platform: NodeJS.Platform, arch: string, musl?: boolean): string | undefined;
|
|
10
|
+
export declare function getNativeBinding(): NativeBinding | undefined;
|
|
11
|
+
export declare function requireNativeBinding(): NativeBinding;
|
|
12
|
+
export declare function __setNativeLoaderForTest(loader: () => NativeBinding): void;
|
|
13
|
+
export declare function __resetNativeLoaderForTest(): void;
|
|
14
|
+
//# sourceMappingURL=native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAmKzD,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D;AAED,wBAAgB,8BAA8B,IAAI;IAChD,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAMA;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,UAAQ,GACX,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAkB5D;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAQpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAK1E;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD"}
|
package/dist/native.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync, readdirSync, } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { FsSafeError } from "./errors.js";
|
|
6
|
+
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const bundledNativeDirectory = fileURLToPath(new URL("../dist/native/", import.meta.url));
|
|
9
|
+
let binding;
|
|
10
|
+
let loadError;
|
|
11
|
+
let attempted = false;
|
|
12
|
+
let loadBinding = loadBundledBinding;
|
|
13
|
+
function isMuslFilename(file) {
|
|
14
|
+
return file.includes("libc.musl-") || file.includes("ld-musl-");
|
|
15
|
+
}
|
|
16
|
+
function isMuslFromReport() {
|
|
17
|
+
try {
|
|
18
|
+
if (!process.report || typeof process.report.getReport !== "function")
|
|
19
|
+
return undefined;
|
|
20
|
+
const report = process.report.getReport();
|
|
21
|
+
if (report.header?.glibcVersionRuntime)
|
|
22
|
+
return false;
|
|
23
|
+
if (report.sharedObjects?.some(isMuslFilename))
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Continue with filesystem and ELF inspection.
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
function isMuslFromFilesystem() {
|
|
32
|
+
for (const directory of ["/lib", "/usr/lib"]) {
|
|
33
|
+
try {
|
|
34
|
+
if (readdirSync(directory).some(isMuslFilename))
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// A missing or unreadable conventional library directory is inconclusive.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function readUInt(buffer, offset, bytes, littleEndian) {
|
|
44
|
+
if (offset < 0 || offset + bytes > buffer.length)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (bytes === 2)
|
|
47
|
+
return littleEndian ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
|
|
48
|
+
if (bytes === 4)
|
|
49
|
+
return littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
|
|
50
|
+
const value = littleEndian ? buffer.readBigUInt64LE(offset) : buffer.readBigUInt64BE(offset);
|
|
51
|
+
return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : undefined;
|
|
52
|
+
}
|
|
53
|
+
function isMuslFromElfInterpreter() {
|
|
54
|
+
let fd;
|
|
55
|
+
try {
|
|
56
|
+
fd = openSync(process.execPath, "r");
|
|
57
|
+
const header = Buffer.alloc(64);
|
|
58
|
+
if (readSync(fd, header, 0, header.length, 0) < 52)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (!header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])))
|
|
61
|
+
return undefined;
|
|
62
|
+
const elfClass = header[4];
|
|
63
|
+
const dataEncoding = header[5];
|
|
64
|
+
if ((elfClass !== 1 && elfClass !== 2) || (dataEncoding !== 1 && dataEncoding !== 2)) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const littleEndian = dataEncoding === 1;
|
|
68
|
+
const is64Bit = elfClass === 2;
|
|
69
|
+
const tableOffset = readUInt(header, is64Bit ? 32 : 28, is64Bit ? 8 : 4, littleEndian);
|
|
70
|
+
const entrySize = readUInt(header, is64Bit ? 54 : 42, 2, littleEndian);
|
|
71
|
+
const entryCount = readUInt(header, is64Bit ? 56 : 44, 2, littleEndian);
|
|
72
|
+
if (tableOffset === undefined ||
|
|
73
|
+
entrySize === undefined ||
|
|
74
|
+
entryCount === undefined ||
|
|
75
|
+
entrySize < (is64Bit ? 56 : 32) ||
|
|
76
|
+
entryCount > 1024) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const programHeader = Buffer.alloc(entrySize);
|
|
80
|
+
for (let index = 0; index < entryCount; index += 1) {
|
|
81
|
+
const offset = tableOffset + index * entrySize;
|
|
82
|
+
if (readSync(fd, programHeader, 0, entrySize, offset) !== entrySize)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (readUInt(programHeader, 0, 4, littleEndian) !== 3)
|
|
85
|
+
continue;
|
|
86
|
+
const interpreterOffset = readUInt(programHeader, is64Bit ? 8 : 4, is64Bit ? 8 : 4, littleEndian);
|
|
87
|
+
const interpreterSize = readUInt(programHeader, is64Bit ? 32 : 16, is64Bit ? 8 : 4, littleEndian);
|
|
88
|
+
if (interpreterOffset === undefined ||
|
|
89
|
+
interpreterSize === undefined ||
|
|
90
|
+
interpreterSize < 1 ||
|
|
91
|
+
interpreterSize > 4096) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const interpreter = Buffer.alloc(interpreterSize);
|
|
95
|
+
if (readSync(fd, interpreter, 0, interpreterSize, interpreterOffset) !== interpreterSize) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return isMuslFilename(interpreter.toString("utf8"));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
if (fd !== undefined) {
|
|
106
|
+
try {
|
|
107
|
+
closeSync(fd);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Best-effort detection must never turn close failure into import failure.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
function isMusl() {
|
|
117
|
+
if (process.platform !== "linux")
|
|
118
|
+
return false;
|
|
119
|
+
for (const detector of [isMuslFromReport, isMuslFromFilesystem, isMuslFromElfInterpreter]) {
|
|
120
|
+
const result = detector();
|
|
121
|
+
if (result !== undefined)
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
// Unknown Linux libc: try the glibc binary and let its require fail normally.
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
function targetFor(platform, arch, musl) {
|
|
128
|
+
if (platform === "win32" && arch === "x64")
|
|
129
|
+
return "win32-x64-msvc";
|
|
130
|
+
if (platform === "darwin" && arch === "x64")
|
|
131
|
+
return "darwin-x64";
|
|
132
|
+
if (platform === "darwin" && arch === "arm64")
|
|
133
|
+
return "darwin-arm64";
|
|
134
|
+
if (platform === "linux" && (arch === "x64" || arch === "arm64")) {
|
|
135
|
+
return `linux-${arch}-${musl ? "musl" : "gnu"}`;
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
function bundledTarget() {
|
|
140
|
+
return targetFor(process.platform, process.arch, isMusl());
|
|
141
|
+
}
|
|
142
|
+
function loadBundledBinding() {
|
|
143
|
+
const target = bundledTarget();
|
|
144
|
+
if (!target) {
|
|
145
|
+
throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
|
|
146
|
+
}
|
|
147
|
+
return require(join(bundledNativeDirectory, target, "fs-safe-native.node"));
|
|
148
|
+
}
|
|
149
|
+
export function __loadBundledNativeForTest() {
|
|
150
|
+
return loadBundledBinding();
|
|
151
|
+
}
|
|
152
|
+
export function __nativeLoaderDetectorsForTest() {
|
|
153
|
+
return {
|
|
154
|
+
report: isMuslFromReport(),
|
|
155
|
+
filesystem: isMuslFromFilesystem(),
|
|
156
|
+
elfInterpreter: isMuslFromElfInterpreter(),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export function __nativeTargetForTest(platform, arch, musl = false) {
|
|
160
|
+
return targetFor(platform, arch, musl);
|
|
161
|
+
}
|
|
162
|
+
export function getNativeBinding() {
|
|
163
|
+
const { mode } = getFsSafeNativeConfig();
|
|
164
|
+
if (mode === "off")
|
|
165
|
+
return undefined;
|
|
166
|
+
if (!attempted) {
|
|
167
|
+
attempted = true;
|
|
168
|
+
try {
|
|
169
|
+
binding = loadBinding();
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
loadError = error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (binding)
|
|
176
|
+
return binding;
|
|
177
|
+
if (mode === "require") {
|
|
178
|
+
throw new FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
179
|
+
cause: loadError,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
export function requireNativeBinding() {
|
|
185
|
+
const native = getNativeBinding();
|
|
186
|
+
if (!native) {
|
|
187
|
+
throw new FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
188
|
+
cause: loadError,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return native;
|
|
192
|
+
}
|
|
193
|
+
export function __setNativeLoaderForTest(loader) {
|
|
194
|
+
binding = undefined;
|
|
195
|
+
loadError = undefined;
|
|
196
|
+
attempted = false;
|
|
197
|
+
loadBinding = loader;
|
|
198
|
+
}
|
|
199
|
+
export function __resetNativeLoaderForTest() {
|
|
200
|
+
binding = undefined;
|
|
201
|
+
loadError = undefined;
|
|
202
|
+
attempted = false;
|
|
203
|
+
loadBinding = loadBundledBinding;
|
|
204
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-sibling.d.ts","sourceRoot":"","sources":["../src/output-sibling.ts"],"names":[],"mappings":"AAgGA,wBAAsB,2BAA2B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Cb"}
|
|
@@ -0,0 +1,122 @@
|
|
|
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 { createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
6
|
+
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
7
|
+
import { FsSafeError } from "./errors.js";
|
|
8
|
+
import { sameFileIdentity } from "./file-identity.js";
|
|
9
|
+
import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
10
|
+
import { sanitizeUntrustedFileName } from "./filename.js";
|
|
11
|
+
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
12
|
+
import { serializePathWrite } from "./write-queue.js";
|
|
13
|
+
const OPEN_READ_WRITE_NOFOLLOW = fsSync.constants.O_RDWR |
|
|
14
|
+
(process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
15
|
+
? fsSync.constants.O_NOFOLLOW
|
|
16
|
+
: 0);
|
|
17
|
+
function safeFallbackFileName(fallbackFileName) {
|
|
18
|
+
return sanitizeUntrustedFileName(fallbackFileName ?? "output.bin", "output.bin");
|
|
19
|
+
}
|
|
20
|
+
function buildSiblingTempPath(targetPath, fallbackFileName) {
|
|
21
|
+
const safeTail = sanitizeUntrustedFileName(path.basename(targetPath), safeFallbackFileName(fallbackFileName));
|
|
22
|
+
return path.join(path.dirname(targetPath), `.fs-safe-output-${process.pid}-${randomUUID()}-${safeTail}.part`);
|
|
23
|
+
}
|
|
24
|
+
function assertStagedFile(pathname, opened, maxBytes) {
|
|
25
|
+
if (pathname.isSymbolicLink() || !pathname.isFile() || !opened.isFile()) {
|
|
26
|
+
throw new FsSafeError("not-file", "sibling-staged output must be a regular file");
|
|
27
|
+
}
|
|
28
|
+
if (!sameFileIdentity(pathname, opened)) {
|
|
29
|
+
throw new FsSafeError("path-mismatch", "sibling-staged output changed while opening");
|
|
30
|
+
}
|
|
31
|
+
if (opened.nlink > 1) {
|
|
32
|
+
throw new FsSafeError("hardlink", "hardlinked sibling-staged output not allowed");
|
|
33
|
+
}
|
|
34
|
+
if (maxBytes !== undefined && opened.size > maxBytes) {
|
|
35
|
+
throw new FsSafeError("too-large", `sibling-staged output exceeds maxBytes (${maxBytes})`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function openPinnedStagedFile(tempPath, maxBytes) {
|
|
39
|
+
const preview = await fs.lstat(tempPath);
|
|
40
|
+
if (preview.isSymbolicLink()) {
|
|
41
|
+
throw new FsSafeError("symlink", "symlink sibling-staged output not allowed");
|
|
42
|
+
}
|
|
43
|
+
const handle = await fs.open(tempPath, OPEN_READ_WRITE_NOFOLLOW);
|
|
44
|
+
try {
|
|
45
|
+
const identity = await handle.stat();
|
|
46
|
+
assertStagedFile(await fs.lstat(tempPath), identity, maxBytes);
|
|
47
|
+
return { handle, identity };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
await handle.close().catch(() => undefined);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function syncFile(handle) {
|
|
55
|
+
try {
|
|
56
|
+
await handle.sync();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error.code !== "EPERM")
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function cleanupTempPath(tempPath, identity) {
|
|
64
|
+
try {
|
|
65
|
+
const current = await fs.lstat(tempPath);
|
|
66
|
+
if (!identity || (!current.isSymbolicLink() && sameFileIdentity(current, identity))) {
|
|
67
|
+
await fs.rm(tempPath, { force: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code !== "ENOENT") {
|
|
72
|
+
// Cleanup is best-effort; the randomized path remains inside the writable boundary.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export async function writeExternalFileViaSibling(params) {
|
|
77
|
+
const finalPath = path.resolve(params.finalPath);
|
|
78
|
+
const parentPath = path.dirname(finalPath);
|
|
79
|
+
const parentGuard = await createAsyncDirectoryGuard(parentPath);
|
|
80
|
+
const tempPath = buildSiblingTempPath(finalPath, params.fallbackFileName);
|
|
81
|
+
const unregisterTempPath = registerTempPathForExit(tempPath);
|
|
82
|
+
let stagedIdentity;
|
|
83
|
+
let renamed = false;
|
|
84
|
+
try {
|
|
85
|
+
const result = await params.write(tempPath);
|
|
86
|
+
const pinned = await openPinnedStagedFile(tempPath, params.maxBytes);
|
|
87
|
+
stagedIdentity = pinned.identity;
|
|
88
|
+
unregisterTempPath.setIdentity(stagedIdentity);
|
|
89
|
+
try {
|
|
90
|
+
if (params.mode !== undefined) {
|
|
91
|
+
await pinned.handle.chmod(params.mode);
|
|
92
|
+
}
|
|
93
|
+
await syncFile(pinned.handle);
|
|
94
|
+
await serializePathWrite(finalPath, async () => {
|
|
95
|
+
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
96
|
+
const currentIdentity = await pinned.handle.stat();
|
|
97
|
+
assertStagedFile(await fs.lstat(tempPath), currentIdentity, params.maxBytes);
|
|
98
|
+
if (!sameFileIdentity(currentIdentity, stagedIdentity)) {
|
|
99
|
+
throw new FsSafeError("path-mismatch", "sibling-staged output changed before rename");
|
|
100
|
+
}
|
|
101
|
+
await fs.rename(tempPath, finalPath);
|
|
102
|
+
renamed = true;
|
|
103
|
+
const published = await fs.lstat(finalPath);
|
|
104
|
+
if (published.isSymbolicLink() || !sameFileIdentity(published, currentIdentity)) {
|
|
105
|
+
throw new FsSafeError("path-mismatch", "sibling-staged output changed during rename");
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
await syncDirectoryBestEffort(parentPath);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await pinned.handle.close().catch(() => undefined);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
if (!renamed) {
|
|
118
|
+
await cleanupTempPath(tempPath, stagedIdentity);
|
|
119
|
+
}
|
|
120
|
+
unregisterTempPath();
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/output.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type ExternalFileWriteOptions<T = void> = {
|
|
|
4
4
|
write: (filePath: string) => Promise<T>;
|
|
5
5
|
maxBytes?: number;
|
|
6
6
|
mode?: number;
|
|
7
|
+
staging?: "workspace" | "sibling";
|
|
8
|
+
fallbackFileName?: string;
|
|
7
9
|
};
|
|
8
10
|
export type ExternalFileWriteResult<T = void> = {
|
|
9
11
|
path: string;
|
package/dist/output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,wBAAwB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAyDF,wBAAsB,2BAA2B,CAAC,CAAC,GAAG,IAAI,EACxD,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CA+CrC"}
|
package/dist/output.js
CHANGED
|
@@ -2,10 +2,21 @@ import path from "node:path";
|
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { sanitizeUntrustedFileName } from "./filename.js";
|
|
4
4
|
import { isPathInside } from "./path.js";
|
|
5
|
+
import { writeExternalFileViaSibling } from "./output-sibling.js";
|
|
5
6
|
import { root } from "./root.js";
|
|
6
7
|
import { tempFile } from "./temp-target.js";
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const NON_PORTABLE_FILE_NAME_CHARACTERS = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/u;
|
|
9
|
+
function tempFileNameForTarget(targetPath, fallbackFileName) {
|
|
10
|
+
const fallback = sanitizeUntrustedFileName(fallbackFileName ?? "output.bin", "output.bin");
|
|
11
|
+
return sanitizeUntrustedFileName(path.basename(targetPath), fallback);
|
|
12
|
+
}
|
|
13
|
+
function sanitizedTargetPath(targetPath, fallbackFileName) {
|
|
14
|
+
const basename = path.basename(targetPath);
|
|
15
|
+
if (!NON_PORTABLE_FILE_NAME_CHARACTERS.test(basename)) {
|
|
16
|
+
return targetPath;
|
|
17
|
+
}
|
|
18
|
+
const sanitized = tempFileNameForTarget(targetPath, fallbackFileName);
|
|
19
|
+
return sanitized === basename ? targetPath : path.join(path.dirname(targetPath), sanitized);
|
|
9
20
|
}
|
|
10
21
|
function ensureTrailingSep(value) {
|
|
11
22
|
return value.endsWith(path.sep) ? value : `${value}${path.sep}`;
|
|
@@ -43,16 +54,32 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
43
54
|
throw new FsSafeError("invalid-path", "target path is required");
|
|
44
55
|
}
|
|
45
56
|
assertFileTargetPath(requestedTargetPath);
|
|
46
|
-
const
|
|
57
|
+
const rawTargetPath = toRootPathInput({
|
|
47
58
|
rootDir: targetRoot.rootDir,
|
|
48
59
|
rootReal: targetRoot.rootReal,
|
|
49
60
|
targetPath: requestedTargetPath,
|
|
50
61
|
});
|
|
51
|
-
assertFileTargetPath(
|
|
62
|
+
assertFileTargetPath(rawTargetPath);
|
|
63
|
+
const targetPath = sanitizedTargetPath(rawTargetPath, options.fallbackFileName);
|
|
52
64
|
const finalPath = await targetRoot.resolve(targetPath);
|
|
65
|
+
if (options.staging === "sibling") {
|
|
66
|
+
const parentPath = path.dirname(targetPath);
|
|
67
|
+
if (parentPath !== ".") {
|
|
68
|
+
await targetRoot.mkdir(parentPath);
|
|
69
|
+
}
|
|
70
|
+
const siblingFinalPath = await targetRoot.resolve(targetPath);
|
|
71
|
+
const result = await writeExternalFileViaSibling({
|
|
72
|
+
finalPath: siblingFinalPath,
|
|
73
|
+
write: options.write,
|
|
74
|
+
fallbackFileName: options.fallbackFileName,
|
|
75
|
+
maxBytes: options.maxBytes,
|
|
76
|
+
mode: options.mode,
|
|
77
|
+
});
|
|
78
|
+
return { path: siblingFinalPath, result };
|
|
79
|
+
}
|
|
53
80
|
const staged = await tempFile({
|
|
54
81
|
prefix: "fs-safe-output",
|
|
55
|
-
fileName: tempFileNameForTarget(targetPath),
|
|
82
|
+
fileName: tempFileNameForTarget(targetPath, options.fallbackFileName),
|
|
56
83
|
});
|
|
57
84
|
try {
|
|
58
85
|
const result = await options.write(staged.path);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type WindowsAceFlags = {
|
|
2
|
+
raw: number;
|
|
3
|
+
objectInherit: boolean;
|
|
4
|
+
containerInherit: boolean;
|
|
5
|
+
noPropagateInherit: boolean;
|
|
6
|
+
inheritOnly: boolean;
|
|
7
|
+
inherited: boolean;
|
|
8
|
+
successfulAccess: boolean;
|
|
9
|
+
failedAccess: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type WindowsAccessControlEntry = {
|
|
12
|
+
sid: string;
|
|
13
|
+
mask: number;
|
|
14
|
+
aceType: "allow" | "deny";
|
|
15
|
+
flags: WindowsAceFlags;
|
|
16
|
+
};
|
|
17
|
+
export type OwnerAndDaclResult = {
|
|
18
|
+
status: "supported";
|
|
19
|
+
ownerSid: string;
|
|
20
|
+
currentUserSid: string;
|
|
21
|
+
daclPresent: boolean;
|
|
22
|
+
isLocal: boolean;
|
|
23
|
+
complete: boolean;
|
|
24
|
+
unsupportedAceTypes: number[];
|
|
25
|
+
aces: WindowsAccessControlEntry[];
|
|
26
|
+
} | {
|
|
27
|
+
status: "unsupported-platform";
|
|
28
|
+
platform: NodeJS.Platform;
|
|
29
|
+
};
|
|
30
|
+
export declare function readOwnerAndDacl(targetPath: string): OwnerAndDaclResult;
|
|
31
|
+
//# sourceMappingURL=owner-dacl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACnC,GACD;IACE,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,CAAC;AAEN,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CA4BvE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { getNativeBinding } from "./native.js";
|
|
3
|
+
export function readOwnerAndDacl(targetPath) {
|
|
4
|
+
if (process.platform !== "win32") {
|
|
5
|
+
return { status: "unsupported-platform", platform: process.platform };
|
|
6
|
+
}
|
|
7
|
+
const native = getNativeBinding();
|
|
8
|
+
if (!native) {
|
|
9
|
+
throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require the bundled native binding");
|
|
10
|
+
}
|
|
11
|
+
const facts = native.readOwnerAndDacl(targetPath);
|
|
12
|
+
return {
|
|
13
|
+
status: "supported",
|
|
14
|
+
ownerSid: facts.ownerSid,
|
|
15
|
+
currentUserSid: facts.currentUserSid,
|
|
16
|
+
daclPresent: facts.daclPresent,
|
|
17
|
+
isLocal: facts.isLocal,
|
|
18
|
+
complete: facts.aceListComplete,
|
|
19
|
+
unsupportedAceTypes: [...facts.unsupportedAceTypes],
|
|
20
|
+
aces: facts.aces.map((entry) => ({
|
|
21
|
+
sid: entry.sid,
|
|
22
|
+
mask: entry.mask,
|
|
23
|
+
aceType: entry.aceType,
|
|
24
|
+
flags: { ...entry.flags },
|
|
25
|
+
})),
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/path-policy.js
CHANGED
|
@@ -15,7 +15,7 @@ export async function assertNoPathAliasEscape(params) {
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
await assertNoHardlinkedFinalPath({
|
|
18
|
-
filePath: resolved.
|
|
18
|
+
filePath: resolved.canonicalPath,
|
|
19
19
|
root: resolved.rootPath,
|
|
20
20
|
boundaryLabel: params.boundaryLabel,
|
|
21
21
|
allowFinalHardlinkForUnlink: params.policy?.allowFinalHardlinkForUnlink,
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, type PermissionCheck, type PermissionCheckOptions, type SafeStatResult, } from "./permissions.js";
|
|
2
|
+
export { createPrivateDirectory, type CreatePrivateDirectoryOptions, } from "./private-directory.js";
|
|
3
|
+
export { readOwnerAndDacl, type OwnerAndDaclResult, type WindowsAccessControlEntry, type WindowsAceFlags, } from "./owner-dacl.js";
|
|
2
4
|
//# sourceMappingURL=permissions-public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions-public.d.ts","sourceRoot":"","sources":["../src/permissions-public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,2BAA2B,EAC3B,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"permissions-public.d.ts","sourceRoot":"","sources":["../src/permissions-public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,2BAA2B,EAC3B,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC"}
|
|
@@ -1 +1,3 @@
|
|
|
1
1
|
export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, } from "./permissions.js";
|
|
2
|
+
export { createPrivateDirectory, } from "./private-directory.js";
|
|
3
|
+
export { readOwnerAndDacl, } from "./owner-dacl.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
|
package/dist/permissions.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
7
|
+
import { inspectWindowsPermissionsNative } from "./windows-permissions-native.js";
|
|
7
8
|
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
8
9
|
import { inspectWindowsOwner, resolveWindowsCurrentUserSid, resolveWindowsPrincipalSids, } from "./windows-owner.js";
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
@@ -102,6 +103,11 @@ export async function inspectPathPermissions(targetPath, opts) {
|
|
|
102
103
|
const bits = modeBits(effectiveMode);
|
|
103
104
|
const platform = opts?.platform ?? process.platform;
|
|
104
105
|
if (platform === "win32") {
|
|
106
|
+
const native = inspectWindowsPermissionsNative({
|
|
107
|
+
targetPath, stat: st, effectiveIsDir, effectiveMode, bits,
|
|
108
|
+
});
|
|
109
|
+
if (native)
|
|
110
|
+
return native;
|
|
105
111
|
const owner = await inspectWindowsOwner({
|
|
106
112
|
targetPath,
|
|
107
113
|
env: opts?.env,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinned-operation.d.ts","sourceRoot":"","sources":["../src/pinned-operation.ts"],"names":[],"mappings":"AAEA,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAarF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export function validatePinnedOperationPayload(payload) {
|
|
3
|
+
if (typeof payload.relativePath === "string") {
|
|
4
|
+
validatePinnedRelativePath(payload.relativePath);
|
|
5
|
+
}
|
|
6
|
+
if (typeof payload.relativeParentPath === "string") {
|
|
7
|
+
validatePinnedRelativePath(payload.relativeParentPath);
|
|
8
|
+
}
|
|
9
|
+
if (typeof payload.from === "string") {
|
|
10
|
+
validatePinnedRelativePath(payload.from);
|
|
11
|
+
}
|
|
12
|
+
if (typeof payload.to === "string") {
|
|
13
|
+
validatePinnedRelativePath(payload.to);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function validatePinnedRelativePath(relativePath) {
|
|
17
|
+
if (relativePath.length === 0 || relativePath === ".") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (relativePath.includes("\0")) {
|
|
21
|
+
throw new FsSafeError("invalid-path", "relative path contains a NUL byte");
|
|
22
|
+
}
|
|
23
|
+
if (relativePath.startsWith("/") ||
|
|
24
|
+
relativePath.startsWith("//") ||
|
|
25
|
+
relativePath === ".." ||
|
|
26
|
+
relativePath.startsWith("../") ||
|
|
27
|
+
relativePath.startsWith("..\\")) {
|
|
28
|
+
throw new FsSafeError("invalid-path", "relative path must not escape root");
|
|
29
|
+
}
|
|
30
|
+
for (const segment of relativePath.split("/")) {
|
|
31
|
+
if (segment === "..") {
|
|
32
|
+
throw new FsSafeError("invalid-path", "relative path must not contain '..'");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/dist/pinned-write.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Readable } from "node:stream";
|
|
2
2
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
3
|
-
type PinnedWriteInput = {
|
|
3
|
+
export type PinnedWriteInput = {
|
|
4
4
|
kind: "buffer";
|
|
5
5
|
data: string | Buffer;
|
|
6
6
|
encoding?: BufferEncoding;
|
|
@@ -8,9 +8,9 @@ type PinnedWriteInput = {
|
|
|
8
8
|
kind: "stream";
|
|
9
9
|
stream: Readable;
|
|
10
10
|
};
|
|
11
|
-
type RenameIdentityMismatchPolicy = "throw" | "verify-content";
|
|
11
|
+
export type RenameIdentityMismatchPolicy = "throw" | "verify-content";
|
|
12
12
|
export type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
|
|
13
|
-
type PinnedWriteParams = {
|
|
13
|
+
export type PinnedWriteParams = {
|
|
14
14
|
rootPath: string;
|
|
15
15
|
relativeParentPath: string;
|
|
16
16
|
basename: string;
|
|
@@ -27,17 +27,4 @@ export declare function runPinnedWriteWithRenamePolicy(params: PinnedWriteParams
|
|
|
27
27
|
targetPath: string;
|
|
28
28
|
renameIdentity?: RenameIdentityPolicy;
|
|
29
29
|
}): Promise<FileIdentityStat>;
|
|
30
|
-
export declare function runPinnedCopyHelper(params: {
|
|
31
|
-
rootPath: string;
|
|
32
|
-
relativeParentPath: string;
|
|
33
|
-
basename: string;
|
|
34
|
-
mkdir: boolean;
|
|
35
|
-
mode: number;
|
|
36
|
-
overwrite?: boolean;
|
|
37
|
-
maxBytes?: number;
|
|
38
|
-
sourcePath: string;
|
|
39
|
-
sourceIdentity: FileIdentityStat;
|
|
40
|
-
rootIdentity?: FileIdentityStat;
|
|
41
|
-
}): Promise<FileIdentityStat>;
|
|
42
|
-
export {};
|
|
43
30
|
//# sourceMappingURL=pinned-write.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAU3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAgEzC,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;CACzD,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
|