@porulle/adapter-local-storage 0.10.3 → 0.10.7
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/README.md +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -8
- package/package.json +4 -4
- package/src/index.ts +12 -8
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ Every key passes through `resolveSafePath()` which:
|
|
|
39
39
|
- Verifies the result stays inside `basePath`
|
|
40
40
|
- Rejects `..`, NUL bytes, absolute paths, and any input that would escape the storage root
|
|
41
41
|
|
|
42
|
-
This closed a CVE-class arbitrary-file-write surface (a key
|
|
42
|
+
This closed a CVE-class arbitrary-file-write surface (a key that walks up out of the root would otherwise write outside it). See `packages/core/test/` for the regression tests.
|
|
43
43
|
|
|
44
44
|
## What it implements
|
|
45
45
|
|
|
@@ -47,5 +47,5 @@ This closed a CVE-class arbitrary-file-write surface (a key like `../../../../et
|
|
|
47
47
|
|
|
48
48
|
## See also
|
|
49
49
|
|
|
50
|
-
- [`SECURITY.md`](
|
|
50
|
+
- [`SECURITY.md`](https://github.com/asyncdotengineering/porulle/blob/main/SECURITY.md) — storage hardening
|
|
51
51
|
- `@porulle/adapter-s3`, `@porulle/adapter-r2` — production storage adapters
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAS1E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiCD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAS1E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiCD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,CAiFvF"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { mkdir, readdir,
|
|
1
|
+
import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve, sep } from "node:path";
|
|
3
3
|
import { Err, Ok } from "@porulle/core";
|
|
4
4
|
/**
|
|
5
5
|
* Resolves a key against basePath and verifies the result stays inside
|
|
6
|
-
* basePath. Rejects
|
|
7
|
-
*
|
|
6
|
+
* basePath. Rejects parent-directory traversal, absolute paths, and any
|
|
7
|
+
* other input that would escape the storage root.
|
|
8
8
|
*
|
|
9
|
-
* Without this guard an upload
|
|
10
|
-
* outside
|
|
9
|
+
* Without this guard an upload whose key walks up out of the root would
|
|
10
|
+
* write outside it (CVE-class: arbitrary file write).
|
|
11
11
|
*/
|
|
12
12
|
function resolveSafePath(basePath, key) {
|
|
13
13
|
if (typeof key !== "string" || key.length === 0) {
|
|
@@ -67,7 +67,14 @@ export function localStorageAdapter(options) {
|
|
|
67
67
|
},
|
|
68
68
|
async delete(key) {
|
|
69
69
|
const path = resolveSafePath(options.basePath, key);
|
|
70
|
-
|
|
70
|
+
try {
|
|
71
|
+
await unlink(path);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// Deleting an absent key stays a no-op, as it was under rm({ force: true }).
|
|
75
|
+
if (error.code !== "ENOENT")
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
71
78
|
return Ok(undefined);
|
|
72
79
|
},
|
|
73
80
|
async list(prefix) {
|
|
@@ -80,12 +87,11 @@ export function localStorageAdapter(options) {
|
|
|
80
87
|
const info = await stat(filePath);
|
|
81
88
|
if (info.isDirectory())
|
|
82
89
|
continue;
|
|
83
|
-
const content = await readFile(filePath);
|
|
84
90
|
output.push({
|
|
85
91
|
key: file,
|
|
86
92
|
url: `${baseUrl}/${prefix}/${file}`,
|
|
87
93
|
contentType: "application/octet-stream",
|
|
88
|
-
size:
|
|
94
|
+
size: info.size,
|
|
89
95
|
});
|
|
90
96
|
}
|
|
91
97
|
return Ok(output);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/adapter-local-storage",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@porulle/core": "0.10.
|
|
14
|
+
"@porulle/core": "0.10.8"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^24.5.2",
|
|
18
18
|
"eslint": "^9.39.1",
|
|
19
19
|
"typescript": "5.9.2",
|
|
20
20
|
"vitest": "^3.2.4",
|
|
21
|
-
"@porulle/
|
|
22
|
-
"@porulle/
|
|
21
|
+
"@porulle/typescript-config": "0.1.0",
|
|
22
|
+
"@porulle/eslint-config": "0.1.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readdir,
|
|
1
|
+
import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve, sep } from "node:path";
|
|
3
3
|
import { Err, Ok, type Result, type StorageAdapter } from "@porulle/core";
|
|
4
4
|
|
|
@@ -16,11 +16,11 @@ export interface LocalStorageAdapterOptions {
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Resolves a key against basePath and verifies the result stays inside
|
|
19
|
-
* basePath. Rejects
|
|
20
|
-
*
|
|
19
|
+
* basePath. Rejects parent-directory traversal, absolute paths, and any
|
|
20
|
+
* other input that would escape the storage root.
|
|
21
21
|
*
|
|
22
|
-
* Without this guard an upload
|
|
23
|
-
* outside
|
|
22
|
+
* Without this guard an upload whose key walks up out of the root would
|
|
23
|
+
* write outside it (CVE-class: arbitrary file write).
|
|
24
24
|
*/
|
|
25
25
|
function resolveSafePath(basePath: string, key: string): string {
|
|
26
26
|
if (typeof key !== "string" || key.length === 0) {
|
|
@@ -89,7 +89,12 @@ export function localStorageAdapter(options: LocalStorageAdapterOptions): Storag
|
|
|
89
89
|
|
|
90
90
|
async delete(key: string): Promise<Result<void>> {
|
|
91
91
|
const path = resolveSafePath(options.basePath, key);
|
|
92
|
-
|
|
92
|
+
try {
|
|
93
|
+
await unlink(path);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Deleting an absent key stays a no-op, as it was under rm({ force: true }).
|
|
96
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
97
|
+
}
|
|
93
98
|
return Ok(undefined);
|
|
94
99
|
},
|
|
95
100
|
|
|
@@ -104,12 +109,11 @@ export function localStorageAdapter(options: LocalStorageAdapterOptions): Storag
|
|
|
104
109
|
const info = await stat(filePath);
|
|
105
110
|
if (info.isDirectory()) continue;
|
|
106
111
|
|
|
107
|
-
const content = await readFile(filePath);
|
|
108
112
|
output.push({
|
|
109
113
|
key: file,
|
|
110
114
|
url: `${baseUrl}/${prefix}/${file}`,
|
|
111
115
|
contentType: "application/octet-stream",
|
|
112
|
-
size:
|
|
116
|
+
size: info.size,
|
|
113
117
|
});
|
|
114
118
|
}
|
|
115
119
|
|