@aigne/afs-provider-utils 1.11.0-beta.12
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/LICENSE.md +26 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +5 -0
- package/dist/local-path.cjs +17 -0
- package/dist/local-path.d.cts +8 -0
- package/dist/local-path.d.cts.map +1 -0
- package/dist/local-path.d.mts +8 -0
- package/dist/local-path.d.mts.map +1 -0
- package/dist/local-path.mjs +18 -0
- package/dist/local-path.mjs.map +1 -0
- package/dist/mime.cjs +53 -0
- package/dist/mime.d.cts +6 -0
- package/dist/mime.d.cts.map +1 -0
- package/dist/mime.d.mts +6 -0
- package/dist/mime.d.mts.map +1 -0
- package/dist/mime.mjs +53 -0
- package/dist/mime.mjs.map +1 -0
- package/dist/path-guard.cjs +34 -0
- package/dist/path-guard.d.cts +9 -0
- package/dist/path-guard.d.cts.map +1 -0
- package/dist/path-guard.d.mts +9 -0
- package/dist/path-guard.d.mts.map +1 -0
- package/dist/path-guard.mjs +35 -0
- package/dist/path-guard.mjs.map +1 -0
- package/package.json +54 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. Unauthorized copying, modification, distribution, or use of
|
|
7
|
+
this Software, via any medium, is strictly prohibited.
|
|
8
|
+
|
|
9
|
+
The Software is provided for internal use only within ArcBlock, Inc. and its
|
|
10
|
+
authorized affiliates.
|
|
11
|
+
|
|
12
|
+
## No License Granted
|
|
13
|
+
|
|
14
|
+
No license, express or implied, is granted to any party for any purpose.
|
|
15
|
+
All rights are reserved by ArcBlock, Inc.
|
|
16
|
+
|
|
17
|
+
## Public Artifact Distribution
|
|
18
|
+
|
|
19
|
+
Portions of this Software may be released publicly under separate open-source
|
|
20
|
+
licenses (such as MIT License) through designated public repositories. Such
|
|
21
|
+
public releases are governed by their respective licenses and do not affect
|
|
22
|
+
the proprietary nature of this repository.
|
|
23
|
+
|
|
24
|
+
## Contact
|
|
25
|
+
|
|
26
|
+
For licensing inquiries, contact: legal@arcblock.io
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const require_local_path = require('./local-path.cjs');
|
|
2
|
+
const require_mime = require('./mime.cjs');
|
|
3
|
+
const require_path_guard = require('./path-guard.cjs');
|
|
4
|
+
|
|
5
|
+
exports.assertPathWithinRoot = require_path_guard.assertPathWithinRoot;
|
|
6
|
+
exports.getMimeType = require_mime.getMimeType;
|
|
7
|
+
exports.isBinaryFile = require_mime.isBinaryFile;
|
|
8
|
+
exports.resolveLocalPath = require_local_path.resolveLocalPath;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ResolveLocalPathOptions, resolveLocalPath } from "./local-path.cjs";
|
|
2
|
+
import { getMimeType, isBinaryFile } from "./mime.cjs";
|
|
3
|
+
import { assertPathWithinRoot } from "./path-guard.cjs";
|
|
4
|
+
export { type ResolveLocalPathOptions, assertPathWithinRoot, getMimeType, isBinaryFile, resolveLocalPath };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ResolveLocalPathOptions, resolveLocalPath } from "./local-path.mjs";
|
|
2
|
+
import { getMimeType, isBinaryFile } from "./mime.mjs";
|
|
3
|
+
import { assertPathWithinRoot } from "./path-guard.mjs";
|
|
4
|
+
export { type ResolveLocalPathOptions, assertPathWithinRoot, getMimeType, isBinaryFile, resolveLocalPath };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let node_path = require("node:path");
|
|
2
|
+
|
|
3
|
+
//#region src/local-path.ts
|
|
4
|
+
function resolveLocalPath(rawPath, options) {
|
|
5
|
+
if (rawPath === ".") return process.cwd();
|
|
6
|
+
let resolved = rawPath.replaceAll("${CWD}", process.cwd());
|
|
7
|
+
if (resolved.startsWith("~/")) {
|
|
8
|
+
const home = process.env.HOME;
|
|
9
|
+
if (!home) throw new Error("Cannot resolve '~/' path: HOME environment variable is not set");
|
|
10
|
+
resolved = (0, node_path.join)(home, resolved.slice(2));
|
|
11
|
+
}
|
|
12
|
+
if (!(0, node_path.isAbsolute)(resolved)) resolved = (0, node_path.join)(options?.cwd || process.cwd(), resolved);
|
|
13
|
+
return resolved;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
exports.resolveLocalPath = resolveLocalPath;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/local-path.d.ts
|
|
2
|
+
interface ResolveLocalPathOptions {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
}
|
|
5
|
+
declare function resolveLocalPath(rawPath: string, options?: ResolveLocalPathOptions): string;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { ResolveLocalPathOptions, resolveLocalPath };
|
|
8
|
+
//# sourceMappingURL=local-path.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-path.d.cts","names":[],"sources":["../src/local-path.ts"],"mappings":";UAEiB,uBAAA;EACf,GAAA;AAAA;AAAA,iBAGc,gBAAA,CAAiB,OAAA,UAAiB,OAAA,GAAU,uBAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/local-path.d.ts
|
|
2
|
+
interface ResolveLocalPathOptions {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
}
|
|
5
|
+
declare function resolveLocalPath(rawPath: string, options?: ResolveLocalPathOptions): string;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { ResolveLocalPathOptions, resolveLocalPath };
|
|
8
|
+
//# sourceMappingURL=local-path.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-path.d.mts","names":[],"sources":["../src/local-path.ts"],"mappings":";UAEiB,uBAAA;EACf,GAAA;AAAA;AAAA,iBAGc,gBAAA,CAAiB,OAAA,UAAiB,OAAA,GAAU,uBAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isAbsolute, join } from "node:path";
|
|
2
|
+
|
|
3
|
+
//#region src/local-path.ts
|
|
4
|
+
function resolveLocalPath(rawPath, options) {
|
|
5
|
+
if (rawPath === ".") return process.cwd();
|
|
6
|
+
let resolved = rawPath.replaceAll("${CWD}", process.cwd());
|
|
7
|
+
if (resolved.startsWith("~/")) {
|
|
8
|
+
const home = process.env.HOME;
|
|
9
|
+
if (!home) throw new Error("Cannot resolve '~/' path: HOME environment variable is not set");
|
|
10
|
+
resolved = join(home, resolved.slice(2));
|
|
11
|
+
}
|
|
12
|
+
if (!isAbsolute(resolved)) resolved = join(options?.cwd || process.cwd(), resolved);
|
|
13
|
+
return resolved;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { resolveLocalPath };
|
|
18
|
+
//# sourceMappingURL=local-path.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-path.mjs","names":[],"sources":["../src/local-path.ts"],"sourcesContent":["import { isAbsolute, join } from \"node:path\";\n\nexport interface ResolveLocalPathOptions {\n cwd?: string;\n}\n\nexport function resolveLocalPath(rawPath: string, options?: ResolveLocalPathOptions): string {\n if (rawPath === \".\") {\n return process.cwd();\n }\n\n let resolved = rawPath.replaceAll(\"${CWD}\", process.cwd());\n\n if (resolved.startsWith(\"~/\")) {\n const home = process.env.HOME;\n if (!home) {\n throw new Error(\"Cannot resolve '~/' path: HOME environment variable is not set\");\n }\n resolved = join(home, resolved.slice(2));\n }\n\n if (!isAbsolute(resolved)) {\n resolved = join(options?.cwd || process.cwd(), resolved);\n }\n\n return resolved;\n}\n"],"mappings":";;;AAMA,SAAgB,iBAAiB,SAAiB,SAA2C;AAC3F,KAAI,YAAY,IACd,QAAO,QAAQ,KAAK;CAGtB,IAAI,WAAW,QAAQ,WAAW,UAAU,QAAQ,KAAK,CAAC;AAE1D,KAAI,SAAS,WAAW,KAAK,EAAE;EAC7B,MAAM,OAAO,QAAQ,IAAI;AACzB,MAAI,CAAC,KACH,OAAM,IAAI,MAAM,iEAAiE;AAEnF,aAAW,KAAK,MAAM,SAAS,MAAM,EAAE,CAAC;;AAG1C,KAAI,CAAC,WAAW,SAAS,CACvB,YAAW,KAAK,SAAS,OAAO,QAAQ,KAAK,EAAE,SAAS;AAG1D,QAAO"}
|
package/dist/mime.cjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
let node_path = require("node:path");
|
|
2
|
+
|
|
3
|
+
//#region src/mime.ts
|
|
4
|
+
const MIME_TYPES = {
|
|
5
|
+
png: "image/png",
|
|
6
|
+
jpg: "image/jpeg",
|
|
7
|
+
jpeg: "image/jpeg",
|
|
8
|
+
gif: "image/gif",
|
|
9
|
+
bmp: "image/bmp",
|
|
10
|
+
webp: "image/webp",
|
|
11
|
+
svg: "image/svg+xml",
|
|
12
|
+
ico: "image/x-icon",
|
|
13
|
+
pdf: "application/pdf",
|
|
14
|
+
txt: "text/plain",
|
|
15
|
+
md: "text/markdown",
|
|
16
|
+
js: "text/javascript",
|
|
17
|
+
ts: "text/typescript",
|
|
18
|
+
json: "application/json",
|
|
19
|
+
html: "text/html",
|
|
20
|
+
css: "text/css",
|
|
21
|
+
xml: "text/xml"
|
|
22
|
+
};
|
|
23
|
+
const BINARY_EXTENSIONS = new Set([
|
|
24
|
+
"png",
|
|
25
|
+
"jpg",
|
|
26
|
+
"jpeg",
|
|
27
|
+
"gif",
|
|
28
|
+
"bmp",
|
|
29
|
+
"webp",
|
|
30
|
+
"ico",
|
|
31
|
+
"pdf",
|
|
32
|
+
"zip",
|
|
33
|
+
"tar",
|
|
34
|
+
"gz",
|
|
35
|
+
"exe",
|
|
36
|
+
"dll",
|
|
37
|
+
"so",
|
|
38
|
+
"dylib",
|
|
39
|
+
"wasm"
|
|
40
|
+
]);
|
|
41
|
+
function getExtension(filePath) {
|
|
42
|
+
return (0, node_path.basename)(filePath).split(".").pop()?.toLowerCase() || "";
|
|
43
|
+
}
|
|
44
|
+
function getMimeType(filePath) {
|
|
45
|
+
return MIME_TYPES[getExtension(filePath)] || "application/octet-stream";
|
|
46
|
+
}
|
|
47
|
+
function isBinaryFile(filePath) {
|
|
48
|
+
return BINARY_EXTENSIONS.has(getExtension(filePath));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
exports.getMimeType = getMimeType;
|
|
53
|
+
exports.isBinaryFile = isBinaryFile;
|
package/dist/mime.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.cts","names":[],"sources":["../src/mime.ts"],"mappings":";iBAgDgB,WAAA,CAAY,QAAA;AAAA,iBAIZ,YAAA,CAAa,QAAA"}
|
package/dist/mime.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.mts","names":[],"sources":["../src/mime.ts"],"mappings":";iBAgDgB,WAAA,CAAY,QAAA;AAAA,iBAIZ,YAAA,CAAa,QAAA"}
|
package/dist/mime.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
|
|
3
|
+
//#region src/mime.ts
|
|
4
|
+
const MIME_TYPES = {
|
|
5
|
+
png: "image/png",
|
|
6
|
+
jpg: "image/jpeg",
|
|
7
|
+
jpeg: "image/jpeg",
|
|
8
|
+
gif: "image/gif",
|
|
9
|
+
bmp: "image/bmp",
|
|
10
|
+
webp: "image/webp",
|
|
11
|
+
svg: "image/svg+xml",
|
|
12
|
+
ico: "image/x-icon",
|
|
13
|
+
pdf: "application/pdf",
|
|
14
|
+
txt: "text/plain",
|
|
15
|
+
md: "text/markdown",
|
|
16
|
+
js: "text/javascript",
|
|
17
|
+
ts: "text/typescript",
|
|
18
|
+
json: "application/json",
|
|
19
|
+
html: "text/html",
|
|
20
|
+
css: "text/css",
|
|
21
|
+
xml: "text/xml"
|
|
22
|
+
};
|
|
23
|
+
const BINARY_EXTENSIONS = new Set([
|
|
24
|
+
"png",
|
|
25
|
+
"jpg",
|
|
26
|
+
"jpeg",
|
|
27
|
+
"gif",
|
|
28
|
+
"bmp",
|
|
29
|
+
"webp",
|
|
30
|
+
"ico",
|
|
31
|
+
"pdf",
|
|
32
|
+
"zip",
|
|
33
|
+
"tar",
|
|
34
|
+
"gz",
|
|
35
|
+
"exe",
|
|
36
|
+
"dll",
|
|
37
|
+
"so",
|
|
38
|
+
"dylib",
|
|
39
|
+
"wasm"
|
|
40
|
+
]);
|
|
41
|
+
function getExtension(filePath) {
|
|
42
|
+
return basename(filePath).split(".").pop()?.toLowerCase() || "";
|
|
43
|
+
}
|
|
44
|
+
function getMimeType(filePath) {
|
|
45
|
+
return MIME_TYPES[getExtension(filePath)] || "application/octet-stream";
|
|
46
|
+
}
|
|
47
|
+
function isBinaryFile(filePath) {
|
|
48
|
+
return BINARY_EXTENSIONS.has(getExtension(filePath));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { getMimeType, isBinaryFile };
|
|
53
|
+
//# sourceMappingURL=mime.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.mjs","names":[],"sources":["../src/mime.ts"],"sourcesContent":["import { basename } from \"node:path\";\n\nconst MIME_TYPES: Record<string, string> = {\n // Images\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n bmp: \"image/bmp\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n ico: \"image/x-icon\",\n // Documents\n pdf: \"application/pdf\",\n txt: \"text/plain\",\n md: \"text/markdown\",\n // Code\n js: \"text/javascript\",\n ts: \"text/typescript\",\n json: \"application/json\",\n html: \"text/html\",\n css: \"text/css\",\n xml: \"text/xml\",\n};\n\nconst BINARY_EXTENSIONS = new Set([\n \"png\",\n \"jpg\",\n \"jpeg\",\n \"gif\",\n \"bmp\",\n \"webp\",\n \"ico\",\n \"pdf\",\n \"zip\",\n \"tar\",\n \"gz\",\n \"exe\",\n \"dll\",\n \"so\",\n \"dylib\",\n \"wasm\",\n]);\n\nfunction getExtension(filePath: string): string {\n return basename(filePath).split(\".\").pop()?.toLowerCase() || \"\";\n}\n\nexport function getMimeType(filePath: string): string {\n return MIME_TYPES[getExtension(filePath)] || \"application/octet-stream\";\n}\n\nexport function isBinaryFile(filePath: string): boolean {\n return BINARY_EXTENSIONS.has(getExtension(filePath));\n}\n"],"mappings":";;;AAEA,MAAM,aAAqC;CAEzC,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CAEL,KAAK;CACL,KAAK;CACL,IAAI;CAEJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;CACN;AAED,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,aAAa,UAA0B;AAC9C,QAAO,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,aAAa,IAAI;;AAG/D,SAAgB,YAAY,UAA0B;AACpD,QAAO,WAAW,aAAa,SAAS,KAAK;;AAG/C,SAAgB,aAAa,UAA2B;AACtD,QAAO,kBAAkB,IAAI,aAAa,SAAS,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
let node_path = require("node:path");
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let _aigne_afs = require("@aigne/afs");
|
|
4
|
+
|
|
5
|
+
//#region src/path-guard.ts
|
|
6
|
+
/**
|
|
7
|
+
* Assert that a resolved path stays within the given root directory.
|
|
8
|
+
* Performs both logical path check and realpath-based symlink check.
|
|
9
|
+
*/
|
|
10
|
+
async function assertPathWithinRoot(fullPath, rootDir) {
|
|
11
|
+
const mountRoot = (0, node_path.resolve)(rootDir);
|
|
12
|
+
const resolved = (0, node_path.resolve)(fullPath);
|
|
13
|
+
if (resolved !== mountRoot && !resolved.startsWith(`${mountRoot}/`)) throw new _aigne_afs.AFSError("Path traversal is not allowed", "AFS_PERMISSION_DENIED");
|
|
14
|
+
try {
|
|
15
|
+
let real;
|
|
16
|
+
try {
|
|
17
|
+
real = await (0, node_fs_promises.realpath)(resolved);
|
|
18
|
+
} catch {
|
|
19
|
+
const parent = (0, node_path.dirname)(resolved);
|
|
20
|
+
try {
|
|
21
|
+
real = await (0, node_fs_promises.realpath)(parent);
|
|
22
|
+
} catch {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const realMountRoot = await (0, node_fs_promises.realpath)(mountRoot);
|
|
27
|
+
if (real !== realMountRoot && !real.startsWith(`${realMountRoot}/`)) throw new _aigne_afs.AFSError("Path traversal via symlink is not allowed", "AFS_PERMISSION_DENIED");
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof _aigne_afs.AFSError) throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
exports.assertPathWithinRoot = assertPathWithinRoot;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/path-guard.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Assert that a resolved path stays within the given root directory.
|
|
4
|
+
* Performs both logical path check and realpath-based symlink check.
|
|
5
|
+
*/
|
|
6
|
+
declare function assertPathWithinRoot(fullPath: string, rootDir: string): Promise<void>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { assertPathWithinRoot };
|
|
9
|
+
//# sourceMappingURL=path-guard.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-guard.d.cts","names":[],"sources":["../src/path-guard.ts"],"mappings":";;AAQA;;;iBAAsB,oBAAA,CAAqB,QAAA,UAAkB,OAAA,WAAkB,OAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/path-guard.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Assert that a resolved path stays within the given root directory.
|
|
4
|
+
* Performs both logical path check and realpath-based symlink check.
|
|
5
|
+
*/
|
|
6
|
+
declare function assertPathWithinRoot(fullPath: string, rootDir: string): Promise<void>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { assertPathWithinRoot };
|
|
9
|
+
//# sourceMappingURL=path-guard.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-guard.d.mts","names":[],"sources":["../src/path-guard.ts"],"mappings":";;AAQA;;;iBAAsB,oBAAA,CAAqB,QAAA,UAAkB,OAAA,WAAkB,OAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { AFSError } from "@aigne/afs";
|
|
4
|
+
|
|
5
|
+
//#region src/path-guard.ts
|
|
6
|
+
/**
|
|
7
|
+
* Assert that a resolved path stays within the given root directory.
|
|
8
|
+
* Performs both logical path check and realpath-based symlink check.
|
|
9
|
+
*/
|
|
10
|
+
async function assertPathWithinRoot(fullPath, rootDir) {
|
|
11
|
+
const mountRoot = resolve(rootDir);
|
|
12
|
+
const resolved = resolve(fullPath);
|
|
13
|
+
if (resolved !== mountRoot && !resolved.startsWith(`${mountRoot}/`)) throw new AFSError("Path traversal is not allowed", "AFS_PERMISSION_DENIED");
|
|
14
|
+
try {
|
|
15
|
+
let real;
|
|
16
|
+
try {
|
|
17
|
+
real = await realpath(resolved);
|
|
18
|
+
} catch {
|
|
19
|
+
const parent = dirname(resolved);
|
|
20
|
+
try {
|
|
21
|
+
real = await realpath(parent);
|
|
22
|
+
} catch {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const realMountRoot = await realpath(mountRoot);
|
|
27
|
+
if (real !== realMountRoot && !real.startsWith(`${realMountRoot}/`)) throw new AFSError("Path traversal via symlink is not allowed", "AFS_PERMISSION_DENIED");
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof AFSError) throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { assertPathWithinRoot };
|
|
35
|
+
//# sourceMappingURL=path-guard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-guard.mjs","names":[],"sources":["../src/path-guard.ts"],"sourcesContent":["import { realpath } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { AFSError } from \"@aigne/afs\";\n\n/**\n * Assert that a resolved path stays within the given root directory.\n * Performs both logical path check and realpath-based symlink check.\n */\nexport async function assertPathWithinRoot(fullPath: string, rootDir: string): Promise<void> {\n const mountRoot = resolve(rootDir);\n const resolved = resolve(fullPath);\n\n // 1. Logical path check (catches ../ traversal)\n if (resolved !== mountRoot && !resolved.startsWith(`${mountRoot}/`)) {\n throw new AFSError(\"Path traversal is not allowed\", \"AFS_PERMISSION_DENIED\");\n }\n\n // 2. Symlink-aware check: resolve real path and verify it's still within root\n try {\n let real: string;\n try {\n real = await realpath(resolved);\n } catch {\n // Target doesn't exist yet (e.g. write to new file) — check parent\n const parent = dirname(resolved);\n try {\n real = await realpath(parent);\n } catch {\n // Parent also doesn't exist — logical check is sufficient\n return;\n }\n }\n const realMountRoot = await realpath(mountRoot);\n if (real !== realMountRoot && !real.startsWith(`${realMountRoot}/`)) {\n throw new AFSError(\"Path traversal via symlink is not allowed\", \"AFS_PERMISSION_DENIED\");\n }\n } catch (error) {\n if (error instanceof AFSError) throw error;\n // Other filesystem errors (e.g. permission denied) — let the actual operation handle them\n }\n}\n"],"mappings":";;;;;;;;;AAQA,eAAsB,qBAAqB,UAAkB,SAAgC;CAC3F,MAAM,YAAY,QAAQ,QAAQ;CAClC,MAAM,WAAW,QAAQ,SAAS;AAGlC,KAAI,aAAa,aAAa,CAAC,SAAS,WAAW,GAAG,UAAU,GAAG,CACjE,OAAM,IAAI,SAAS,iCAAiC,wBAAwB;AAI9E,KAAI;EACF,IAAI;AACJ,MAAI;AACF,UAAO,MAAM,SAAS,SAAS;UACzB;GAEN,MAAM,SAAS,QAAQ,SAAS;AAChC,OAAI;AACF,WAAO,MAAM,SAAS,OAAO;WACvB;AAEN;;;EAGJ,MAAM,gBAAgB,MAAM,SAAS,UAAU;AAC/C,MAAI,SAAS,iBAAiB,CAAC,KAAK,WAAW,GAAG,cAAc,GAAG,CACjE,OAAM,IAAI,SAAS,6CAA6C,wBAAwB;UAEnF,OAAO;AACd,MAAI,iBAAiB,SAAU,OAAM"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aigne/afs-provider-utils",
|
|
3
|
+
"version": "1.11.0-beta.12",
|
|
4
|
+
"description": "Shared utilities for AFS providers: MIME detection, path resolution, path guard",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"author": "Arcblock <blocklet@arcblock.io> https://github.com/arcblock",
|
|
10
|
+
"homepage": "https://github.com/arcblock/afs",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/arcblock/afs"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/arcblock/afs/issues"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
20
|
+
"module": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"require": "./dist/index.cjs",
|
|
25
|
+
"import": "./dist/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./*": "./*"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@aigne/afs": "^1.11.0-beta.12"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/bun": "^1.3.6",
|
|
40
|
+
"npm-run-all": "^4.1.5",
|
|
41
|
+
"rimraf": "^6.1.2",
|
|
42
|
+
"tsdown": "0.20.0-beta.3",
|
|
43
|
+
"typescript": "5.9.2",
|
|
44
|
+
"@aigne/scripts": "0.0.0",
|
|
45
|
+
"@aigne/typescript-config": "0.0.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"check-types": "tsc --noEmit",
|
|
50
|
+
"clean": "rimraf dist coverage",
|
|
51
|
+
"test": "bun test",
|
|
52
|
+
"test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text"
|
|
53
|
+
}
|
|
54
|
+
}
|