@openclaw/fs-safe 0.2.4 → 0.2.5

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 (90) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +2 -0
  3. package/dist/archive-staging.d.ts.map +1 -1
  4. package/dist/archive-staging.js +12 -1
  5. package/dist/file-store.d.ts.map +1 -1
  6. package/dist/file-store.js +6 -5
  7. package/dist/json-document-store.d.ts +1 -1
  8. package/dist/json-document-store.d.ts.map +1 -1
  9. package/dist/json-document-store.js +5 -3
  10. package/dist/json-durable-queue.d.ts.map +1 -1
  11. package/dist/json-durable-queue.js +157 -4
  12. package/dist/json-stringify.d.ts +2 -0
  13. package/dist/json-stringify.d.ts.map +1 -0
  14. package/dist/json-stringify.js +7 -0
  15. package/dist/json.d.ts.map +1 -1
  16. package/dist/json.js +3 -2
  17. package/dist/local-roots.d.ts.map +1 -1
  18. package/dist/local-roots.js +4 -1
  19. package/dist/path.js +1 -1
  20. package/dist/permissions.d.ts.map +1 -1
  21. package/dist/permissions.js +7 -10
  22. package/dist/pinned-python.d.ts.map +1 -1
  23. package/dist/pinned-python.js +125 -43
  24. package/dist/root-impl.d.ts.map +1 -1
  25. package/dist/root-impl.js +6 -2
  26. package/dist/root-paths.js +25 -3
  27. package/dist/secure-file.d.ts.map +1 -1
  28. package/dist/secure-file.js +3 -1
  29. package/dist/timing.d.ts.map +1 -1
  30. package/dist/timing.js +8 -1
  31. package/docs/index.md +1 -1
  32. package/docs/json-store.md +3 -1
  33. package/package.json +4 -4
  34. package/dist/archive-utils.d.ts +0 -3
  35. package/dist/archive-utils.d.ts.map +0 -1
  36. package/dist/archive-utils.js +0 -26
  37. package/dist/boundary-file-read.d.ts +0 -44
  38. package/dist/boundary-file-read.d.ts.map +0 -1
  39. package/dist/boundary-file-read.js +0 -129
  40. package/dist/boundary-path.d.ts +0 -39
  41. package/dist/boundary-path.d.ts.map +0 -1
  42. package/dist/boundary-path.js +0 -598
  43. package/dist/fs-pinned-path-helper.d.ts +0 -7
  44. package/dist/fs-pinned-path-helper.d.ts.map +0 -1
  45. package/dist/fs-pinned-path-helper.js +0 -182
  46. package/dist/fs-pinned-write-helper.d.ts +0 -21
  47. package/dist/fs-pinned-write-helper.d.ts.map +0 -1
  48. package/dist/fs-pinned-write-helper.js +0 -263
  49. package/dist/hardlink-guards.d.ts +0 -7
  50. package/dist/hardlink-guards.d.ts.map +0 -1
  51. package/dist/hardlink-guards.js +0 -30
  52. package/dist/install-safe-path.d.ts +0 -20
  53. package/dist/install-safe-path.d.ts.map +0 -1
  54. package/dist/install-safe-path.js +0 -94
  55. package/dist/json-file.d.ts +0 -3
  56. package/dist/json-file.d.ts.map +0 -1
  57. package/dist/json-file.js +0 -123
  58. package/dist/json-files.d.ts +0 -20
  59. package/dist/json-files.d.ts.map +0 -1
  60. package/dist/json-files.js +0 -153
  61. package/dist/path-alias-guards.d.ts +0 -19
  62. package/dist/path-alias-guards.d.ts.map +0 -1
  63. package/dist/path-alias-guards.js +0 -21
  64. package/dist/path-guards.d.ts +0 -7
  65. package/dist/path-guards.d.ts.map +0 -1
  66. package/dist/path-guards.js +0 -49
  67. package/dist/path-safety.d.ts +0 -12
  68. package/dist/path-safety.d.ts.map +0 -1
  69. package/dist/path-safety.js +0 -50
  70. package/dist/path-scope.d.ts +0 -2
  71. package/dist/path-scope.d.ts.map +0 -1
  72. package/dist/path-scope.js +0 -1
  73. package/dist/private-file-store.d.ts +0 -60
  74. package/dist/private-file-store.d.ts.map +0 -1
  75. package/dist/private-file-store.js +0 -233
  76. package/dist/safe-open-sync.d.ts +0 -24
  77. package/dist/safe-open-sync.d.ts.map +0 -1
  78. package/dist/safe-open-sync.js +0 -71
  79. package/dist/safe-root.d.ts +0 -123
  80. package/dist/safe-root.d.ts.map +0 -1
  81. package/dist/safe-root.js +0 -1060
  82. package/dist/secure-temp-workspace.d.ts +0 -25
  83. package/dist/secure-temp-workspace.d.ts.map +0 -1
  84. package/dist/secure-temp-workspace.js +0 -136
  85. package/dist/sibling-temp-file.d.ts +0 -16
  86. package/dist/sibling-temp-file.d.ts.map +0 -1
  87. package/dist/sibling-temp-file.js +0 -73
  88. package/dist/sibling-temp-write.d.ts +0 -8
  89. package/dist/sibling-temp-write.d.ts.map +0 -1
  90. package/dist/sibling-temp-write.js +0 -40
@@ -1,25 +0,0 @@
1
- export type SecureTempWorkspaceOptions = {
2
- rootDir: string;
3
- prefix: string;
4
- dirMode?: number;
5
- fileMode?: number;
6
- };
7
- export type SecureTempWorkspace = {
8
- dir: string;
9
- path(fileName: string): string;
10
- writePrivate(fileName: string, data: string | Uint8Array): Promise<string>;
11
- read(fileName: string): Promise<Buffer>;
12
- cleanup(): Promise<void>;
13
- };
14
- export type SecureTempWorkspaceSync = {
15
- dir: string;
16
- path(fileName: string): string;
17
- writePrivate(fileName: string, data: string | Uint8Array): string;
18
- read(fileName: string): Buffer;
19
- cleanup(): void;
20
- };
21
- export declare function createSecureTempWorkspace(options: SecureTempWorkspaceOptions): Promise<SecureTempWorkspace>;
22
- export declare function withSecureTempWorkspace<T>(options: SecureTempWorkspaceOptions, run: (workspace: SecureTempWorkspace) => Promise<T>): Promise<T>;
23
- export declare function createSecureTempWorkspaceSync(options: SecureTempWorkspaceOptions): SecureTempWorkspaceSync;
24
- export declare function withSecureTempWorkspaceSync<T>(options: SecureTempWorkspaceOptions, run: (workspace: SecureTempWorkspaceSync) => T): T;
25
- //# sourceMappingURL=secure-temp-workspace.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"secure-temp-workspace.d.ts","sourceRoot":"","sources":["../src/secure-temp-workspace.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3E,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAClE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAgDF,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,mBAAmB,CAAC,CA0B9B;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAC7C,OAAO,EAAE,0BAA0B,EACnC,GAAG,EAAE,CAAC,SAAS,EAAE,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,GAClD,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,0BAA0B,GAClC,uBAAuB,CAsCzB;AAED,wBAAgB,2BAA2B,CAAC,CAAC,EAC3C,OAAO,EAAE,0BAA0B,EACnC,GAAG,EAAE,CAAC,SAAS,EAAE,uBAAuB,KAAK,CAAC,GAC7C,CAAC,CAUH"}
@@ -1,136 +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
- function sanitizeTempPrefix(prefix) {
6
- const sanitized = prefix.trim().replace(/[^a-zA-Z0-9._-]/g, "-");
7
- if (!sanitized || sanitized === "." || sanitized === "..") {
8
- return "fs-safe-";
9
- }
10
- return sanitized.endsWith("-") ? sanitized : `${sanitized}-`;
11
- }
12
- function resolveWorkspaceLeaf(dir, fileName) {
13
- const raw = fileName.trim();
14
- if (!raw ||
15
- raw === "." ||
16
- raw === ".." ||
17
- raw.includes("\0") ||
18
- raw.includes("/") ||
19
- raw.includes("\\") ||
20
- path.basename(raw) !== raw) {
21
- throw new Error(`Invalid temp workspace file name: ${JSON.stringify(fileName)}`);
22
- }
23
- return path.join(dir, raw);
24
- }
25
- async function ensurePrivateDirectory(dir, mode) {
26
- await fs.mkdir(dir, { recursive: true, mode });
27
- const stat = await fs.lstat(dir);
28
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
29
- throw new Error(`Temp root must be a directory: ${dir}`);
30
- }
31
- await fs.chmod(dir, mode).catch(() => undefined);
32
- }
33
- function ensurePrivateDirectorySync(dir, mode) {
34
- fsSync.mkdirSync(dir, { recursive: true, mode });
35
- const stat = fsSync.lstatSync(dir);
36
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
37
- throw new Error(`Temp root must be a directory: ${dir}`);
38
- }
39
- try {
40
- fsSync.chmodSync(dir, mode);
41
- }
42
- catch {
43
- // Best-effort on platforms that do not enforce POSIX modes.
44
- }
45
- }
46
- export async function createSecureTempWorkspace(options) {
47
- const dirMode = options.dirMode ?? 0o700;
48
- const fileMode = options.fileMode ?? 0o600;
49
- const root = path.resolve(options.rootDir);
50
- await ensurePrivateDirectory(root, dirMode);
51
- const dir = await fs.mkdtemp(path.join(root, sanitizeTempPrefix(options.prefix)));
52
- await fs.chmod(dir, dirMode).catch(() => undefined);
53
- const stat = await fs.lstat(dir);
54
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
55
- throw new Error(`Temp workspace must be a directory: ${dir}`);
56
- }
57
- return {
58
- dir,
59
- path: (fileName) => resolveWorkspaceLeaf(dir, fileName),
60
- writePrivate: async (fileName, data) => {
61
- const filePath = resolveWorkspaceLeaf(dir, fileName);
62
- await fs.writeFile(filePath, data, { mode: fileMode, flag: "wx" });
63
- await fs.chmod(filePath, fileMode).catch(() => undefined);
64
- return filePath;
65
- },
66
- read: async (fileName) => await fs.readFile(resolveWorkspaceLeaf(dir, fileName)),
67
- cleanup: async () => {
68
- await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
69
- },
70
- };
71
- }
72
- export async function withSecureTempWorkspace(options, run) {
73
- const workspace = await createSecureTempWorkspace({
74
- ...options,
75
- prefix: `${sanitizeTempPrefix(options.prefix)}${randomUUID()}-`,
76
- });
77
- try {
78
- return await run(workspace);
79
- }
80
- finally {
81
- await workspace.cleanup();
82
- }
83
- }
84
- export function createSecureTempWorkspaceSync(options) {
85
- const dirMode = options.dirMode ?? 0o700;
86
- const fileMode = options.fileMode ?? 0o600;
87
- const root = path.resolve(options.rootDir);
88
- ensurePrivateDirectorySync(root, dirMode);
89
- const dir = fsSync.mkdtempSync(path.join(root, sanitizeTempPrefix(options.prefix)));
90
- try {
91
- fsSync.chmodSync(dir, dirMode);
92
- }
93
- catch {
94
- // Best-effort on platforms that do not enforce POSIX modes.
95
- }
96
- const stat = fsSync.lstatSync(dir);
97
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
98
- throw new Error(`Temp workspace must be a directory: ${dir}`);
99
- }
100
- return {
101
- dir,
102
- path: (fileName) => resolveWorkspaceLeaf(dir, fileName),
103
- writePrivate: (fileName, data) => {
104
- const filePath = resolveWorkspaceLeaf(dir, fileName);
105
- fsSync.writeFileSync(filePath, data, { mode: fileMode, flag: "wx" });
106
- try {
107
- fsSync.chmodSync(filePath, fileMode);
108
- }
109
- catch {
110
- // Best-effort on platforms that do not enforce POSIX modes.
111
- }
112
- return filePath;
113
- },
114
- read: (fileName) => fsSync.readFileSync(resolveWorkspaceLeaf(dir, fileName)),
115
- cleanup: () => {
116
- try {
117
- fsSync.rmSync(dir, { recursive: true, force: true });
118
- }
119
- catch {
120
- // Best-effort cleanup.
121
- }
122
- },
123
- };
124
- }
125
- export function withSecureTempWorkspaceSync(options, run) {
126
- const workspace = createSecureTempWorkspaceSync({
127
- ...options,
128
- prefix: `${sanitizeTempPrefix(options.prefix)}${randomUUID()}-`,
129
- });
130
- try {
131
- return run(workspace);
132
- }
133
- finally {
134
- workspace.cleanup();
135
- }
136
- }
@@ -1,16 +0,0 @@
1
- export type WriteSiblingTempFileOptions<T> = {
2
- dir: string;
3
- writeTemp: (tempPath: string) => Promise<T>;
4
- resolveFinalPath: (result: T) => string;
5
- tempPrefix?: string;
6
- dirMode?: number;
7
- fileMode?: number;
8
- syncTempFile?: boolean;
9
- syncParentDir?: boolean;
10
- };
11
- export type WriteSiblingTempFileResult<T> = {
12
- filePath: string;
13
- result: T;
14
- };
15
- export declare function writeSiblingTempFile<T>(options: WriteSiblingTempFileOptions<T>): Promise<WriteSiblingTempFileResult<T>>;
16
- //# sourceMappingURL=sibling-temp-file.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sibling-temp-file.d.ts","sourceRoot":"","sources":["../src/sibling-temp-file.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAuCF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CA+BxC"}
@@ -1,73 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- function buildTempPath(dir, tempPrefix) {
5
- return path.join(dir, `${tempPrefix ?? ".fs-safe-stream"}.${process.pid}.${randomUUID()}.tmp`);
6
- }
7
- async function syncFileBestEffort(filePath) {
8
- const handle = await fs.open(filePath, "r+");
9
- try {
10
- await handle.sync();
11
- }
12
- catch (error) {
13
- if (error.code !== "EPERM") {
14
- throw error;
15
- }
16
- }
17
- finally {
18
- await handle.close();
19
- }
20
- }
21
- async function syncDirectoryBestEffort(dirPath) {
22
- let handle;
23
- try {
24
- handle = await fs.open(dirPath, "r");
25
- await handle.sync();
26
- }
27
- catch {
28
- // Best-effort on platforms/filesystems that do not support directory fsync.
29
- }
30
- finally {
31
- await handle?.close().catch(() => undefined);
32
- }
33
- }
34
- function assertFinalPathIsSibling(dir, filePath) {
35
- const resolvedDir = path.resolve(dir);
36
- const resolvedFile = path.resolve(filePath);
37
- if (path.dirname(resolvedFile) !== resolvedDir) {
38
- throw new Error("Final path must be in the sibling temp directory.");
39
- }
40
- }
41
- export async function writeSiblingTempFile(options) {
42
- const dir = path.resolve(options.dir);
43
- await fs.mkdir(dir, { recursive: true, mode: options.dirMode ?? 0o700 });
44
- await fs.chmod(dir, options.dirMode ?? 0o700).catch(() => undefined);
45
- const tempPath = buildTempPath(dir, options.tempPrefix);
46
- let tempExists = false;
47
- try {
48
- tempExists = true;
49
- const result = await options.writeTemp(tempPath);
50
- if (options.fileMode !== undefined) {
51
- await fs.chmod(tempPath, options.fileMode).catch(() => undefined);
52
- }
53
- if (options.syncTempFile) {
54
- await syncFileBestEffort(tempPath);
55
- }
56
- const filePath = path.resolve(options.resolveFinalPath(result));
57
- assertFinalPathIsSibling(dir, filePath);
58
- await fs.rename(tempPath, filePath);
59
- tempExists = false;
60
- if (options.fileMode !== undefined) {
61
- await fs.chmod(filePath, options.fileMode).catch(() => undefined);
62
- }
63
- if (options.syncParentDir) {
64
- await syncDirectoryBestEffort(dir);
65
- }
66
- return { filePath, result };
67
- }
68
- finally {
69
- if (tempExists) {
70
- await fs.rm(tempPath, { force: true }).catch(() => undefined);
71
- }
72
- }
73
- }
@@ -1,8 +0,0 @@
1
- export declare function writeViaSiblingTempPath(params: {
2
- rootDir: string;
3
- targetPath: string;
4
- writeTemp: (tempPath: string) => Promise<void>;
5
- fallbackFileName?: string;
6
- tempPrefix?: string;
7
- }): Promise<void>;
8
- //# sourceMappingURL=sibling-temp-write.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sibling-temp-write.d.ts","sourceRoot":"","sources":["../src/sibling-temp-write.ts"],"names":[],"mappings":"AAmBA,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
@@ -1,40 +0,0 @@
1
- import crypto from "node:crypto";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { sanitizeUntrustedFileName } from "./filename.js";
5
- import { safeRoot } from "./safe-root.js";
6
- function buildSiblingTempPath(params) {
7
- const id = crypto.randomUUID();
8
- const safeTail = sanitizeUntrustedFileName(path.basename(params.targetPath), params.fallbackFileName);
9
- return path.join(path.dirname(params.targetPath), `${params.tempPrefix}${id}-${safeTail}.part`);
10
- }
11
- export async function writeViaSiblingTempPath(params) {
12
- const rootDir = await fs
13
- .realpath(path.resolve(params.rootDir))
14
- .catch(() => path.resolve(params.rootDir));
15
- const requestedTargetPath = path.resolve(params.targetPath);
16
- const targetPath = await fs
17
- .realpath(path.dirname(requestedTargetPath))
18
- .then((realDir) => path.join(realDir, path.basename(requestedTargetPath)))
19
- .catch(() => requestedTargetPath);
20
- const relativeTargetPath = path.relative(rootDir, targetPath);
21
- if (!relativeTargetPath ||
22
- relativeTargetPath === ".." ||
23
- relativeTargetPath.startsWith(`..${path.sep}`) ||
24
- path.isAbsolute(relativeTargetPath)) {
25
- throw new Error("Target path is outside the allowed root");
26
- }
27
- const tempPath = buildSiblingTempPath({
28
- targetPath,
29
- fallbackFileName: params.fallbackFileName ?? "output.bin",
30
- tempPrefix: params.tempPrefix ?? ".fs-safe-output-",
31
- });
32
- try {
33
- await params.writeTemp(tempPath);
34
- const root = await safeRoot(rootDir);
35
- await root.copyFileFrom(tempPath, relativeTargetPath, { mkdir: false });
36
- }
37
- finally {
38
- await fs.rm(tempPath, { force: true }).catch(() => { });
39
- }
40
- }