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

Function assertPublicTarget

server/src/training-agent/webhook-fetch.ts:80–112  ·  view source on GitHub ↗
(url: URL)

Source from the content-addressed store, hash-verified

78}
79
80async function assertPublicTarget(url: URL): Promise<void> {
81 if (url.protocol !== 'https:' && url.protocol !== 'http:') {
82 throw new SsrfRefusedError(url.toString(), `scheme ${url.protocol} not allowed`);
83 }
84 // `url.hostname` strips brackets from `[::1]` → `::1`. Userinfo (user:pass@)
85 // never leaks into hostname per WHATWG, so we don't need to scrub that.
86 const hostname = url.hostname;
87 if (!hostname || hostname === 'localhost' || hostname.endsWith('.localhost')) {
88 throw new SsrfRefusedError(url.toString(), 'hostname resolves to loopback');
89 }
90 const version = isIP(hostname);
91 if (version !== 0) {
92 if (isPrivateIpAddress(hostname)) {
93 throw new SsrfRefusedError(url.toString(), 'literal private/loopback address');
94 }
95 return;
96 }
97 if (isNumericHostname(hostname)) {
98 throw new SsrfRefusedError(url.toString(), 'numeric-encoded hostname not allowed');
99 }
100 try {
101 const records = await lookup(hostname, { all: true, verbatim: true });
102 if (records.length === 0) {
103 throw new SsrfRefusedError(url.toString(), 'hostname did not resolve');
104 }
105 if (records.some(r => isPrivateIpAddress(r.address))) {
106 throw new SsrfRefusedError(url.toString(), 'hostname resolves to private address');
107 }
108 } catch (err) {
109 if (err instanceof SsrfRefusedError) throw err;
110 throw new SsrfRefusedError(url.toString(), `DNS lookup failed: ${err instanceof Error ? err.message : String(err)}`);
111 }
112}
113
114/** Build a `fetch`-shaped function gated by the SSRF guard.
115 *

Callers 1

createWebhookFetchFunction · 0.85

Calls 3

isPrivateIpAddressFunction · 0.85
isNumericHostnameFunction · 0.85
lookupFunction · 0.85

Tested by

no test coverage detected