MCPcopy Create free account
hub / github.com/adcontextprotocol/adcp / safeFetch

Function safeFetch

server/src/utils/url-security.ts:173–197  ·  view source on GitHub ↗
(
  url: string,
  options?: { headers?: Record<string, string>; maxRedirects?: number; method?: 'GET' | 'HEAD'; signal?: AbortSignal },
)

Source from the content-addressed store, hash-verified

171 * callers receive a Response with no tainted URL flowing to fetch().
172 */
173export async function safeFetch(
174 url: string,
175 options?: { headers?: Record<string, string>; maxRedirects?: number; method?: 'GET' | 'HEAD'; signal?: AbortSignal },
176): Promise<Response> {
177 const parsedUrl = new URL(url);
178 await validateFetchUrl(parsedUrl);
179
180 const headers = options?.headers ?? {};
181 const maxRedirects = options?.maxRedirects ?? 5;
182 const method = options?.method ?? 'GET';
183 const signal = options?.signal;
184
185 // URL is validated above by validateFetchUrl (rejects private IPs, link-local, etc).
186 let response = await fetch(sanitizeUrl(parsedUrl), { method, headers, redirect: 'manual', signal });
187
188 for (let i = 0; i < maxRedirects && [301, 302, 303, 307, 308].includes(response.status); i++) {
189 const location = response.headers.get('location');
190 if (!location) throw new Error('Redirect with no Location header');
191 // validateRedirectTarget re-validates the resolved hop against the same private-IP rules.
192 const redirectUrl = await validateRedirectTarget(location, parsedUrl);
193 response = await fetch(sanitizeUrl(redirectUrl), { method, headers, redirect: 'manual', signal });
194 }
195
196 return response;
197}

Callers 4

createContentRouterFunction · 0.85
fetchRssFeedFunction · 0.85
fetchWithTimeoutFunction · 0.85
createBrandToolHandlersFunction · 0.85

Calls 4

validateFetchUrlFunction · 0.85
sanitizeUrlFunction · 0.85
validateRedirectTargetFunction · 0.85
getMethod · 0.45

Tested by

no test coverage detected