@quantakrypto/mcp 0.1.0 → 0.2.1
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/HOSTING.md +23 -4
- package/README.md +26 -4
- package/dist/fsconfig.d.ts +69 -0
- package/dist/fsconfig.d.ts.map +1 -0
- package/dist/fsconfig.js +110 -0
- package/dist/fsconfig.js.map +1 -0
- package/dist/http.d.ts +49 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +191 -24
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/protocol.d.ts +15 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server.d.ts +9 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +21 -8
- package/dist/server.js.map +1 -1
- package/dist/stdio.js +0 -0
- package/dist/tools.d.ts +30 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +72 -11
- package/dist/tools.js.map +1 -1
- package/package.json +5 -5
package/HOSTING.md
CHANGED
|
@@ -27,9 +27,24 @@ hosted endpoint is reachable by untrusted peers. Out of the box `node dist/http.
|
|
|
27
27
|
gate. The knowledge tools (`explain_finding`, `suggest_hybrid`, `list_rules`)
|
|
28
28
|
are pure and always exposed. The gating is a pure function (`gateHttpTools`),
|
|
29
29
|
unit-tested in `test/http.test.ts`.
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
(
|
|
30
|
+
- **Confines the filesystem tools to a root allow-list.** When the FS tools are
|
|
31
|
+
enabled, every scanned path must resolve inside `QUANTAKRYPTO_MCP_ROOT`
|
|
32
|
+
(`:`-separated; defaults to the process CWD). `..` traversal and out-of-root
|
|
33
|
+
absolute paths are rejected (`resolveScanPath` in `src/fsconfig.ts`), so the
|
|
34
|
+
endpoint cannot be turned into an `/etc/passwd` read oracle.
|
|
35
|
+
- **Validates the request `Origin`.** `POST /mcp` rejects a browser request whose
|
|
36
|
+
`Origin` host is not loopback (or in `QUANTAKRYPTO_MCP_ALLOW_ORIGIN`) with `403`,
|
|
37
|
+
blocking DNS-rebinding / localhost-CSRF against the default no-token config.
|
|
38
|
+
Requests with no `Origin` (CLI / native MCP clients) are unaffected.
|
|
39
|
+
- **Bounds each request.** A 1 MiB request-body cap (`413` only on the cap; a
|
|
40
|
+
transport read error is `400`), a per-request tool timeout that **aborts the
|
|
41
|
+
in-flight scan** (`QUANTAKRYPTO_MCP_TIMEOUT_MS`, default 30 s → `504`; no
|
|
42
|
+
background work leaks past the response), a response-size cap
|
|
43
|
+
(`QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES`, default 4 MiB → `500`), and per-scan work
|
|
44
|
+
budgets (`QUANTAKRYPTO_MCP_MAX_FILES` / `QUANTAKRYPTO_MCP_MAX_BYTES`, each capped).
|
|
45
|
+
- **Sanitizes error messages.** Internal failures (an `ENOENT` carrying a server
|
|
46
|
+
path, a stack trace) are logged to stderr and replaced with a generic message
|
|
47
|
+
in the wire response, so a remote caller never learns server internals.
|
|
33
48
|
|
|
34
49
|
| Env var | Default | Purpose |
|
|
35
50
|
| --- | --- | --- |
|
|
@@ -37,8 +52,12 @@ hosted endpoint is reachable by untrusted peers. Out of the box `node dist/http.
|
|
|
37
52
|
| `PORT` | `3000` | Listen port. |
|
|
38
53
|
| `QUANTAKRYPTO_MCP_TOKEN` | _(unset)_ | When set, requires `Authorization: Bearer <token>`. |
|
|
39
54
|
| `QUANTAKRYPTO_MCP_ALLOW_FS` | _(off)_ | `1`/`true` exposes the filesystem tools over HTTP. |
|
|
40
|
-
| `
|
|
55
|
+
| `QUANTAKRYPTO_MCP_ROOT` | _(cwd)_ | `:`-separated allow-list of directories the FS tools may scan. |
|
|
56
|
+
| `QUANTAKRYPTO_MCP_ALLOW_ORIGIN` | _(loopback)_ | Comma-separated extra `Origin` hosts allowed on `/mcp`. |
|
|
57
|
+
| `QUANTAKRYPTO_MCP_TIMEOUT_MS` | `30000` | Per-request deadline; aborts the in-flight scan on timeout. |
|
|
41
58
|
| `QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES` | `4194304` | Response-body size cap. |
|
|
59
|
+
| `QUANTAKRYPTO_MCP_MAX_FILES` | `25000` (cap `250000`) | Max files a single scan may read. |
|
|
60
|
+
| `QUANTAKRYPTO_MCP_MAX_BYTES` | `268435456` (cap 2 GiB) | Max cumulative bytes a single scan may read. |
|
|
42
61
|
|
|
43
62
|
**Design choice — refuse vs. warn on a wide-open bind.** Binding to a
|
|
44
63
|
non-loopback host with `QUANTAKRYPTO_MCP_TOKEN` unset is **refused** (startup fails)
|
package/README.md
CHANGED
|
@@ -160,9 +160,21 @@ is reachable by untrusted peers:
|
|
|
160
160
|
only when `QUANTAKRYPTO_MCP_ALLOW_FS=1`. The knowledge tools (`explain_finding`,
|
|
161
161
|
`suggest_hybrid`, `list_rules`) are always available. `tools/list` and
|
|
162
162
|
`tools/call` both reflect the gating.
|
|
163
|
-
- **
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
- **Filesystem tools are root-confined.** Even with `QUANTAKRYPTO_MCP_ALLOW_FS=1`,
|
|
164
|
+
every scanned path must resolve inside the `QUANTAKRYPTO_MCP_ROOT` allow-list
|
|
165
|
+
(`:`-separated; the process CWD by default). `..` traversal and out-of-root
|
|
166
|
+
absolute paths (e.g. `/etc/passwd`) are rejected — the server is not an
|
|
167
|
+
arbitrary-file-read oracle.
|
|
168
|
+
- **Origin validation.** `POST /mcp` rejects a browser request whose `Origin`
|
|
169
|
+
host is not loopback (or allow-listed via `QUANTAKRYPTO_MCP_ALLOW_ORIGIN`),
|
|
170
|
+
defending the default no-token loopback config against DNS-rebinding /
|
|
171
|
+
localhost-CSRF. Non-browser clients (no `Origin`) are unaffected.
|
|
172
|
+
- **Limits + work budgets.** A 1 MiB request-body cap (`413` only on the cap,
|
|
173
|
+
`400` on a transport error), a per-request tool timeout that **aborts the
|
|
174
|
+
underlying scan** (`QUANTAKRYPTO_MCP_TIMEOUT_MS`, default 30000 → `504`), a
|
|
175
|
+
response-size cap (`QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES`, default 4 MiB), and
|
|
176
|
+
per-scan work budgets (`QUANTAKRYPTO_MCP_MAX_FILES` / `QUANTAKRYPTO_MCP_MAX_BYTES`)
|
|
177
|
+
so a single call cannot exhaust host resources.
|
|
166
178
|
|
|
167
179
|
| Env var | Default | Purpose |
|
|
168
180
|
| --- | --- | --- |
|
|
@@ -170,8 +182,12 @@ is reachable by untrusted peers:
|
|
|
170
182
|
| `PORT` | `3000` | Listen port. |
|
|
171
183
|
| `QUANTAKRYPTO_MCP_TOKEN` | _(unset)_ | When set, requires `Authorization: Bearer <token>`. |
|
|
172
184
|
| `QUANTAKRYPTO_MCP_ALLOW_FS` | _(off)_ | `1`/`true` exposes the filesystem tools over HTTP. |
|
|
173
|
-
| `
|
|
185
|
+
| `QUANTAKRYPTO_MCP_ROOT` | _(cwd)_ | `:`-separated allow-list of directories the FS tools may scan. |
|
|
186
|
+
| `QUANTAKRYPTO_MCP_ALLOW_ORIGIN` | _(loopback)_ | Comma-separated extra `Origin` hosts allowed on `/mcp`. |
|
|
187
|
+
| `QUANTAKRYPTO_MCP_TIMEOUT_MS` | `30000` | Per-request deadline; aborts the in-flight scan on timeout. |
|
|
174
188
|
| `QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES` | `4194304` | Response-body size cap. |
|
|
189
|
+
| `QUANTAKRYPTO_MCP_MAX_FILES` | `25000` (cap `250000`) | Max files a single scan may read. |
|
|
190
|
+
| `QUANTAKRYPTO_MCP_MAX_BYTES` | `268435456` (cap 2 GiB) | Max cumulative bytes a single scan may read. |
|
|
175
191
|
|
|
176
192
|
```bash
|
|
177
193
|
# Local, knowledge tools only (default safe posture)
|
|
@@ -225,3 +241,9 @@ streams) — no process spawning.
|
|
|
225
241
|
## License
|
|
226
242
|
|
|
227
243
|
Apache-2.0
|
|
244
|
+
|
|
245
|
+
## Support & training
|
|
246
|
+
|
|
247
|
+
Questions, commercial support, or post-quantum readiness training for your team —
|
|
248
|
+
visit **[quantakrypto.com](https://quantakrypto.com)** or email
|
|
249
|
+
**[hello@quantakrypto.com](mailto:hello@quantakrypto.com)**.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-tool safety policy for the quantakrypto MCP (P0 — FS confinement
|
|
3
|
+
* and work budgets).
|
|
4
|
+
*
|
|
5
|
+
* The FS-backed tools (`scan_path`, `inventory_crypto`, `generate_cbom`) pass a
|
|
6
|
+
* caller-supplied `path` straight into `@quantakrypto/core`'s `scan()`. With the
|
|
7
|
+
* filesystem tools enabled (`QUANTAKRYPTO_MCP_ALLOW_FS=1`) that turns the server
|
|
8
|
+
* into an arbitrary-file-read oracle: `path` could be `/etc/passwd` or any tree
|
|
9
|
+
* on the host (SECURITY.md warns about exactly this). This module turns that
|
|
10
|
+
* warning into enforced policy:
|
|
11
|
+
*
|
|
12
|
+
* - **Root allow-list** (`QUANTAKRYPTO_MCP_ROOT`, `:`-separated): every scanned
|
|
13
|
+
* path must resolve inside one of the configured roots. When unset, the
|
|
14
|
+
* process CWD is the single implicit root.
|
|
15
|
+
* - **`..` traversal rejection**: a resolved path that escapes every root is
|
|
16
|
+
* refused, even if the literal string contained no `..`.
|
|
17
|
+
* - **Work budgets** (`maxFiles` / `maxBytes`): bounded by default and capped,
|
|
18
|
+
* so a single `scan_path` cannot exhaust host resources.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is a pure function of an env snapshot + the requested path so
|
|
21
|
+
* it is fully unit-testable; only {@link resolveFsConfig} reads `process.env`.
|
|
22
|
+
*/
|
|
23
|
+
/** Minimal env shape so the resolver stays pure and testable. */
|
|
24
|
+
export type FsEnv = Record<string, string | undefined>;
|
|
25
|
+
/** Default file-count budget for a single FS tool call. */
|
|
26
|
+
export declare const DEFAULT_MAX_FILES = 25000;
|
|
27
|
+
/** Hard cap on the file-count budget, even when raised via env. */
|
|
28
|
+
export declare const MAX_MAX_FILES = 250000;
|
|
29
|
+
/** Default cumulative-bytes budget for a single FS tool call (256 MiB). */
|
|
30
|
+
export declare const DEFAULT_MAX_BYTES: number;
|
|
31
|
+
/** Hard cap on the cumulative-bytes budget, even when raised via env (2 GiB). */
|
|
32
|
+
export declare const MAX_MAX_BYTES: number;
|
|
33
|
+
/** Resolved FS-tool policy: where scans may read and how much work they may do. */
|
|
34
|
+
export interface FsConfig {
|
|
35
|
+
/**
|
|
36
|
+
* Absolute, normalized roots a scan may read inside. Always non-empty; defaults
|
|
37
|
+
* to `[process.cwd()]` when `QUANTAKRYPTO_MCP_ROOT` is unset.
|
|
38
|
+
*/
|
|
39
|
+
roots: string[];
|
|
40
|
+
/** Max files a single scan may read before {@link BudgetExceededError}. */
|
|
41
|
+
maxFiles: number;
|
|
42
|
+
/** Max cumulative bytes a single scan may read before {@link BudgetExceededError}. */
|
|
43
|
+
maxBytes: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the FS-tool policy from an env snapshot. Pure aside from the supplied
|
|
47
|
+
* `cwd` default (so callers/tests can pin it).
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveFsConfig(env: FsEnv, cwd?: string): FsConfig;
|
|
50
|
+
/** Outcome of validating a requested path against the root allow-list. */
|
|
51
|
+
export type PathDecision = {
|
|
52
|
+
ok: true;
|
|
53
|
+
path: string;
|
|
54
|
+
} | {
|
|
55
|
+
ok: false;
|
|
56
|
+
reason: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Validate and resolve a caller-supplied scan path against the allow-list.
|
|
60
|
+
*
|
|
61
|
+
* Relative paths are resolved against the FIRST configured root (the primary
|
|
62
|
+
* scan root), not the process CWD, so a relative request can never reach outside
|
|
63
|
+
* the allow-list. Absolute paths must already sit inside a configured root. Any
|
|
64
|
+
* path that resolves outside every root — whether via an absolute path or a
|
|
65
|
+
* `..` traversal — is rejected. Pure: does no filesystem I/O (it does not follow
|
|
66
|
+
* symlinks; the allow-list is the trust boundary).
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveScanPath(config: FsConfig, requested: string): PathDecision;
|
|
69
|
+
//# sourceMappingURL=fsconfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fsconfig.d.ts","sourceRoot":"","sources":["../src/fsconfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,iEAAiE;AACjE,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEvD,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC,mEAAmE;AACnE,eAAO,MAAM,aAAa,SAAU,CAAC;AAErC,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,QAAoB,CAAC;AAEnD,iFAAiF;AACjF,eAAO,MAAM,aAAa,QAAyB,CAAC;AAEpD,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;CAClB;AA8BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAMjF;AAED,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAUtF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAsBjF"}
|
package/dist/fsconfig.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-tool safety policy for the quantakrypto MCP (P0 — FS confinement
|
|
3
|
+
* and work budgets).
|
|
4
|
+
*
|
|
5
|
+
* The FS-backed tools (`scan_path`, `inventory_crypto`, `generate_cbom`) pass a
|
|
6
|
+
* caller-supplied `path` straight into `@quantakrypto/core`'s `scan()`. With the
|
|
7
|
+
* filesystem tools enabled (`QUANTAKRYPTO_MCP_ALLOW_FS=1`) that turns the server
|
|
8
|
+
* into an arbitrary-file-read oracle: `path` could be `/etc/passwd` or any tree
|
|
9
|
+
* on the host (SECURITY.md warns about exactly this). This module turns that
|
|
10
|
+
* warning into enforced policy:
|
|
11
|
+
*
|
|
12
|
+
* - **Root allow-list** (`QUANTAKRYPTO_MCP_ROOT`, `:`-separated): every scanned
|
|
13
|
+
* path must resolve inside one of the configured roots. When unset, the
|
|
14
|
+
* process CWD is the single implicit root.
|
|
15
|
+
* - **`..` traversal rejection**: a resolved path that escapes every root is
|
|
16
|
+
* refused, even if the literal string contained no `..`.
|
|
17
|
+
* - **Work budgets** (`maxFiles` / `maxBytes`): bounded by default and capped,
|
|
18
|
+
* so a single `scan_path` cannot exhaust host resources.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is a pure function of an env snapshot + the requested path so
|
|
21
|
+
* it is fully unit-testable; only {@link resolveFsConfig} reads `process.env`.
|
|
22
|
+
*/
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
import process from "node:process";
|
|
25
|
+
/** Default file-count budget for a single FS tool call. */
|
|
26
|
+
export const DEFAULT_MAX_FILES = 25_000;
|
|
27
|
+
/** Hard cap on the file-count budget, even when raised via env. */
|
|
28
|
+
export const MAX_MAX_FILES = 250_000;
|
|
29
|
+
/** Default cumulative-bytes budget for a single FS tool call (256 MiB). */
|
|
30
|
+
export const DEFAULT_MAX_BYTES = 256 * 1024 * 1024;
|
|
31
|
+
/** Hard cap on the cumulative-bytes budget, even when raised via env (2 GiB). */
|
|
32
|
+
export const MAX_MAX_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33
|
+
/** Parse a positive integer from an env string, clamped to `[1, cap]`. */
|
|
34
|
+
function toBudget(value, fallback, cap) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return Math.min(fallback, cap);
|
|
37
|
+
const n = Number(value);
|
|
38
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
39
|
+
return Math.min(fallback, cap);
|
|
40
|
+
return Math.min(Math.floor(n), cap);
|
|
41
|
+
}
|
|
42
|
+
/** Split the `:`-separated root allow-list, dropping empty segments. */
|
|
43
|
+
function parseRoots(value, cwd) {
|
|
44
|
+
const raw = (value ?? "")
|
|
45
|
+
.split(path.delimiter)
|
|
46
|
+
.map((s) => s.trim())
|
|
47
|
+
.filter((s) => s.length > 0);
|
|
48
|
+
const list = raw.length > 0 ? raw : [cwd];
|
|
49
|
+
// Normalize to absolute, de-duplicated roots.
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
const roots = [];
|
|
52
|
+
for (const r of list) {
|
|
53
|
+
const abs = path.resolve(cwd, r);
|
|
54
|
+
if (!seen.has(abs)) {
|
|
55
|
+
seen.add(abs);
|
|
56
|
+
roots.push(abs);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return roots;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the FS-tool policy from an env snapshot. Pure aside from the supplied
|
|
63
|
+
* `cwd` default (so callers/tests can pin it).
|
|
64
|
+
*/
|
|
65
|
+
export function resolveFsConfig(env, cwd = process.cwd()) {
|
|
66
|
+
return {
|
|
67
|
+
roots: parseRoots(env.QUANTAKRYPTO_MCP_ROOT, cwd),
|
|
68
|
+
maxFiles: toBudget(env.QUANTAKRYPTO_MCP_MAX_FILES, DEFAULT_MAX_FILES, MAX_MAX_FILES),
|
|
69
|
+
maxBytes: toBudget(env.QUANTAKRYPTO_MCP_MAX_BYTES, DEFAULT_MAX_BYTES, MAX_MAX_BYTES),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** True when `child` is `root` itself or lives beneath it (no `..` escape). */
|
|
73
|
+
function isInsideRoot(child, root) {
|
|
74
|
+
if (child === root)
|
|
75
|
+
return true;
|
|
76
|
+
const rel = path.relative(root, child);
|
|
77
|
+
// `rel` starting with ".." (or being absolute) means `child` escapes `root`.
|
|
78
|
+
return rel.length > 0 && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Validate and resolve a caller-supplied scan path against the allow-list.
|
|
82
|
+
*
|
|
83
|
+
* Relative paths are resolved against the FIRST configured root (the primary
|
|
84
|
+
* scan root), not the process CWD, so a relative request can never reach outside
|
|
85
|
+
* the allow-list. Absolute paths must already sit inside a configured root. Any
|
|
86
|
+
* path that resolves outside every root — whether via an absolute path or a
|
|
87
|
+
* `..` traversal — is rejected. Pure: does no filesystem I/O (it does not follow
|
|
88
|
+
* symlinks; the allow-list is the trust boundary).
|
|
89
|
+
*/
|
|
90
|
+
export function resolveScanPath(config, requested) {
|
|
91
|
+
const trimmed = requested.trim();
|
|
92
|
+
if (trimmed.length === 0) {
|
|
93
|
+
return { ok: false, reason: "path must be a non-empty string" };
|
|
94
|
+
}
|
|
95
|
+
const primaryRoot = config.roots[0];
|
|
96
|
+
const resolved = path.isAbsolute(trimmed)
|
|
97
|
+
? path.resolve(trimmed)
|
|
98
|
+
: path.resolve(primaryRoot, trimmed);
|
|
99
|
+
for (const root of config.roots) {
|
|
100
|
+
if (isInsideRoot(resolved, root)) {
|
|
101
|
+
return { ok: true, path: resolved };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
reason: "path is outside the configured scan root(s). " +
|
|
107
|
+
"Set QUANTAKRYPTO_MCP_ROOT to allow-list the directories the MCP may scan.",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=fsconfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fsconfig.js","sourceRoot":"","sources":["../src/fsconfig.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
CHANGED
|
@@ -67,6 +67,11 @@ export interface HttpConfig {
|
|
|
67
67
|
maxResponseBytes: number;
|
|
68
68
|
/** True when the host is a loopback interface (safe without auth). */
|
|
69
69
|
loopback: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Extra origin hosts to allow on `POST /mcp` (lower-cased hostnames), beyond
|
|
72
|
+
* the implicit loopback set and the bind host. From QUANTAKRYPTO_MCP_ALLOW_ORIGIN.
|
|
73
|
+
*/
|
|
74
|
+
allowedOrigins: string[];
|
|
70
75
|
}
|
|
71
76
|
/**
|
|
72
77
|
* Resolve the HTTP transport config from env + explicit overrides. Pure: does
|
|
@@ -84,6 +89,27 @@ export declare function startupDecision(config: HttpConfig): {
|
|
|
84
89
|
ok: boolean;
|
|
85
90
|
reason?: string;
|
|
86
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Decide whether a request's `Origin` / `Host` is acceptable, to defend the
|
|
94
|
+
* default no-token loopback config against DNS-rebinding and localhost-CSRF: a
|
|
95
|
+
* malicious web page can POST to `http://127.0.0.1:<port>/mcp`, but the browser
|
|
96
|
+
* stamps a foreign `Origin` we can reject. Pure and testable.
|
|
97
|
+
*
|
|
98
|
+
* Policy:
|
|
99
|
+
* - No `Origin` header (curl, a native MCP client, same-origin GET) → allow;
|
|
100
|
+
* the header is a browser artifact and absence is not an attack signal here.
|
|
101
|
+
* - An `Origin` present → its host must be a configured loopback host (or a
|
|
102
|
+
* host explicitly added to the allow-list). A foreign origin is rejected.
|
|
103
|
+
* - When the server itself binds a non-loopback interface, a token is already
|
|
104
|
+
* mandatory (see {@link startupDecision}); the loopback allow-list still
|
|
105
|
+
* applies but auth is the primary control there.
|
|
106
|
+
*/
|
|
107
|
+
export declare function originDecision(allowedHosts: ReadonlySet<string>, originHeader: string | undefined): {
|
|
108
|
+
ok: boolean;
|
|
109
|
+
reason?: string;
|
|
110
|
+
};
|
|
111
|
+
/** The hosts an `Origin` may name. Loopback by default, plus the bind host. */
|
|
112
|
+
export declare function allowedOriginHosts(config: HttpConfig): Set<string>;
|
|
87
113
|
/** A request-authorization outcome. */
|
|
88
114
|
export interface AuthDecision {
|
|
89
115
|
authorized: boolean;
|
|
@@ -97,6 +123,15 @@ export interface AuthDecision {
|
|
|
97
123
|
* allowed (the loopback / trusted-edge case). Pure and testable.
|
|
98
124
|
*/
|
|
99
125
|
export declare function authorizeRequest(token: string, authorizationHeader: string | undefined): AuthDecision;
|
|
126
|
+
/**
|
|
127
|
+
* Constant-time string compare. Both inputs are hashed with the same algorithm
|
|
128
|
+
* to fixed-length digests before {@link timingSafeEqual}, so the comparison runs
|
|
129
|
+
* over equal-length buffers and the early length-mismatch return (which leaked
|
|
130
|
+
* the configured token's length) is gone. The hashing is a domain-separation /
|
|
131
|
+
* length-equalization step, not a secrecy measure: `timingSafeEqual` still does
|
|
132
|
+
* the constant-time work and rejects unequal digests.
|
|
133
|
+
*/
|
|
134
|
+
export declare function timingSafeEqualStr(a: string, b: string): boolean;
|
|
100
135
|
/**
|
|
101
136
|
* Select the tools to expose over HTTP for a given policy. The knowledge tools
|
|
102
137
|
* are always returned; the filesystem tools are included only when
|
|
@@ -104,6 +139,20 @@ export declare function authorizeRequest(token: string, authorizationHeader: str
|
|
|
104
139
|
* for HTTP tool gating, so tools/list and tools/call stay consistent.
|
|
105
140
|
*/
|
|
106
141
|
export declare function gateHttpTools(tools: readonly ToolDefinition[], allowFs: boolean): ToolDefinition[];
|
|
142
|
+
/**
|
|
143
|
+
* Error thrown by {@link readBody} when the request body exceeds the size cap.
|
|
144
|
+
* Distinguished from a transport/I/O error so the caller can map it to HTTP 413
|
|
145
|
+
* specifically, while genuine read failures map to 400/500 (M1).
|
|
146
|
+
*/
|
|
147
|
+
export declare class BodyTooLargeError extends Error {
|
|
148
|
+
readonly name = "BodyTooLargeError";
|
|
149
|
+
constructor(message?: string);
|
|
150
|
+
}
|
|
151
|
+
/** Error raised by {@link withTimeout} when the request deadline elapses. */
|
|
152
|
+
export declare class RequestTimeoutError extends Error {
|
|
153
|
+
readonly name = "RequestTimeoutError";
|
|
154
|
+
constructor(message?: string);
|
|
155
|
+
}
|
|
107
156
|
/**
|
|
108
157
|
* Build (but do not start) the HTTP server wrapping an {@link McpServer}.
|
|
109
158
|
* Exposed for testing and for embedding in a larger process. The `config`
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,KAAK,EAAmB,MAAM,EAAkB,MAAM,WAAW,CAAC;AAQzE,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,8EAA8E;AAC9E,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAc/C,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD,uEAAuE;AACvE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEzD,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|