feat(core): implement Stage 2 security and consistency improvements for web_fetch (#22217)

This commit is contained in:
Aishanee Shah
2026-03-16 17:38:53 -04:00
committed by GitHub
parent b6c6da3618
commit 990d010ecf
4 changed files with 250 additions and 86 deletions
+32
View File
@@ -8,6 +8,7 @@ import { getErrorMessage, isNodeError } from './errors.js';
import { URL } from 'node:url';
import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici';
import ipaddr from 'ipaddr.js';
import { lookup } from 'node:dns/promises';
const DEFAULT_HEADERS_TIMEOUT = 300000; // 5 minutes
const DEFAULT_BODY_TIMEOUT = 300000; // 5 minutes
@@ -23,6 +24,13 @@ export class FetchError extends Error {
}
}
export class PrivateIpError extends Error {
constructor(message = 'Access to private network is blocked') {
super(message);
this.name = 'PrivateIpError';
}
}
// Configure default global dispatcher with higher timeouts
setGlobalDispatcher(
new Agent({
@@ -115,6 +123,30 @@ export function isAddressPrivate(address: string): boolean {
}
}
/**
* Checks if a URL resolves to a private IP address.
*/
export async function isPrivateIpAsync(url: string): Promise<boolean> {
try {
const parsedUrl = new URL(url);
const hostname = parsedUrl.hostname;
if (isLoopbackHost(hostname)) {
return false;
}
const addresses = await lookup(hostname, { all: true });
return addresses.some((addr) => isAddressPrivate(addr.address));
} catch (error) {
if (error instanceof TypeError) {
return false;
}
throw new Error('Failed to verify if URL resolves to private IP', {
cause: error,
});
}
}
/**
* Creates an undici ProxyAgent that incorporates safe DNS lookup.
*/