@j0hanz/fetch-url-mcp 1.6.0 → 1.7.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 +507 -403
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/http/auth.d.ts +12 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +85 -6
- package/dist/http/health.d.ts +1 -0
- package/dist/http/health.d.ts.map +1 -0
- package/dist/http/helpers.d.ts +1 -0
- package/dist/http/helpers.d.ts.map +1 -0
- package/dist/http/native.d.ts +1 -0
- package/dist/http/native.d.ts.map +1 -0
- package/dist/http/native.js +80 -63
- package/dist/http/rate-limit.d.ts +1 -0
- package/dist/http/rate-limit.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/content.d.ts +3 -0
- package/dist/lib/content.d.ts.map +1 -0
- package/dist/lib/content.js +16 -11
- package/dist/lib/core.d.ts +6 -8
- package/dist/lib/core.d.ts.map +1 -0
- package/dist/lib/core.js +111 -97
- package/dist/lib/fetch-pipeline.d.ts +1 -1
- package/dist/lib/fetch-pipeline.d.ts.map +1 -0
- package/dist/lib/fetch-pipeline.js +54 -44
- package/dist/lib/http.d.ts +1 -0
- package/dist/lib/http.d.ts.map +1 -0
- package/dist/lib/mcp-tools.d.ts +45 -7
- package/dist/lib/mcp-tools.d.ts.map +1 -0
- package/dist/lib/mcp-tools.js +37 -6
- package/dist/lib/net-utils.d.ts +1 -0
- package/dist/lib/net-utils.d.ts.map +1 -0
- package/dist/lib/progress.d.ts +1 -0
- package/dist/lib/progress.d.ts.map +1 -0
- package/dist/lib/task-handlers.d.ts +9 -1
- package/dist/lib/task-handlers.d.ts.map +1 -0
- package/dist/lib/task-handlers.js +30 -38
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +12 -1
- package/dist/lib/url.d.ts +3 -0
- package/dist/lib/url.d.ts.map +1 -0
- package/dist/lib/url.js +78 -151
- package/dist/lib/utils.d.ts +2 -2
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +60 -94
- package/dist/lib/zod.d.ts +3 -0
- package/dist/lib/zod.d.ts.map +1 -0
- package/dist/lib/zod.js +33 -0
- package/dist/prompts/index.d.ts +2 -1
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +2 -13
- package/dist/resources/index.d.ts +2 -1
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +5 -19
- package/dist/resources/instructions.d.ts +1 -0
- package/dist/resources/instructions.d.ts.map +1 -0
- package/dist/resources/instructions.js +2 -0
- package/dist/schemas/cache.d.ts +18 -0
- package/dist/schemas/cache.d.ts.map +1 -0
- package/dist/schemas/cache.js +19 -0
- package/dist/schemas/inputs.d.ts +1 -0
- package/dist/schemas/inputs.d.ts.map +1 -0
- package/dist/schemas/outputs.d.ts +6 -5
- package/dist/schemas/outputs.d.ts.map +1 -0
- package/dist/schemas/outputs.js +5 -9
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +9 -7
- package/dist/tasks/execution.d.ts +1 -0
- package/dist/tasks/execution.d.ts.map +1 -0
- package/dist/tasks/execution.js +3 -21
- package/dist/tasks/manager.d.ts +2 -6
- package/dist/tasks/manager.d.ts.map +1 -0
- package/dist/tasks/manager.js +2 -4
- package/dist/tasks/owner.d.ts +1 -0
- package/dist/tasks/owner.d.ts.map +1 -0
- package/dist/tasks/tool-registry.d.ts +2 -0
- package/dist/tasks/tool-registry.d.ts.map +1 -0
- package/dist/tasks/tool-registry.js +3 -0
- package/dist/tools/fetch-url.d.ts +4 -6
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +61 -59
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/transform/html-translators.d.ts +1 -0
- package/dist/transform/html-translators.d.ts.map +1 -0
- package/dist/transform/html-translators.js +5 -2
- package/dist/transform/metadata.d.ts +1 -0
- package/dist/transform/metadata.d.ts.map +1 -0
- package/dist/transform/metadata.js +1 -0
- package/dist/transform/{workers/shared.d.ts → shared.d.ts} +2 -1
- package/dist/transform/shared.d.ts.map +1 -0
- package/dist/transform/{workers/shared.js → shared.js} +1 -1
- package/dist/transform/transform.d.ts +1 -0
- package/dist/transform/transform.d.ts.map +1 -0
- package/dist/transform/transform.js +21 -14
- package/dist/transform/types.d.ts +1 -4
- package/dist/transform/types.d.ts.map +1 -0
- package/dist/transform/worker-pool.d.ts +3 -18
- package/dist/transform/worker-pool.d.ts.map +1 -0
- package/dist/transform/worker-pool.js +51 -167
- package/package.json +9 -6
- package/dist/transform/workers/transform-child.d.ts +0 -1
- package/dist/transform/workers/transform-child.js +0 -15
- package/dist/transform/workers/transform-worker.d.ts +0 -1
- package/dist/transform/workers/transform-worker.js +0 -13
package/dist/lib/mcp-tools.js
CHANGED
|
@@ -2,17 +2,46 @@ import {} from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { FetchError, isAbortError, isSystemError } from './utils.js';
|
|
4
4
|
const paramsSchema = z.looseObject({});
|
|
5
|
-
const
|
|
5
|
+
const jsonRpcRequestIdSchema = z.union([z.string(), z.number()]);
|
|
6
|
+
const jsonRpcRequestSchema = z.strictObject({
|
|
6
7
|
jsonrpc: z.literal('2.0'),
|
|
7
8
|
method: z.string().min(1),
|
|
8
|
-
id:
|
|
9
|
+
id: jsonRpcRequestIdSchema.optional(),
|
|
9
10
|
params: paramsSchema.optional(),
|
|
10
11
|
});
|
|
12
|
+
const jsonRpcResultResponseSchema = z.strictObject({
|
|
13
|
+
jsonrpc: z.literal('2.0'),
|
|
14
|
+
id: z.union([z.string(), z.number()]),
|
|
15
|
+
result: z.record(z.string(), z.unknown()),
|
|
16
|
+
});
|
|
17
|
+
const jsonRpcErrorResponseSchema = z.strictObject({
|
|
18
|
+
jsonrpc: z.literal('2.0'),
|
|
19
|
+
id: z.union([z.string(), z.number(), z.null()]).optional(),
|
|
20
|
+
error: z.strictObject({
|
|
21
|
+
code: z.number().int(),
|
|
22
|
+
message: z.string(),
|
|
23
|
+
data: z.unknown().optional(),
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
const jsonRpcResponseSchema = z.union([
|
|
27
|
+
jsonRpcResultResponseSchema,
|
|
28
|
+
jsonRpcErrorResponseSchema,
|
|
29
|
+
]);
|
|
30
|
+
const jsonRpcMessageSchema = z.union([
|
|
31
|
+
jsonRpcRequestSchema,
|
|
32
|
+
jsonRpcResponseSchema,
|
|
33
|
+
]);
|
|
11
34
|
export function isJsonRpcBatchRequest(body) {
|
|
12
35
|
return Array.isArray(body);
|
|
13
36
|
}
|
|
14
37
|
export function isMcpRequestBody(body) {
|
|
15
|
-
return
|
|
38
|
+
return jsonRpcRequestSchema.safeParse(body).success;
|
|
39
|
+
}
|
|
40
|
+
export function isJsonRpcResponseBody(body) {
|
|
41
|
+
return jsonRpcResponseSchema.safeParse(body).success;
|
|
42
|
+
}
|
|
43
|
+
export function isMcpMessageBody(body) {
|
|
44
|
+
return jsonRpcMessageSchema.safeParse(body).success;
|
|
16
45
|
}
|
|
17
46
|
function parseAcceptMediaTypes(header) {
|
|
18
47
|
if (!header)
|
|
@@ -76,8 +105,10 @@ function resolveToolErrorMessage(error, fallbackMessage) {
|
|
|
76
105
|
return `${fallbackMessage}: Unknown error`;
|
|
77
106
|
}
|
|
78
107
|
function resolveToolErrorCode(error) {
|
|
79
|
-
if (error instanceof FetchError)
|
|
80
|
-
|
|
108
|
+
if (error instanceof FetchError) {
|
|
109
|
+
const detailsCode = error.details['code'];
|
|
110
|
+
return typeof detailsCode === 'string' ? detailsCode : error.code;
|
|
111
|
+
}
|
|
81
112
|
if (isValidationError(error))
|
|
82
113
|
return 'VALIDATION_ERROR';
|
|
83
114
|
if (isAbortError(error))
|
|
@@ -103,5 +134,5 @@ export function handleToolError(error, url, fallbackMessage = 'Operation failed'
|
|
|
103
134
|
* without changes. Direct imports from the sub-modules are preferred for new code.
|
|
104
135
|
* ------------------------------------------------------------------------------------------------- */
|
|
105
136
|
export { registerServerLifecycleCleanup, registerTaskHandlers, cancelTasksForOwner, abortAllTaskExecutions, } from './task-handlers.js';
|
|
106
|
-
export {
|
|
137
|
+
export { readNestedRecord, withSignal, TRUNCATION_MARKER, appendTruncationMarker, executeFetchPipeline, parseCachedMarkdownResult, markdownTransform, serializeMarkdownResult, performSharedFetch, } from './fetch-pipeline.js';
|
|
107
138
|
export { createProgressReporter, } from './progress.js';
|
package/dist/lib/net-utils.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare function buildIpv4(parts: readonly [number, number, number, number]): string;
|
|
2
2
|
export declare function stripTrailingDots(value: string): string;
|
|
3
3
|
export declare function normalizeHostname(value: string): string | null;
|
|
4
|
+
//# sourceMappingURL=net-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"net-utils.d.ts","sourceRoot":"","sources":["../../src/lib/net-utils.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAC/C,MAAM,CAER;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS9D"}
|
package/dist/lib/progress.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/lib/progress.ts"],"names":[],"mappings":"AAOA,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AACrC,UAAU,WAAW;IACnB,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AACD,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AACD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,0BAA0B,CAAC;CACpC;AACD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D;AACD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AA0ID,wBAAgB,sBAAsB,CACpC,KAAK,CAAC,EAAE,gBAAgB,GACvB,gBAAgB,CAElB"}
|
|
@@ -2,4 +2,12 @@ import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
type CleanupCallback = () => void;
|
|
3
3
|
export declare function registerServerLifecycleCleanup(server: McpServer, callback: CleanupCallback): void;
|
|
4
4
|
export { cancelTasksForOwner, abortAllTaskExecutions, } from '../tasks/execution.js';
|
|
5
|
-
|
|
5
|
+
interface TaskHandlerRegistrationOptions {
|
|
6
|
+
requireInterception?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface TaskHandlerRegistrationResult {
|
|
9
|
+
interceptedToolsCall: boolean;
|
|
10
|
+
taskCapableToolsRegistered: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function registerTaskHandlers(server: McpServer, options?: TaskHandlerRegistrationOptions): TaskHandlerRegistrationResult;
|
|
13
|
+
//# sourceMappingURL=task-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-handlers.d.ts","sourceRoot":"","sources":["../../src/lib/task-handlers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA0CzE,KAAK,eAAe,GAAG,MAAM,IAAI,CAAC;AAyClC,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,eAAe,GACxB,IAAI,CAGN;AAED,OAAO,EACL,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AA+E/B,UAAU,8BAA8B;IACtC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,UAAU,6BAA6B;IACrC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,0BAA0B,EAAE,OAAO,CAAC;CACrC;AAUD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,OAAO,CAAC,EAAE,8BAA8B,GACvC,6BAA6B,CAiK/B"}
|
|
@@ -5,8 +5,9 @@ import { z } from 'zod';
|
|
|
5
5
|
import { abortTaskExecution, emitTaskStatusNotification, handleToolCallRequest, throwTaskNotFound, toTaskSummary, withRelatedTaskMeta, } from '../tasks/execution.js';
|
|
6
6
|
import { taskManager } from '../tasks/manager.js';
|
|
7
7
|
import { isServerResult, parseHandlerExtra, resolveTaskOwnerKey, resolveToolCallContext, } from '../tasks/owner.js';
|
|
8
|
-
import { hasTaskCapableTool } from '../tasks/tool-registry.js';
|
|
8
|
+
import { hasRegisteredTaskCapableTools, hasTaskCapableTool, } from '../tasks/tool-registry.js';
|
|
9
9
|
import { logWarn, runWithRequestContext } from './core.js';
|
|
10
|
+
import { formatZodError } from './zod.js';
|
|
10
11
|
const patchedCleanupServers = new WeakSet();
|
|
11
12
|
const serverCleanupCallbacks = new WeakMap();
|
|
12
13
|
function getServerCleanupCallbackSet(server) {
|
|
@@ -55,42 +56,31 @@ export { cancelTasksForOwner, abortAllTaskExecutions, } from '../tasks/execution
|
|
|
55
56
|
/* -------------------------------------------------------------------------------------------------
|
|
56
57
|
* Task handler schemas and registration
|
|
57
58
|
* ------------------------------------------------------------------------------------------------- */
|
|
58
|
-
const TaskGetSchema = z
|
|
59
|
-
.object({
|
|
59
|
+
const TaskGetSchema = z.looseObject({
|
|
60
60
|
method: z.literal('tasks/get'),
|
|
61
|
-
params: z.
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const TaskListSchema = z
|
|
65
|
-
.object({
|
|
61
|
+
params: z.looseObject({ taskId: z.string() }),
|
|
62
|
+
});
|
|
63
|
+
const TaskListSchema = z.looseObject({
|
|
66
64
|
method: z.literal('tasks/list'),
|
|
67
65
|
params: z
|
|
68
|
-
.
|
|
66
|
+
.looseObject({
|
|
69
67
|
cursor: z.string().optional(),
|
|
70
68
|
})
|
|
71
|
-
.loose()
|
|
72
69
|
.optional(),
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const TaskCancelSchema = z
|
|
76
|
-
.object({
|
|
70
|
+
});
|
|
71
|
+
const TaskCancelSchema = z.looseObject({
|
|
77
72
|
method: z.literal('tasks/cancel'),
|
|
78
|
-
params: z.
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const TaskResultSchema = z
|
|
82
|
-
.object({
|
|
73
|
+
params: z.looseObject({ taskId: z.string() }),
|
|
74
|
+
});
|
|
75
|
+
const TaskResultSchema = z.looseObject({
|
|
83
76
|
method: z.literal('tasks/result'),
|
|
84
|
-
params: z.
|
|
85
|
-
})
|
|
86
|
-
.loose();
|
|
77
|
+
params: z.looseObject({ taskId: z.string() }),
|
|
78
|
+
});
|
|
87
79
|
const MIN_TASK_TTL_MS = 1_000;
|
|
88
80
|
const MAX_TASK_TTL_MS = 86_400_000;
|
|
89
|
-
const ExtendedCallToolRequestSchema = z
|
|
90
|
-
.object({
|
|
81
|
+
const ExtendedCallToolRequestSchema = z.looseObject({
|
|
91
82
|
method: z.literal('tools/call'),
|
|
92
|
-
params: z
|
|
93
|
-
.object({
|
|
83
|
+
params: z.looseObject({
|
|
94
84
|
name: z.string().min(1),
|
|
95
85
|
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
96
86
|
task: z
|
|
@@ -104,7 +94,7 @@ const ExtendedCallToolRequestSchema = z
|
|
|
104
94
|
})
|
|
105
95
|
.optional(),
|
|
106
96
|
_meta: z
|
|
107
|
-
.
|
|
97
|
+
.looseObject({
|
|
108
98
|
progressToken: z.union([z.string(), z.number()]).optional(),
|
|
109
99
|
'io.modelcontextprotocol/related-task': z
|
|
110
100
|
.strictObject({
|
|
@@ -112,21 +102,14 @@ const ExtendedCallToolRequestSchema = z
|
|
|
112
102
|
})
|
|
113
103
|
.optional(),
|
|
114
104
|
})
|
|
115
|
-
.loose()
|
|
116
105
|
.optional(),
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
})
|
|
120
|
-
.loose();
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
121
108
|
function parseExtendedCallToolRequest(request) {
|
|
122
109
|
const parsed = ExtendedCallToolRequestSchema.safeParse(request);
|
|
123
110
|
if (parsed.success)
|
|
124
111
|
return parsed.data;
|
|
125
|
-
|
|
126
|
-
const details = Object.entries(flat.fieldErrors)
|
|
127
|
-
.map(([k, v]) => `${k}: ${(v ?? []).join(', ')}`)
|
|
128
|
-
.join('; ') || flat.formErrors.join('; ');
|
|
129
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tool request params: ${details}`);
|
|
112
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tool request params: ${formatZodError(parsed.error)}`);
|
|
130
113
|
}
|
|
131
114
|
function resolveOwnerScopedExtra(extra) {
|
|
132
115
|
const parsedExtra = parseHandlerExtra(extra);
|
|
@@ -143,9 +126,14 @@ function getSdkCallToolHandler(server) {
|
|
|
143
126
|
const handler = maybeHandlers.get('tools/call');
|
|
144
127
|
return typeof handler === 'function' ? handler : null;
|
|
145
128
|
}
|
|
146
|
-
export function registerTaskHandlers(server) {
|
|
129
|
+
export function registerTaskHandlers(server, options) {
|
|
147
130
|
const sdkCallToolHandler = getSdkCallToolHandler(server);
|
|
131
|
+
const taskCapableToolsRegistered = hasRegisteredTaskCapableTools();
|
|
132
|
+
const requireInterception = options?.requireInterception ?? true;
|
|
148
133
|
if (!sdkCallToolHandler) {
|
|
134
|
+
if (taskCapableToolsRegistered && requireInterception) {
|
|
135
|
+
throw new Error('Task-capable tools are registered but SDK tools/call interception is unavailable. Upgrade compatibility or disable strict interception with TASKS_REQUIRE_INTERCEPTION=false.');
|
|
136
|
+
}
|
|
149
137
|
logWarn('Task call interception disabled: SDK tools/call handler unavailable; task-capable tools require MCP SDK compatibility update', { sdkVersion: 'unknown' });
|
|
150
138
|
}
|
|
151
139
|
if (sdkCallToolHandler) {
|
|
@@ -239,4 +227,8 @@ export function registerTaskHandlers(server) {
|
|
|
239
227
|
emitTaskStatusNotification(server, task);
|
|
240
228
|
return toTaskSummary(task);
|
|
241
229
|
});
|
|
230
|
+
return {
|
|
231
|
+
interceptedToolsCall: sdkCallToolHandler !== null,
|
|
232
|
+
taskCapableToolsRegistered,
|
|
233
|
+
};
|
|
242
234
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,CAAC,EAAE,QAAQ,GAClB;IAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAU/C"}
|
package/dist/lib/types.js
CHANGED
package/dist/lib/url.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare class SafeDnsResolver {
|
|
|
4
4
|
private readonly ipBlocker;
|
|
5
5
|
private readonly security;
|
|
6
6
|
private readonly blockedHostSuffixes;
|
|
7
|
+
private readonly cnameResolver;
|
|
7
8
|
constructor(ipBlocker: IpBlocker, security: SecurityConfig, blockedHostSuffixes: readonly string[]);
|
|
8
9
|
resolveAndValidate(hostname: string, signal?: AbortSignal): Promise<string>;
|
|
9
10
|
private isBlockedHostname;
|
|
@@ -50,6 +51,7 @@ export declare class IpBlocker {
|
|
|
50
51
|
private readonly blockList;
|
|
51
52
|
constructor(security: SecurityConfig);
|
|
52
53
|
isBlockedIp(candidate: string): boolean;
|
|
54
|
+
isHostBlocked(hostname: string, blockedHostSuffixes: readonly string[]): boolean;
|
|
53
55
|
}
|
|
54
56
|
type ConstantsConfig = typeof config.constants;
|
|
55
57
|
export declare class UrlNormalizer {
|
|
@@ -68,3 +70,4 @@ export declare class UrlNormalizer {
|
|
|
68
70
|
private assertHostnameAllowed;
|
|
69
71
|
}
|
|
70
72
|
export {};
|
|
73
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/lib/url.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/url.js
CHANGED
|
@@ -9,29 +9,6 @@ function normalizeDnsName(value) {
|
|
|
9
9
|
const normalized = value.trim().toLowerCase().replace(/\.+$/, '');
|
|
10
10
|
return normalized;
|
|
11
11
|
}
|
|
12
|
-
function createSignalAbortRace(signal, isAbort, onTimeout, onAbort) {
|
|
13
|
-
let abortListener = null;
|
|
14
|
-
const abortPromise = new Promise((_, reject) => {
|
|
15
|
-
abortListener = () => {
|
|
16
|
-
reject(isAbort() ? onAbort() : onTimeout());
|
|
17
|
-
};
|
|
18
|
-
signal.addEventListener('abort', abortListener, { once: true });
|
|
19
|
-
if (signal.aborted)
|
|
20
|
-
abortListener();
|
|
21
|
-
});
|
|
22
|
-
const cleanup = () => {
|
|
23
|
-
if (!abortListener)
|
|
24
|
-
return;
|
|
25
|
-
try {
|
|
26
|
-
signal.removeEventListener('abort', abortListener);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// Ignore listener cleanup failures; they are non-fatal by design.
|
|
30
|
-
}
|
|
31
|
-
abortListener = null;
|
|
32
|
-
};
|
|
33
|
-
return { abortPromise, cleanup };
|
|
34
|
-
}
|
|
35
12
|
async function withTimeout(promise, timeoutMs, onTimeout, signal, onAbort) {
|
|
36
13
|
const timeoutSignal = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;
|
|
37
14
|
const raceSignal = signal && timeoutSignal
|
|
@@ -39,13 +16,25 @@ async function withTimeout(promise, timeoutMs, onTimeout, signal, onAbort) {
|
|
|
39
16
|
: (signal ?? timeoutSignal);
|
|
40
17
|
if (!raceSignal)
|
|
41
18
|
return promise;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const onAborted = () => {
|
|
21
|
+
reject(signal?.aborted
|
|
22
|
+
? (onAbort?.() ?? new Error('Request was canceled'))
|
|
23
|
+
: onTimeout());
|
|
24
|
+
};
|
|
25
|
+
if (raceSignal.aborted) {
|
|
26
|
+
onAborted();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
raceSignal.addEventListener('abort', onAborted, { once: true });
|
|
30
|
+
promise.then((value) => {
|
|
31
|
+
raceSignal.removeEventListener('abort', onAborted);
|
|
32
|
+
resolve(value);
|
|
33
|
+
}, (err) => {
|
|
34
|
+
raceSignal.removeEventListener('abort', onAborted);
|
|
35
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
49
38
|
}
|
|
50
39
|
function createAbortSignalError() {
|
|
51
40
|
const err = new Error('Request was canceled');
|
|
@@ -56,6 +45,9 @@ export class SafeDnsResolver {
|
|
|
56
45
|
ipBlocker;
|
|
57
46
|
security;
|
|
58
47
|
blockedHostSuffixes;
|
|
48
|
+
cnameResolver = new dns.promises.Resolver({
|
|
49
|
+
timeout: DNS_LOOKUP_TIMEOUT_MS,
|
|
50
|
+
});
|
|
59
51
|
constructor(ipBlocker, security, blockedHostSuffixes) {
|
|
60
52
|
this.ipBlocker = ipBlocker;
|
|
61
53
|
this.security = security;
|
|
@@ -105,13 +97,7 @@ export class SafeDnsResolver {
|
|
|
105
97
|
return addresses[0].address;
|
|
106
98
|
}
|
|
107
99
|
isBlockedHostname(hostname) {
|
|
108
|
-
|
|
109
|
-
return true;
|
|
110
|
-
if (isLocalFetchAllowed())
|
|
111
|
-
return false;
|
|
112
|
-
if (this.security.blockedHosts.has(hostname))
|
|
113
|
-
return true;
|
|
114
|
-
return this.blockedHostSuffixes.some((suffix) => hostname.endsWith(suffix));
|
|
100
|
+
return this.ipBlocker.isHostBlocked(hostname, this.blockedHostSuffixes);
|
|
115
101
|
}
|
|
116
102
|
async assertNoBlockedCname(hostname, signal) {
|
|
117
103
|
let current = hostname;
|
|
@@ -133,8 +119,9 @@ export class SafeDnsResolver {
|
|
|
133
119
|
}
|
|
134
120
|
async resolveCname(hostname, signal) {
|
|
135
121
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
122
|
+
if (signal?.aborted)
|
|
123
|
+
throw createAbortSignalError();
|
|
124
|
+
const cnames = await this.cnameResolver.resolveCname(hostname);
|
|
138
125
|
return cnames
|
|
139
126
|
.map((value) => normalizeDnsName(value))
|
|
140
127
|
.filter((value) => value.length > 0);
|
|
@@ -146,7 +133,8 @@ export class SafeDnsResolver {
|
|
|
146
133
|
if (isSystemError(error) &&
|
|
147
134
|
(error.code === 'ENODATA' ||
|
|
148
135
|
error.code === 'ENOTFOUND' ||
|
|
149
|
-
error.code === 'ENODOMAIN'
|
|
136
|
+
error.code === 'ENODOMAIN' ||
|
|
137
|
+
error.code === 'ETIMEOUT')) {
|
|
150
138
|
return [];
|
|
151
139
|
}
|
|
152
140
|
logDebug('DNS CNAME lookup failed; continuing with address lookup', {
|
|
@@ -158,12 +146,10 @@ export class SafeDnsResolver {
|
|
|
158
146
|
}
|
|
159
147
|
}
|
|
160
148
|
function extractHostname(url) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
149
|
+
const parsed = URL.parse(url);
|
|
150
|
+
if (!parsed)
|
|
165
151
|
throw createErrorWithCode('Invalid URL', 'EINVAL');
|
|
166
|
-
|
|
152
|
+
return parsed.hostname;
|
|
167
153
|
}
|
|
168
154
|
export function createDnsPreflight(dnsResolver) {
|
|
169
155
|
return async (url, signal) => {
|
|
@@ -178,19 +164,13 @@ export function normalizeHost(value) {
|
|
|
178
164
|
const first = takeFirstHostValue(trimmedLower);
|
|
179
165
|
if (!first)
|
|
180
166
|
return null;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return candidate;
|
|
189
|
-
}
|
|
190
|
-
if (isIpV6Literal(first)) {
|
|
191
|
-
return normalizeHostname(first);
|
|
192
|
-
}
|
|
193
|
-
return normalizeHostname(stripPortIfPresent(first));
|
|
167
|
+
const socketAddr = SocketAddress.parse(first);
|
|
168
|
+
if (socketAddr)
|
|
169
|
+
return normalizeHostname(socketAddr.address);
|
|
170
|
+
const parsed = URL.parse(`http://${first}`);
|
|
171
|
+
if (parsed)
|
|
172
|
+
return normalizeHostname(parsed.hostname);
|
|
173
|
+
return normalizeHostname(first);
|
|
194
174
|
}
|
|
195
175
|
function takeFirstHostValue(value) {
|
|
196
176
|
// Faster than split(',') for large forwarded headers; preserves behavior.
|
|
@@ -198,47 +178,6 @@ function takeFirstHostValue(value) {
|
|
|
198
178
|
const first = commaIndex === -1 ? value : value.slice(0, commaIndex);
|
|
199
179
|
return first ? trimToNull(first) : null;
|
|
200
180
|
}
|
|
201
|
-
function stripIpv6Brackets(value) {
|
|
202
|
-
if (!value.startsWith('['))
|
|
203
|
-
return null;
|
|
204
|
-
const end = value.indexOf(']');
|
|
205
|
-
if (end === -1)
|
|
206
|
-
return null;
|
|
207
|
-
return value.slice(1, end);
|
|
208
|
-
}
|
|
209
|
-
function stripPortIfPresent(value) {
|
|
210
|
-
const colonIndex = value.indexOf(':');
|
|
211
|
-
if (colonIndex === -1)
|
|
212
|
-
return value;
|
|
213
|
-
return value.slice(0, colonIndex);
|
|
214
|
-
}
|
|
215
|
-
function isIpV6Literal(value) {
|
|
216
|
-
return isIP(value) === 6;
|
|
217
|
-
}
|
|
218
|
-
function normalizeSocketAddress(value) {
|
|
219
|
-
const socketAddress = SocketAddress.parse(value);
|
|
220
|
-
if (!socketAddress)
|
|
221
|
-
return null;
|
|
222
|
-
return normalizeHostname(socketAddress.address);
|
|
223
|
-
}
|
|
224
|
-
function normalizeBracketedIpv6(value) {
|
|
225
|
-
const ipv6 = stripIpv6Brackets(value);
|
|
226
|
-
if (!ipv6)
|
|
227
|
-
return null;
|
|
228
|
-
return normalizeHostname(ipv6);
|
|
229
|
-
}
|
|
230
|
-
function parseHostWithUrl(value) {
|
|
231
|
-
const candidateUrl = `http://${value}`;
|
|
232
|
-
if (!URL.canParse(candidateUrl))
|
|
233
|
-
return null;
|
|
234
|
-
try {
|
|
235
|
-
const parsed = new URL(candidateUrl);
|
|
236
|
-
return normalizeHostname(parsed.hostname);
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
181
|
function trimToNull(value) {
|
|
243
182
|
const trimmed = value.trim();
|
|
244
183
|
return trimmed ? trimmed : null;
|
|
@@ -284,23 +223,13 @@ export function createDefaultBlockList() {
|
|
|
284
223
|
}
|
|
285
224
|
return list;
|
|
286
225
|
}
|
|
287
|
-
function extractMappedIpv4(ip) {
|
|
288
|
-
if (!ip.startsWith(IPV6_MAPPED_PREFIX))
|
|
289
|
-
return null;
|
|
290
|
-
const mapped = ip.slice(IPV6_MAPPED_PREFIX.length);
|
|
291
|
-
return isIP(mapped) === 4 ? mapped : null;
|
|
292
|
-
}
|
|
293
|
-
function stripIpv6ZoneId(ip) {
|
|
294
|
-
const zoneIndex = ip.indexOf('%');
|
|
295
|
-
if (zoneIndex <= 0)
|
|
296
|
-
return ip;
|
|
297
|
-
return ip.slice(0, zoneIndex);
|
|
298
|
-
}
|
|
299
226
|
export function normalizeIpForBlockList(input) {
|
|
300
227
|
const lowered = input.trim().toLowerCase();
|
|
301
228
|
if (!lowered)
|
|
302
229
|
return null;
|
|
303
|
-
|
|
230
|
+
// Strip IPv6 zone ID (e.g. %eth0)
|
|
231
|
+
const zoneIndex = lowered.indexOf('%');
|
|
232
|
+
const normalizedInput = zoneIndex > 0 ? lowered.slice(0, zoneIndex) : lowered;
|
|
304
233
|
if (!normalizedInput)
|
|
305
234
|
return null;
|
|
306
235
|
const ipType = isIP(normalizedInput);
|
|
@@ -308,10 +237,13 @@ export function normalizeIpForBlockList(input) {
|
|
|
308
237
|
case 4:
|
|
309
238
|
return { ip: normalizedInput, family: 'ipv4' };
|
|
310
239
|
case 6: {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
240
|
+
// Extract mapped IPv4 from ::ffff: prefix
|
|
241
|
+
if (normalizedInput.startsWith(IPV6_MAPPED_PREFIX)) {
|
|
242
|
+
const mapped = normalizedInput.slice(IPV6_MAPPED_PREFIX.length);
|
|
243
|
+
if (isIP(mapped) === 4)
|
|
244
|
+
return { ip: mapped, family: 'ipv4' };
|
|
245
|
+
}
|
|
246
|
+
return { ip: normalizedInput, family: 'ipv6' };
|
|
315
247
|
}
|
|
316
248
|
default:
|
|
317
249
|
return null;
|
|
@@ -385,12 +317,13 @@ export class RawUrlTransformer {
|
|
|
385
317
|
let base;
|
|
386
318
|
let hash;
|
|
387
319
|
let parsed;
|
|
388
|
-
|
|
389
|
-
|
|
320
|
+
const maybeParsed = URL.parse(url);
|
|
321
|
+
if (maybeParsed) {
|
|
322
|
+
parsed = maybeParsed;
|
|
390
323
|
base = parsed.origin + parsed.pathname;
|
|
391
324
|
({ hash } = parsed);
|
|
392
325
|
}
|
|
393
|
-
|
|
326
|
+
else {
|
|
394
327
|
({ base, hash } = this.splitParams(url));
|
|
395
328
|
}
|
|
396
329
|
const match = this.tryTransformWithUrl(base, hash, parsed);
|
|
@@ -408,22 +341,20 @@ export class RawUrlTransformer {
|
|
|
408
341
|
return false;
|
|
409
342
|
if (this.isRawUrl(urlString))
|
|
410
343
|
return true;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const pathname =
|
|
344
|
+
const parsed = URL.parse(urlString);
|
|
345
|
+
if (parsed) {
|
|
346
|
+
const pathname = parsed.pathname.toLowerCase();
|
|
414
347
|
const lastDot = pathname.lastIndexOf('.');
|
|
415
348
|
if (lastDot === -1)
|
|
416
349
|
return false;
|
|
417
350
|
return RAW_TEXT_EXTENSIONS.has(pathname.slice(lastDot));
|
|
418
351
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
return RAW_TEXT_EXTENSIONS.has(lowerBase.slice(lastDot));
|
|
426
|
-
}
|
|
352
|
+
const { base } = this.splitParams(urlString);
|
|
353
|
+
const lowerBase = base.toLowerCase();
|
|
354
|
+
const lastDot = lowerBase.lastIndexOf('.');
|
|
355
|
+
if (lastDot === -1)
|
|
356
|
+
return false;
|
|
357
|
+
return RAW_TEXT_EXTENSIONS.has(lowerBase.slice(lastDot));
|
|
427
358
|
}
|
|
428
359
|
isRawUrl(url) {
|
|
429
360
|
const lower = url.toLowerCase();
|
|
@@ -440,15 +371,7 @@ export class RawUrlTransformer {
|
|
|
440
371
|
return { base: urlString.slice(0, endIndex), hash };
|
|
441
372
|
}
|
|
442
373
|
tryTransformWithUrl(base, hash, preParsed) {
|
|
443
|
-
|
|
444
|
-
if (!parsed) {
|
|
445
|
-
try {
|
|
446
|
-
parsed = new URL(base);
|
|
447
|
-
}
|
|
448
|
-
catch {
|
|
449
|
-
// Ignore invalid URLs
|
|
450
|
-
}
|
|
451
|
-
}
|
|
374
|
+
const parsed = preParsed ?? URL.parse(base);
|
|
452
375
|
if (!parsed)
|
|
453
376
|
return null;
|
|
454
377
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
@@ -594,6 +517,15 @@ export class IpBlocker {
|
|
|
594
517
|
? this.blockList.check(normalizedIp.ip, normalizedIp.family)
|
|
595
518
|
: false;
|
|
596
519
|
}
|
|
520
|
+
isHostBlocked(hostname, blockedHostSuffixes) {
|
|
521
|
+
if (isCloudMetadataHost(hostname))
|
|
522
|
+
return true;
|
|
523
|
+
if (isLocalFetchAllowed())
|
|
524
|
+
return false;
|
|
525
|
+
if (this.security.blockedHosts.has(hostname))
|
|
526
|
+
return true;
|
|
527
|
+
return blockedHostSuffixes.some((suffix) => hostname.endsWith(suffix));
|
|
528
|
+
}
|
|
597
529
|
}
|
|
598
530
|
export class UrlNormalizer {
|
|
599
531
|
constants;
|
|
@@ -611,11 +543,8 @@ export class UrlNormalizer {
|
|
|
611
543
|
if (trimmedUrl.length > this.constants.maxUrlLength) {
|
|
612
544
|
throw createValidationError(`URL exceeds maximum length of ${this.constants.maxUrlLength} characters`);
|
|
613
545
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
url = new URL(trimmedUrl);
|
|
617
|
-
}
|
|
618
|
-
catch {
|
|
546
|
+
const url = URL.parse(trimmedUrl);
|
|
547
|
+
if (!url) {
|
|
619
548
|
throw createValidationError('Invalid URL format');
|
|
620
549
|
}
|
|
621
550
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
@@ -652,13 +581,11 @@ export class UrlNormalizer {
|
|
|
652
581
|
if (isCloudMetadataHost(hostname)) {
|
|
653
582
|
throw createValidationError(`Blocked host: ${hostname}. Cloud metadata endpoints are not allowed`);
|
|
654
583
|
}
|
|
655
|
-
if (!isLocalFetchAllowed()) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
throw createValidationError(`Blocked IP range: ${hostname}. Private IPs are not allowed`);
|
|
661
|
-
}
|
|
584
|
+
if (!isLocalFetchAllowed() && this.security.blockedHosts.has(hostname)) {
|
|
585
|
+
throw createValidationError(`Blocked host: ${hostname}. Internal hosts are not allowed`);
|
|
586
|
+
}
|
|
587
|
+
if (!isLocalFetchAllowed() && this.ipBlocker.isBlockedIp(hostname)) {
|
|
588
|
+
throw createValidationError(`Blocked IP range: ${hostname}. Private IPs are not allowed`);
|
|
662
589
|
}
|
|
663
590
|
if (this.blockedHostSuffixes.some((suffix) => hostname.endsWith(suffix))) {
|
|
664
591
|
throw createValidationError(`Blocked hostname pattern: ${hostname}. Internal domain suffixes are not allowed`);
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -15,8 +15,7 @@ export declare function toError(error: unknown): Error;
|
|
|
15
15
|
export declare function isAbortError(error: unknown): boolean;
|
|
16
16
|
export declare function createErrorWithCode(message: string, code: string, options?: ErrorOptions): NodeJS.ErrnoException;
|
|
17
17
|
export declare function isSystemError(error: unknown): error is NodeJS.ErrnoException;
|
|
18
|
-
export declare
|
|
19
|
-
export declare function stableStringify(obj: unknown, depth?: number, seen?: WeakSet<WeakKey>): string;
|
|
18
|
+
export declare function stableStringify(obj: unknown): string;
|
|
20
19
|
interface HttpServerTuningTarget {
|
|
21
20
|
headersTimeout?: number;
|
|
22
21
|
requestTimeout?: number;
|
|
@@ -56,3 +55,4 @@ interface LikeNode {
|
|
|
56
55
|
}
|
|
57
56
|
export declare function isLikeNode(value: unknown): value is LikeNode;
|
|
58
57
|
export {};
|
|
58
|
+
//# sourceMappingURL=utils.d.ts.map
|