@chrischall/mcp-utils 2.10.0 → 2.12.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/README.md +448 -15
- package/dist/concurrency/index.d.ts +3 -1
- package/dist/concurrency/index.d.ts.map +1 -1
- package/dist/concurrency/index.js +3 -1
- package/dist/concurrency/index.js.map +1 -1
- package/dist/concurrency/verify-after-write.d.ts +84 -0
- package/dist/concurrency/verify-after-write.d.ts.map +1 -0
- package/dist/concurrency/verify-after-write.js +108 -0
- package/dist/concurrency/verify-after-write.js.map +1 -0
- package/dist/errors/index.d.ts +35 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +79 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/fetchproxy/fallback.d.ts +130 -0
- package/dist/fetchproxy/fallback.d.ts.map +1 -0
- package/dist/fetchproxy/fallback.js +199 -0
- package/dist/fetchproxy/fallback.js.map +1 -0
- package/dist/fetchproxy/index.d.ts +31 -1
- package/dist/fetchproxy/index.d.ts.map +1 -1
- package/dist/fetchproxy/index.js +120 -13
- package/dist/fetchproxy/index.js.map +1 -1
- package/dist/fs/confine.d.ts +9 -0
- package/dist/fs/confine.d.ts.map +1 -1
- package/dist/fs/confine.js +11 -2
- package/dist/fs/confine.js.map +1 -1
- package/dist/fs/index.d.ts +9 -2
- package/dist/fs/index.d.ts.map +1 -1
- package/dist/fs/index.js +13 -4
- package/dist/fs/index.js.map +1 -1
- package/dist/fs/magic.d.ts +35 -0
- package/dist/fs/magic.d.ts.map +1 -0
- package/dist/fs/magic.js +110 -0
- package/dist/fs/magic.js.map +1 -0
- package/dist/fs/output.d.ts +14 -10
- package/dist/fs/output.d.ts.map +1 -1
- package/dist/fs/output.js +21 -45
- package/dist/fs/output.js.map +1 -1
- package/dist/fs/upload-guard.d.ts +97 -0
- package/dist/fs/upload-guard.d.ts.map +1 -0
- package/dist/fs/upload-guard.js +185 -0
- package/dist/fs/upload-guard.js.map +1 -0
- package/dist/fs/write.d.ts +100 -0
- package/dist/fs/write.d.ts.map +1 -0
- package/dist/fs/write.js +207 -0
- package/dist/fs/write.js.map +1 -0
- package/dist/graphql/client.d.ts +223 -0
- package/dist/graphql/client.d.ts.map +1 -0
- package/dist/graphql/client.js +335 -0
- package/dist/graphql/client.js.map +1 -0
- package/dist/graphql/index.d.ts +20 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +20 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/operation-kind.d.ts +58 -0
- package/dist/graphql/operation-kind.d.ts.map +1 -0
- package/dist/graphql/operation-kind.js +237 -0
- package/dist/graphql/operation-kind.js.map +1 -0
- package/dist/html/index.d.ts +33 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +81 -1
- package/dist/html/index.js.map +1 -1
- package/dist/http/index.d.ts +11 -1
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +35 -7
- package/dist/http/index.js.map +1 -1
- package/dist/response/index.d.ts +1 -0
- package/dist/response/index.d.ts.map +1 -1
- package/dist/response/index.js +1 -0
- package/dist/response/index.js.map +1 -1
- package/dist/response/untrusted.d.ts +72 -0
- package/dist/response/untrusted.d.ts.map +1 -0
- package/dist/response/untrusted.js +55 -0
- package/dist/response/untrusted.js.map +1 -0
- package/dist/scrape/index.d.ts +75 -0
- package/dist/scrape/index.d.ts.map +1 -1
- package/dist/scrape/index.js +272 -0
- package/dist/scrape/index.js.map +1 -1
- package/dist/server/confirm-env.d.ts +12 -2
- package/dist/server/confirm-env.d.ts.map +1 -1
- package/dist/server/confirm-env.js +49 -3
- package/dist/server/confirm-env.js.map +1 -1
- package/dist/server/confirm-spent-file.d.ts +7 -0
- package/dist/server/confirm-spent-file.d.ts.map +1 -0
- package/dist/server/confirm-spent-file.js +118 -0
- package/dist/server/confirm-spent-file.js.map +1 -0
- package/dist/server/confirm-token.d.ts +12 -2
- package/dist/server/confirm-token.d.ts.map +1 -1
- package/dist/server/confirm-token.js +8 -1
- package/dist/server/confirm-token.js.map +1 -1
- package/dist/server/confirm-write.d.ts +152 -0
- package/dist/server/confirm-write.d.ts.map +1 -0
- package/dist/server/confirm-write.js +107 -0
- package/dist/server/confirm-write.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/session/atomic-write.d.ts +15 -0
- package/dist/session/atomic-write.d.ts.map +1 -0
- package/dist/session/atomic-write.js +42 -0
- package/dist/session/atomic-write.js.map +1 -0
- package/dist/session/file-lock.d.ts +57 -0
- package/dist/session/file-lock.d.ts.map +1 -0
- package/dist/session/file-lock.js +215 -0
- package/dist/session/file-lock.js.map +1 -0
- package/dist/session/index.d.ts +118 -2
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +200 -82
- package/dist/session/index.js.map +1 -1
- package/dist/session/login-page.d.ts +73 -0
- package/dist/session/login-page.d.ts.map +1 -0
- package/dist/session/login-page.js +143 -0
- package/dist/session/login-page.js.map +1 -0
- package/package.json +5 -1
package/dist/fs/confine.js
CHANGED
|
@@ -54,10 +54,19 @@ function realPathAllowingMissing(p) {
|
|
|
54
54
|
* `allowedRoots`.
|
|
55
55
|
*/
|
|
56
56
|
export function assertPathWithinRoots(path, roots) {
|
|
57
|
+
return confineToRoots(path, roots).real;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* {@link assertPathWithinRoots}, also reporting WHICH root (its real path)
|
|
61
|
+
* contained the target — for checks that judge the path relative to its root
|
|
62
|
+
* (the upload guard's hidden-segment rule). Internal to the fs module.
|
|
63
|
+
*/
|
|
64
|
+
export function confineToRoots(path, roots) {
|
|
57
65
|
const target = realPathAllowingMissing(expandPath(path));
|
|
58
66
|
for (const root of roots) {
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
const realRoot = realPathAllowingMissing(expandPath(root));
|
|
68
|
+
if (isInside(realRoot, target))
|
|
69
|
+
return { real: target, root: realRoot };
|
|
61
70
|
}
|
|
62
71
|
throw new Error(`Path is outside the allowed directories: ${path}`);
|
|
63
72
|
}
|
package/dist/fs/confine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confine.js","sourceRoot":"","sources":["../../src/fs/confine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,+EAA+E;AAC/E,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;;GAKG;AACH,SAAS,uBAAuB,CAAC,CAAS;IACxC,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,QAAQ,GAAG,GAAG,CAAC;IACnB,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,QAAQ;YAAE,MAAM;QAC/B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QACnE,QAAQ,GAAG,MAAM,CAAC;IACpB,CAAC;IACD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,KAAwB;IAC1E,MAAM,MAAM,GAAG,uBAAuB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,
|
|
1
|
+
{"version":3,"file":"confine.js","sourceRoot":"","sources":["../../src/fs/confine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,+EAA+E;AAC/E,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;;GAKG;AACH,SAAS,uBAAuB,CAAC,CAAS;IACxC,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,QAAQ,GAAG,GAAG,CAAC;IACnB,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,QAAQ;YAAE,MAAM;QAC/B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QACnE,QAAQ,GAAG,MAAM,CAAC;IACpB,CAAC;IACD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,KAAwB;IAC1E,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,KAAwB;IACnE,MAAM,MAAM,GAAG,uBAAuB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,uBAAuB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,IAAI,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC1E,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,4CAA4C,IAAI,EAAE,CAAC,CAAC;AACtE,CAAC"}
|
package/dist/fs/index.d.ts
CHANGED
|
@@ -30,10 +30,17 @@ export interface ReadFileHeadOptions {
|
|
|
30
30
|
export declare function fileBlob(path: string, opts?: FileBlobOptions): Promise<Blob>;
|
|
31
31
|
/**
|
|
32
32
|
* Read the first `bytes` of a file (for magic-byte / header sniffing — image
|
|
33
|
-
* dimensions, file-type detection
|
|
34
|
-
*
|
|
33
|
+
* dimensions, file-type detection; pair with {@link sniffMimeBytes} /
|
|
34
|
+
* {@link bytesMatchMime}) WITHOUT loading the whole file. Returns only as many
|
|
35
|
+
* bytes as were actually read (a short file yields a short buffer).
|
|
36
|
+
*
|
|
37
|
+
* With `allowedRoots`, the path is confined first and its REAL path opened
|
|
38
|
+
* with `O_NOFOLLOW`, so a symlink swapped in after the check is refused.
|
|
35
39
|
*/
|
|
36
40
|
export declare function readFileHead(path: string, bytes: number, opts?: ReadFileHeadOptions): Promise<Buffer>;
|
|
37
41
|
export * from './output.js';
|
|
38
42
|
export { assertPathWithinRoots } from './confine.js';
|
|
43
|
+
export { bytesMatchMime, sniffMimeBytes } from './magic.js';
|
|
44
|
+
export { FileWriteRefusedError, writeFileSafe, writeUniqueFile, type FileWriteRefusal, type WriteFileSafeOptions, type WriteUniqueFileOptions, } from './write.js';
|
|
45
|
+
export { UploadRefusedError, vetUploadFile, type UploadRefusal, type VetUploadOptions, type VettedUpload, } from './upload-guard.js';
|
|
39
46
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/fs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fs/index.ts"],"names":[],"mappings":"AAgBA,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CActF;AAED
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fs/index.ts"],"names":[],"mappings":"AAgBA,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CActF;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmB/G;AAED,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,GAC5B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,mBAAmB,CAAC"}
|
package/dist/fs/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// by the file on disk (`fs.openAsBlob`), so `fetch` streams the bytes straight
|
|
9
9
|
// off disk as it sends the multipart body — constant memory regardless of size.
|
|
10
10
|
// ────────────────────────────────────────────────────────────────────────────
|
|
11
|
-
import { openAsBlob } from 'node:fs';
|
|
11
|
+
import { constants as fsConstants, openAsBlob } from 'node:fs';
|
|
12
12
|
import { open } from 'node:fs/promises';
|
|
13
13
|
import { assertPathWithinRoots } from './confine.js';
|
|
14
14
|
/**
|
|
@@ -36,8 +36,12 @@ export async function fileBlob(path, opts = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Read the first `bytes` of a file (for magic-byte / header sniffing — image
|
|
39
|
-
* dimensions, file-type detection
|
|
40
|
-
*
|
|
39
|
+
* dimensions, file-type detection; pair with {@link sniffMimeBytes} /
|
|
40
|
+
* {@link bytesMatchMime}) WITHOUT loading the whole file. Returns only as many
|
|
41
|
+
* bytes as were actually read (a short file yields a short buffer).
|
|
42
|
+
*
|
|
43
|
+
* With `allowedRoots`, the path is confined first and its REAL path opened
|
|
44
|
+
* with `O_NOFOLLOW`, so a symlink swapped in after the check is refused.
|
|
41
45
|
*/
|
|
42
46
|
export async function readFileHead(path, bytes, opts = {}) {
|
|
43
47
|
const target = opts.allowedRoots ? assertPathWithinRoots(path, opts.allowedRoots) : path;
|
|
@@ -45,7 +49,9 @@ export async function readFileHead(path, bytes, opts = {}) {
|
|
|
45
49
|
// non-leaking message instead of a raw Node ENOENT (path + stack).
|
|
46
50
|
let fh;
|
|
47
51
|
try {
|
|
48
|
-
|
|
52
|
+
// Confined: `target` is already the real path, so a final-component symlink
|
|
53
|
+
// here can only be one swapped in since the check — refuse it.
|
|
54
|
+
fh = await open(target, opts.allowedRoots ? fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0) : 'r');
|
|
49
55
|
}
|
|
50
56
|
catch {
|
|
51
57
|
throw new Error(`Cannot read file: ${path}`);
|
|
@@ -61,4 +67,7 @@ export async function readFileHead(path, bytes, opts = {}) {
|
|
|
61
67
|
}
|
|
62
68
|
export * from './output.js';
|
|
63
69
|
export { assertPathWithinRoots } from './confine.js';
|
|
70
|
+
export { bytesMatchMime, sniffMimeBytes } from './magic.js';
|
|
71
|
+
export { FileWriteRefusedError, writeFileSafe, writeUniqueFile, } from './write.js';
|
|
72
|
+
export { UploadRefusedError, vetUploadFile, } from './upload-guard.js';
|
|
64
73
|
//# sourceMappingURL=index.js.map
|
package/dist/fs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fs/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,4BAA4B;AAC5B,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,iFAAiF;AACjF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAE/E,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fs/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magic-byte (leading signature) detection for the file types the fleet moves
|
|
3
|
+
* around: images (PNG / JPEG / WebP / GIF), ISO-BMFF media (HEIC / HEIF / AVIF
|
|
4
|
+
* / MP4 / MOV / M4A), PDF, zip containers (mscz, mxl, docx, epub…) and
|
|
5
|
+
* Standard MIDI Files.
|
|
6
|
+
*
|
|
7
|
+
* Consolidates gemini's `sniffMimeBytes` (images), musescore's
|
|
8
|
+
* `FORMAT_MAGIC`/`readMagic` (PDF / zip / MIDI — the check that keeps a gated
|
|
9
|
+
* "Forbidden" HTML page from being saved as a `.pdf`), and skylight's
|
|
10
|
+
* upload-guard `SIGNATURES` (images + ISO-BMFF). Pair with `readFileHead` for
|
|
11
|
+
* an on-disk file; 16 bytes is enough for every signature here.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Magic-byte MIME sniff. Returns the detected type — `image/png`,
|
|
15
|
+
* `image/jpeg`, `image/webp`, `image/gif`, `application/pdf`,
|
|
16
|
+
* `application/zip`, `audio/midi`, or (from an ISO-BMFF `ftyp` major brand)
|
|
17
|
+
* `image/heic`, `image/heif`, `image/avif`, `video/quicktime`, `audio/mp4`,
|
|
18
|
+
* `video/mp4` — or `undefined` for anything else (including a legacy
|
|
19
|
+
* QuickTime head with no `ftyp`, whose type it cannot name). Callers decide
|
|
20
|
+
* their own default. Needs at most the first 16 bytes.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sniffMimeBytes(bytes: Uint8Array): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Whether `bytes` (a file's head) start like `mime` claims — the "is this
|
|
25
|
+
* renamed secret really a .png" check. Fails closed: a MIME type with no known
|
|
26
|
+
* signature returns `false`, never a silent pass.
|
|
27
|
+
*
|
|
28
|
+
* The ISO-BMFF family (heic / heif / avif / mp4 / quicktime / m4a) is matched
|
|
29
|
+
* loosely — any leading BMFF box is accepted for any of them — because brands
|
|
30
|
+
* vary between encoders and an old `.mov` has no `ftyp` at all; a credential
|
|
31
|
+
* or text file still never passes. Aliases like `image/jpg` are normalized and
|
|
32
|
+
* the comparison is case-insensitive.
|
|
33
|
+
*/
|
|
34
|
+
export declare function bytesMatchMime(bytes: Uint8Array, mime: string): boolean;
|
|
35
|
+
//# sourceMappingURL=magic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magic.d.ts","sourceRoot":"","sources":["../../src/fs/magic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkEH;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAGpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMvE"}
|
package/dist/fs/magic.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magic-byte (leading signature) detection for the file types the fleet moves
|
|
3
|
+
* around: images (PNG / JPEG / WebP / GIF), ISO-BMFF media (HEIC / HEIF / AVIF
|
|
4
|
+
* / MP4 / MOV / M4A), PDF, zip containers (mscz, mxl, docx, epub…) and
|
|
5
|
+
* Standard MIDI Files.
|
|
6
|
+
*
|
|
7
|
+
* Consolidates gemini's `sniffMimeBytes` (images), musescore's
|
|
8
|
+
* `FORMAT_MAGIC`/`readMagic` (PDF / zip / MIDI — the check that keeps a gated
|
|
9
|
+
* "Forbidden" HTML page from being saved as a `.pdf`), and skylight's
|
|
10
|
+
* upload-guard `SIGNATURES` (images + ISO-BMFF). Pair with `readFileHead` for
|
|
11
|
+
* an on-disk file; 16 bytes is enough for every signature here.
|
|
12
|
+
*/
|
|
13
|
+
const PNG_SIG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
14
|
+
/** Whether `buf` starts with the bytes of `sig` (a byte array or latin1 string). */
|
|
15
|
+
function startsWith(buf, sig, offset = 0) {
|
|
16
|
+
const bytes = typeof sig === 'string' ? Array.from(sig, (c) => c.charCodeAt(0)) : sig;
|
|
17
|
+
if (buf.length < offset + bytes.length)
|
|
18
|
+
return false;
|
|
19
|
+
for (let i = 0; i < bytes.length; i++)
|
|
20
|
+
if (buf[offset + i] !== bytes[i])
|
|
21
|
+
return false;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
function latin1(buf, start, end) {
|
|
25
|
+
return String.fromCharCode(...buf.subarray(start, end));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Top-level ISO-BMFF box types that can open a file. `ftyp` for anything
|
|
29
|
+
* modern; the rest are legacy QuickTime atoms an old `.mov` may start with.
|
|
30
|
+
*/
|
|
31
|
+
const BMFF_BOXES = new Set(['ftyp', 'moov', 'mdat', 'wide', 'free', 'skip', 'pnot']);
|
|
32
|
+
function isBmff(buf) {
|
|
33
|
+
return buf.length >= 8 && BMFF_BOXES.has(latin1(buf, 4, 8));
|
|
34
|
+
}
|
|
35
|
+
const HEIC_BRANDS = new Set(['heic', 'heix', 'heim', 'heis', 'hevc', 'hevx']);
|
|
36
|
+
const HEIF_BRANDS = new Set(['mif1', 'msf1']);
|
|
37
|
+
/** The MIME an `ftyp` major brand names; `undefined` when there is no ftyp box. */
|
|
38
|
+
function bmffMime(buf) {
|
|
39
|
+
if (buf.length < 12 || latin1(buf, 4, 8) !== 'ftyp')
|
|
40
|
+
return undefined;
|
|
41
|
+
const brand = latin1(buf, 8, 12);
|
|
42
|
+
if (HEIC_BRANDS.has(brand))
|
|
43
|
+
return 'image/heic';
|
|
44
|
+
if (HEIF_BRANDS.has(brand))
|
|
45
|
+
return 'image/heif';
|
|
46
|
+
if (brand === 'avif' || brand === 'avis')
|
|
47
|
+
return 'image/avif';
|
|
48
|
+
if (brand === 'qt ')
|
|
49
|
+
return 'video/quicktime';
|
|
50
|
+
if (brand === 'M4A ')
|
|
51
|
+
return 'audio/mp4';
|
|
52
|
+
return 'video/mp4';
|
|
53
|
+
}
|
|
54
|
+
/** Exact (non-ISO-BMFF) signatures, in detection order. */
|
|
55
|
+
const SIGNATURES = [
|
|
56
|
+
['image/png', (b) => startsWith(b, PNG_SIG)],
|
|
57
|
+
['image/jpeg', (b) => startsWith(b, [0xff, 0xd8, 0xff])],
|
|
58
|
+
['image/webp', (b) => startsWith(b, 'RIFF') && startsWith(b, 'WEBP', 8)],
|
|
59
|
+
['image/gif', (b) => startsWith(b, 'GIF8')],
|
|
60
|
+
['application/pdf', (b) => startsWith(b, '%PDF-')],
|
|
61
|
+
// Local file header; empty archive (end-of-central-directory first); spanned.
|
|
62
|
+
['application/zip', (b) => startsWith(b, [0x50, 0x4b, 0x03, 0x04]) || startsWith(b, [0x50, 0x4b, 0x05, 0x06]) || startsWith(b, [0x50, 0x4b, 0x07, 0x08])],
|
|
63
|
+
['audio/midi', (b) => startsWith(b, 'MThd')],
|
|
64
|
+
];
|
|
65
|
+
/** ISO-BMFF MIME types — matched as a family by {@link bytesMatchMime}. */
|
|
66
|
+
const BMFF_MIMES = new Set(['image/heic', 'image/heif', 'image/avif', 'video/mp4', 'video/quicktime', 'audio/mp4']);
|
|
67
|
+
/** Common aliases normalized before matching. */
|
|
68
|
+
const MIME_ALIASES = {
|
|
69
|
+
'image/jpg': 'image/jpeg',
|
|
70
|
+
'image/pjpeg': 'image/jpeg',
|
|
71
|
+
'audio/x-midi': 'audio/midi',
|
|
72
|
+
'audio/mid': 'audio/midi',
|
|
73
|
+
'application/x-zip-compressed': 'application/zip',
|
|
74
|
+
'application/x-pdf': 'application/pdf',
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Magic-byte MIME sniff. Returns the detected type — `image/png`,
|
|
78
|
+
* `image/jpeg`, `image/webp`, `image/gif`, `application/pdf`,
|
|
79
|
+
* `application/zip`, `audio/midi`, or (from an ISO-BMFF `ftyp` major brand)
|
|
80
|
+
* `image/heic`, `image/heif`, `image/avif`, `video/quicktime`, `audio/mp4`,
|
|
81
|
+
* `video/mp4` — or `undefined` for anything else (including a legacy
|
|
82
|
+
* QuickTime head with no `ftyp`, whose type it cannot name). Callers decide
|
|
83
|
+
* their own default. Needs at most the first 16 bytes.
|
|
84
|
+
*/
|
|
85
|
+
export function sniffMimeBytes(bytes) {
|
|
86
|
+
for (const [mime, test] of SIGNATURES)
|
|
87
|
+
if (test(bytes))
|
|
88
|
+
return mime;
|
|
89
|
+
return bmffMime(bytes);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether `bytes` (a file's head) start like `mime` claims — the "is this
|
|
93
|
+
* renamed secret really a .png" check. Fails closed: a MIME type with no known
|
|
94
|
+
* signature returns `false`, never a silent pass.
|
|
95
|
+
*
|
|
96
|
+
* The ISO-BMFF family (heic / heif / avif / mp4 / quicktime / m4a) is matched
|
|
97
|
+
* loosely — any leading BMFF box is accepted for any of them — because brands
|
|
98
|
+
* vary between encoders and an old `.mov` has no `ftyp` at all; a credential
|
|
99
|
+
* or text file still never passes. Aliases like `image/jpg` are normalized and
|
|
100
|
+
* the comparison is case-insensitive.
|
|
101
|
+
*/
|
|
102
|
+
export function bytesMatchMime(bytes, mime) {
|
|
103
|
+
const lower = mime.toLowerCase();
|
|
104
|
+
const want = MIME_ALIASES[lower] ?? lower;
|
|
105
|
+
if (BMFF_MIMES.has(want))
|
|
106
|
+
return isBmff(bytes);
|
|
107
|
+
const sig = SIGNATURES.find(([m]) => m === want);
|
|
108
|
+
return sig !== undefined && sig[1](bytes);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=magic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magic.js","sourceRoot":"","sources":["../../src/fs/magic.ts"],"names":[],"mappings":"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"}
|
package/dist/fs/output.d.ts
CHANGED
|
@@ -28,8 +28,12 @@ export declare function resolveOutputDir(perCall: string | undefined, envVar: st
|
|
|
28
28
|
/**
|
|
29
29
|
* A non-overwriting path for `base.ext` in `dir`: the bare name when free,
|
|
30
30
|
* else `base-2.ext`, `base-3.ext`, … (gemini's numbering). `ext` is passed
|
|
31
|
-
* without the dot. `base` is sanitized to a single path component
|
|
32
|
-
*
|
|
31
|
+
* without the dot. `base` is sanitized to a single path component so a `../…`
|
|
32
|
+
* stem can't write outside `dir`.
|
|
33
|
+
*
|
|
34
|
+
* Check-then-use: the name can be taken (or a symlink planted there) between
|
|
35
|
+
* this call and the caller's write. To write, prefer {@link writeUniqueFile} /
|
|
36
|
+
* {@link writeBinaryOutput}, which claim the name with an exclusive create.
|
|
33
37
|
*/
|
|
34
38
|
export declare function uniquePath(dir: string, base: string, ext: string): string;
|
|
35
39
|
/** Options for {@link writeBinaryOutput}. */
|
|
@@ -44,20 +48,20 @@ export interface WriteBinaryOutputOptions {
|
|
|
44
48
|
mimeType?: string;
|
|
45
49
|
/** Explicit extension (no dot); overrides the MIME lookup. */
|
|
46
50
|
extension?: string;
|
|
51
|
+
/** File mode for the new file (before umask), e.g. `0o600`. Default `0o666`. */
|
|
52
|
+
mode?: number;
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Decode `base64` and write it to a **non-overwriting** file in `dir`, deriving
|
|
50
56
|
* the extension from `extension` → the MIME type → `'bin'`. Returns the
|
|
51
57
|
* absolute path written. The caller returns that path from the tool result
|
|
52
58
|
* (with an `inline` flag for base64 via `imageResult` when the user asks).
|
|
59
|
+
*
|
|
60
|
+
* Each candidate name is claimed with an exclusive, no-follow create
|
|
61
|
+
* (`O_CREAT | O_EXCL | O_NOFOLLOW`) and the next name tried on EEXIST, so the
|
|
62
|
+
* write is race-free and never goes through a symlink planted at the name —
|
|
63
|
+
* previously a dangling symlink there looked "free" to `existsSync` and the
|
|
64
|
+
* write created its target outside `dir`.
|
|
53
65
|
*/
|
|
54
66
|
export declare function writeBinaryOutput(opts: WriteBinaryOutputOptions): string;
|
|
55
|
-
/**
|
|
56
|
-
* Magic-byte MIME sniff for the image formats the fleet passes around
|
|
57
|
-
* (PNG / JPEG / WebP / GIF). Returns `undefined` for anything else — callers
|
|
58
|
-
* decide their own default. Pairs with `readFileHead` for on-disk files;
|
|
59
|
-
* consolidates gemini's `sniffMimeBytes` (and evite's dimension sniffer uses
|
|
60
|
-
* the same signatures).
|
|
61
|
-
*/
|
|
62
|
-
export declare function sniffMimeBytes(bytes: Uint8Array): string | undefined;
|
|
63
67
|
//# sourceMappingURL=output.d.ts.map
|
package/dist/fs/output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/fs/output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/fs/output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAI5E;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,SAAS,CAAC;IAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAO,GAC/D,MAAM,CAaR;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQzE;AAkBD,6CAA6C;AAC7C,MAAM,WAAW,wBAAwB;IACvC,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,MAAM,CAGxE"}
|
package/dist/fs/output.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* filenames, extension derived from the MIME type. The fleet convention (see
|
|
10
10
|
* the mcp-fleet-builder skill) is that every binary-output tool uses this shape.
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync, mkdirSync
|
|
12
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
import { expandPath, readEnvVar } from '../config/index.js';
|
|
15
15
|
import { assertPathWithinRoots } from './confine.js';
|
|
16
|
+
import { sanitizeBaseName, writeUniqueFileSync } from './write.js';
|
|
16
17
|
/**
|
|
17
18
|
* Resolve the directory a binary-output tool should write into:
|
|
18
19
|
* the per-call argument → the named env var → the current working directory.
|
|
@@ -39,26 +40,15 @@ export function resolveOutputDir(perCall, envVar, opts = {}) {
|
|
|
39
40
|
mkdirSync(dir, { recursive: true });
|
|
40
41
|
return dir;
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Reduce a caller-supplied filename stem to a single safe path component:
|
|
44
|
-
* strip directory separators and any `..`, so the stem can never escape the
|
|
45
|
-
* output directory (defense-in-depth — `SafePathSegment` does the same for
|
|
46
|
-
* request-path atoms). A stem that sanitizes away entirely falls back to
|
|
47
|
-
* `'file'`.
|
|
48
|
-
*/
|
|
49
|
-
function sanitizeBaseName(base) {
|
|
50
|
-
const safe = base
|
|
51
|
-
.replace(/[/\\]+/g, '_') // kill path separators
|
|
52
|
-
.replace(/\.\.+/g, '_') // neutralize .. (and longer dot runs)
|
|
53
|
-
.replace(/^\.+/, '') // no leading dots (hidden-file / current-dir)
|
|
54
|
-
.trim();
|
|
55
|
-
return safe.length > 0 ? safe : 'file';
|
|
56
|
-
}
|
|
57
43
|
/**
|
|
58
44
|
* A non-overwriting path for `base.ext` in `dir`: the bare name when free,
|
|
59
45
|
* else `base-2.ext`, `base-3.ext`, … (gemini's numbering). `ext` is passed
|
|
60
|
-
* without the dot. `base` is sanitized to a single path component
|
|
61
|
-
*
|
|
46
|
+
* without the dot. `base` is sanitized to a single path component so a `../…`
|
|
47
|
+
* stem can't write outside `dir`.
|
|
48
|
+
*
|
|
49
|
+
* Check-then-use: the name can be taken (or a symlink planted there) between
|
|
50
|
+
* this call and the caller's write. To write, prefer {@link writeUniqueFile} /
|
|
51
|
+
* {@link writeBinaryOutput}, which claim the name with an exclusive create.
|
|
62
52
|
*/
|
|
63
53
|
export function uniquePath(dir, base, ext) {
|
|
64
54
|
const safe = sanitizeBaseName(base);
|
|
@@ -77,43 +67,29 @@ const MIME_EXT = {
|
|
|
77
67
|
'image/jpeg': 'jpg',
|
|
78
68
|
'image/webp': 'webp',
|
|
79
69
|
'image/gif': 'gif',
|
|
70
|
+
'image/heic': 'heic',
|
|
71
|
+
'image/avif': 'avif',
|
|
80
72
|
'application/pdf': 'pdf',
|
|
81
73
|
'application/json': 'json',
|
|
74
|
+
'application/zip': 'zip',
|
|
75
|
+
'audio/midi': 'mid',
|
|
76
|
+
'video/mp4': 'mp4',
|
|
77
|
+
'video/quicktime': 'mov',
|
|
82
78
|
};
|
|
83
79
|
/**
|
|
84
80
|
* Decode `base64` and write it to a **non-overwriting** file in `dir`, deriving
|
|
85
81
|
* the extension from `extension` → the MIME type → `'bin'`. Returns the
|
|
86
82
|
* absolute path written. The caller returns that path from the tool result
|
|
87
83
|
* (with an `inline` flag for base64 via `imageResult` when the user asks).
|
|
84
|
+
*
|
|
85
|
+
* Each candidate name is claimed with an exclusive, no-follow create
|
|
86
|
+
* (`O_CREAT | O_EXCL | O_NOFOLLOW`) and the next name tried on EEXIST, so the
|
|
87
|
+
* write is race-free and never goes through a symlink planted at the name —
|
|
88
|
+
* previously a dangling symlink there looked "free" to `existsSync` and the
|
|
89
|
+
* write created its target outside `dir`.
|
|
88
90
|
*/
|
|
89
91
|
export function writeBinaryOutput(opts) {
|
|
90
92
|
const ext = opts.extension ?? (opts.mimeType ? MIME_EXT[opts.mimeType] : undefined) ?? 'bin';
|
|
91
|
-
|
|
92
|
-
const path = uniquePath(opts.dir, opts.baseName, ext);
|
|
93
|
-
writeFileSync(path, Buffer.from(opts.base64, 'base64'));
|
|
94
|
-
return path;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Magic-byte MIME sniff for the image formats the fleet passes around
|
|
98
|
-
* (PNG / JPEG / WebP / GIF). Returns `undefined` for anything else — callers
|
|
99
|
-
* decide their own default. Pairs with `readFileHead` for on-disk files;
|
|
100
|
-
* consolidates gemini's `sniffMimeBytes` (and evite's dimension sniffer uses
|
|
101
|
-
* the same signatures).
|
|
102
|
-
*/
|
|
103
|
-
export function sniffMimeBytes(bytes) {
|
|
104
|
-
const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
105
|
-
if (buf.length >= 8 && buf.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
|
|
106
|
-
return 'image/png';
|
|
107
|
-
}
|
|
108
|
-
if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
|
|
109
|
-
return 'image/jpeg';
|
|
110
|
-
}
|
|
111
|
-
if (buf.length >= 12 && buf.subarray(0, 4).toString('latin1') === 'RIFF' && buf.subarray(8, 12).toString('latin1') === 'WEBP') {
|
|
112
|
-
return 'image/webp';
|
|
113
|
-
}
|
|
114
|
-
if (buf.length >= 4 && buf.subarray(0, 4).toString('latin1') === 'GIF8') {
|
|
115
|
-
return 'image/gif';
|
|
116
|
-
}
|
|
117
|
-
return undefined;
|
|
93
|
+
return writeUniqueFileSync(opts.dir, opts.baseName, ext, Buffer.from(opts.base64, 'base64'), opts.mode);
|
|
118
94
|
}
|
|
119
95
|
//# sourceMappingURL=output.js.map
|
package/dist/fs/output.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/fs/output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/fs/output.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload guard — vet a model-supplied local path BEFORE a byte of it leaves the
|
|
3
|
+
* machine.
|
|
4
|
+
*
|
|
5
|
+
* Upload tools take a local path from the model, so a prompt-injected call can
|
|
6
|
+
* name `~/.ssh/id_ed25519` or `~/.aws/credentials`; a confirmation preview only
|
|
7
|
+
* helps if a human reads it, and under `MCP_CONFIRM_MODE=auto` nothing forces
|
|
8
|
+
* that. Consolidates skylight's `upload-guard.ts#vetUploadFile` (extension
|
|
9
|
+
* allowlist, lstat + regular-file, size cap, `O_NOFOLLOW` open, per-type magic
|
|
10
|
+
* bytes incl. ISO-BMFF — fleet-audit#248/#1177) with vibo's
|
|
11
|
+
* `confineUploadPath` (root confinement through symlinks, relative paths
|
|
12
|
+
* against the upload dir, dotfile refusal — #1137), and adds the root
|
|
13
|
+
* confinement skylight lacked (#1124).
|
|
14
|
+
*
|
|
15
|
+
* Order matters: confinement is checked first (so a preview already refuses an
|
|
16
|
+
* out-of-root path, whatever its type), then the extension, then the file
|
|
17
|
+
* itself — opened ONCE with `O_NOFOLLOW | O_NONBLOCK`, and the size, type and
|
|
18
|
+
* magic bytes are judged from that descriptor, so a swap after the checks
|
|
19
|
+
* cannot substitute a different file for the one vetted.
|
|
20
|
+
*/
|
|
21
|
+
import { McpToolError } from '../errors/index.js';
|
|
22
|
+
/** Why {@link vetUploadFile} refused a path. */
|
|
23
|
+
export type UploadRefusal = 'outside-roots' | 'extension' | 'unreadable' | 'symlink' | 'not-file' | 'hidden' | 'too-large' | 'changed' | 'signature';
|
|
24
|
+
/** {@link vetUploadFile} refused the path; nothing was read for upload. */
|
|
25
|
+
export declare class UploadRefusedError extends McpToolError {
|
|
26
|
+
readonly reason: UploadRefusal;
|
|
27
|
+
constructor(reason: UploadRefusal, message: string, hint?: string);
|
|
28
|
+
}
|
|
29
|
+
/** Options for {@link vetUploadFile}. */
|
|
30
|
+
export interface VetUploadOptions {
|
|
31
|
+
/**
|
|
32
|
+
* The allowlist: lower-case extension (no dot) → the MIME type it is sent
|
|
33
|
+
* as. A path whose extension is not a key is refused, and so is an
|
|
34
|
+
* extensionless path (what key and credential files look like). Every MIME
|
|
35
|
+
* here must be one `bytesMatchMime` can verify, or that type is refused.
|
|
36
|
+
*/
|
|
37
|
+
mimeByExt: Readonly<Record<string, string>>;
|
|
38
|
+
/** Largest file accepted, in bytes. */
|
|
39
|
+
maxBytes: number;
|
|
40
|
+
/**
|
|
41
|
+
* The directories uploads may come from (each `~`-expanded, resolved through
|
|
42
|
+
* symlinks). REQUIRED, so leaving a tool unconfined is a visible decision:
|
|
43
|
+
* pass `'unconfined'` to skip confinement (every other check still runs).
|
|
44
|
+
* An empty array refuses everything.
|
|
45
|
+
*/
|
|
46
|
+
allowedRoots: readonly string[] | 'unconfined';
|
|
47
|
+
/** What a relative path resolves against. Default: the process cwd. */
|
|
48
|
+
baseDir?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Refuse a path with any dot-prefixed segment (`.ssh/…`, `.env.jpg`) —
|
|
51
|
+
* judged BELOW the containing root (so a root under `~/.cache` is fine), or
|
|
52
|
+
* across the whole real path when unconfined.
|
|
53
|
+
*/
|
|
54
|
+
denyHiddenSegments?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Also return the whole file, read from the same vetted descriptor
|
|
57
|
+
* (`bytes`). Use it when the upload buffers anyway: re-opening the path
|
|
58
|
+
* later (e.g. with `fileBlob`) reopens a window for a swap.
|
|
59
|
+
*/
|
|
60
|
+
readAll?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** A path {@link vetUploadFile} accepted. */
|
|
63
|
+
export interface VettedUpload {
|
|
64
|
+
/** The REAL path that was vetted — read THIS, never the caller's string. */
|
|
65
|
+
path: string;
|
|
66
|
+
/** The absolute path as the caller named it (for messages / previews). */
|
|
67
|
+
requested: string;
|
|
68
|
+
/** Lower-case extension, no dot. */
|
|
69
|
+
ext: string;
|
|
70
|
+
/** The MIME type from `mimeByExt`, verified against the magic bytes. */
|
|
71
|
+
mime: string;
|
|
72
|
+
size: number;
|
|
73
|
+
/** The roots it was confined to — pass as `allowedRoots` to a later read. */
|
|
74
|
+
allowedRoots?: readonly string[];
|
|
75
|
+
/** The whole file, when `readAll` was set. */
|
|
76
|
+
bytes?: Uint8Array;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Vet a tool-supplied local path for upload, or throw
|
|
80
|
+
* {@link UploadRefusedError}. In order:
|
|
81
|
+
*
|
|
82
|
+
* 1. **confinement** — the real path (through symlinks) must sit inside one of
|
|
83
|
+
* `allowedRoots` (unless `'unconfined'`);
|
|
84
|
+
* 2. **extension** — must be a key of `mimeByExt`;
|
|
85
|
+
* 3. **not a symlink, a regular file** — `lstat` of the named path;
|
|
86
|
+
* 4. **no hidden segments** — when `denyHiddenSegments`;
|
|
87
|
+
* 5. **one no-follow open** of the real path — `O_NOFOLLOW` refuses a symlink
|
|
88
|
+
* swapped in since the checks, `O_NONBLOCK` keeps a swapped-in FIFO from
|
|
89
|
+
* hanging the open; the descriptor must be the same regular file `lstat`
|
|
90
|
+
* saw, at most `maxBytes`;
|
|
91
|
+
* 6. **magic bytes** — its head must match the claimed MIME
|
|
92
|
+
* ({@link bytesMatchMime}); a type it cannot sniff is refused.
|
|
93
|
+
*
|
|
94
|
+
* Returns the real path to read (and, with `readAll`, the bytes themselves).
|
|
95
|
+
*/
|
|
96
|
+
export declare function vetUploadFile(path: string, opts: VetUploadOptions): Promise<VettedUpload>;
|
|
97
|
+
//# sourceMappingURL=upload-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-guard.d.ts","sourceRoot":"","sources":["../../src/fs/upload-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUlD,gDAAgD;AAChD,MAAM,MAAM,aAAa,GACrB,eAAe,GACf,WAAW,GACX,YAAY,GACZ,SAAS,GACT,UAAU,GACV,QAAQ,GACR,WAAW,GACX,SAAS,GACT,WAAW,CAAC;AAEhB,2EAA2E;AAC3E,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,YAAY,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAIhE;CACF;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,YAAY,CAAC;IAC/C,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,8CAA8C;IAC9C,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AA6BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAiG/F"}
|