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

Function resolveRedirectDomain

server/src/db/domain-resolution-db.ts:240–297  ·  view source on GitHub ↗

* Follow HTTP redirects to find the canonical domain. * Tries HEAD first (lightweight), falls back to GET if the server rejects HEAD. * Returns the resolved domain, or null on failure. * * SSRF protection: only follows HTTPS, rejects private IPs and non-hostname targets.

(domain: string)

Source from the content-addressed store, hash-verified

238 * SSRF protection: only follows HTTPS, rejects private IPs and non-hostname targets.
239 */
240async function resolveRedirectDomain(domain: string): Promise<string | null> {
241 // Block requests to private/reserved ranges
242 if (PRIVATE_IP_RE.test(domain)) return null;
243
244 // Only resolve domains that look like valid hostnames (no IPs, no ports)
245 if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i.test(domain)) return null;
246
247 for (const method of ['HEAD', 'GET'] as const) {
248 try {
249 const controller = new AbortController();
250 const timeout = setTimeout(() => controller.abort(), 5000);
251
252 const response = await fetch(`https://${domain}`, {
253 method,
254 redirect: 'manual',
255 signal: controller.signal,
256 });
257
258 clearTimeout(timeout);
259
260 // HEAD rejected — try GET
261 if (method === 'HEAD' && response.status === 405) continue;
262
263 // Only inspect 3xx redirects — read the Location header without following
264 const status = response.status;
265 if (status < 300 || status >= 400) return null;
266
267 const location = response.headers.get('location');
268 if (!location) return null;
269
270 let parsed: URL;
271 try {
272 parsed = new URL(location, `https://${domain}`);
273 } catch {
274 return null;
275 }
276
277 // Only accept HTTPS redirects to valid hostnames
278 if (parsed.protocol !== 'https:') return null;
279 if (PRIVATE_IP_RE.test(parsed.hostname)) return null;
280 // Validate hostname format (no IPs, no ports in hostname)
281 if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i.test(parsed.hostname)) return null;
282
283 const finalDomain = parsed.hostname.replace(/^www\./, '').toLowerCase();
284 if (finalDomain !== domain) {
285 logger.debug({ from: domain, to: finalDomain }, 'Domain redirect detected');
286 return finalDomain;
287 }
288
289 return null;
290 } catch {
291 if (method === 'HEAD') continue; // try GET
292 return null; // both failed
293 }
294 }
295
296 return null;
297}

Callers 1

resolveOrgByDomainFunction · 0.85

Calls 1

getMethod · 0.45

Tested by

no test coverage detected