@lerianstudio/matcher-mcp 1.2.0 → 1.3.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.
@@ -1,105 +0,0 @@
1
- // SSRF-guarded fetch for `ingestion_upload`'s upload-by-reference path
2
- // (`fileUrl`). Base64-in-JSON doesn't scale — a real file inflates ~33% and
3
- // burns the calling LLM's output tokens one-for-one. This lets the client pass
4
- // a short URL instead; the relay fetches the bytes server-side and rebuilds
5
- // the multipart upload exactly as the base64 path does.
6
- //
7
- // The relay is a network-reachable, credential-bearing process: an unguarded
8
- // "fetch whatever URL the client gives you" is textbook SSRF (cloud metadata
9
- // endpoints, internal services, redirect chains to either). Every check below
10
- // is fail-closed — on ambiguity or partial information, reject.
11
- /**
12
- * Fetch a source file from `fileUrl` under a strict SSRF allowlist, returning
13
- * the raw bytes. Throws a plain `Error` with a clear, secret-free message on
14
- * any rejection — there is no token on this outbound call to begin with (it
15
- * targets an external resource, not matcher), so messages may freely include
16
- * the URL/hostname for operator debugging.
17
- */
18
- export async function fetchSourceFile(fileUrl, opts) {
19
- let url;
20
- try {
21
- url = new URL(fileUrl);
22
- }
23
- catch {
24
- throw new Error(`invalid fileUrl: ${fileUrl} (not a parseable URL)`);
25
- }
26
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
27
- throw new Error(`invalid fileUrl: ${fileUrl} (scheme must be http or https, got ${url.protocol})`);
28
- }
29
- // Exact, case-insensitive hostname match only — no substring/suffix
30
- // matching. An empty allowlist (the default) rejects every host.
31
- // NOTE: this validates the hostname string, not the resolved IP. Only
32
- // allowlist non-rebindable/static internal names (e.g. host.docker.internal),
33
- // never external domains whose DNS you don't control (DNS-rebinding vector).
34
- const hostname = url.hostname.toLowerCase();
35
- const allowed = opts.allowedHosts.some((h) => h.toLowerCase() === hostname);
36
- if (!allowed) {
37
- throw new Error(`fileUrl host "${url.hostname}" is not in the upload allowlist ` +
38
- '(MCP_UPLOAD_ALLOWED_HOSTS) — add it there to permit this host');
39
- }
40
- const controller = new AbortController();
41
- const timer = setTimeout(() => controller.abort(), opts.timeoutMs);
42
- let res;
43
- try {
44
- res = await fetch(fileUrl, {
45
- // A redirect must NOT be silently followed — redirect-to-internal (e.g.
46
- // an allowlisted host 302-ing to a metadata IP) is a classic SSRF
47
- // bypass. 'error' makes fetch reject the promise on any redirect.
48
- redirect: 'error',
49
- signal: controller.signal,
50
- });
51
- }
52
- catch (err) {
53
- if (controller.signal.aborted) {
54
- throw new Error(`fetching fileUrl timed out after ${opts.timeoutMs}ms: ${fileUrl}`, { cause: err });
55
- }
56
- const detail = err instanceof Error ? err.message : 'unknown fetch error';
57
- throw new Error(`failed to fetch fileUrl (redirects are rejected; this may be why): ${fileUrl} — ${detail}`, { cause: err });
58
- }
59
- finally {
60
- clearTimeout(timer);
61
- }
62
- if (!res.ok) {
63
- throw new Error(`fileUrl returned ${res.status} ${res.statusText}: ${fileUrl}`);
64
- }
65
- // Reject on a declared oversize BEFORE reading the body.
66
- const declaredLength = res.headers.get('content-length');
67
- if (declaredLength !== undefined && declaredLength !== null) {
68
- const declared = Number(declaredLength);
69
- if (Number.isFinite(declared) && declared > opts.maxBytes) {
70
- throw new Error(`fileUrl response is ${declared} bytes, exceeding the ${opts.maxBytes}-byte limit (MCP_UPLOAD_MAX_FETCH_BYTES)`);
71
- }
72
- }
73
- if (!res.body) {
74
- return new Uint8Array(0);
75
- }
76
- // Enforce the cap WHILE streaming so a source that omits or understates
77
- // Content-Length can't force an unbounded read into the shared relay.
78
- const reader = res.body.getReader();
79
- const chunks = [];
80
- let total = 0;
81
- try {
82
- for (;;) {
83
- const { done, value } = await reader.read();
84
- if (done)
85
- break;
86
- total += value.byteLength;
87
- if (total > opts.maxBytes) {
88
- await reader.cancel();
89
- throw new Error(`fileUrl response exceeds the ${opts.maxBytes}-byte limit (MCP_UPLOAD_MAX_FETCH_BYTES)`);
90
- }
91
- chunks.push(value);
92
- }
93
- }
94
- finally {
95
- reader.releaseLock();
96
- }
97
- const buf = new Uint8Array(total);
98
- let offset = 0;
99
- for (const chunk of chunks) {
100
- buf.set(chunk, offset);
101
- offset += chunk.byteLength;
102
- }
103
- return buf;
104
- }
105
- //# sourceMappingURL=fetch-source.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetch-source.js","sourceRoot":"","sources":["../../../src/tools/ingestion/fetch-source.ts"],"names":[],"mappings":"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"}