ComposioHQ/composio · error · ComposioBlockedInternalUrlError
Could not resolve host "${host}"
Error message
Could not resolve host "${host}" What it means
When fetching a user-supplied URL, the SSRF guard resolves the hostname with DNS lookup() before connecting. If the DNS lookup itself throws (NXDOMAIN, DNSSEC failure, resolver unreachable), the fetch is refused with this error (the lookup-thrown variant).
Source
Thrown at ts/packages/core/src/utils/ssrfGuard.node.ts:192
url = new URL(rawUrl);
} catch {
throw new ComposioBlockedInternalUrlError('Refusing to fetch a malformed URL', { url: rawUrl });
}
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
throw new ComposioBlockedInternalUrlError(
`Refusing to fetch a non-http(s) URL (scheme "${url.protocol}")`,
{ url: rawUrl }
);
}
const host = url.hostname.replace(/^\[|\]$/g, '');
let resolved: Array<{ address: string }>;
try {
resolved = await lookup(host, { all: true, verbatim: true });
} catch {
throw new ComposioBlockedInternalUrlError(`Could not resolve host "${host}"`, { url: rawUrl });
}
if (resolved.length === 0) {
throw new ComposioBlockedInternalUrlError(`Could not resolve host "${host}"`, { url: rawUrl });
}
for (const { address } of resolved) {
if (isBlockedIp(address)) {
throw new ComposioBlockedInternalUrlError(
`Refusing to fetch "${host}" — it resolves to a private, loopback, or link-local address`,
{ url: rawUrl, resolvedIp: address }
);
}
}
// Every answer was validated, so all of them are safe to connect to, and
// resolver order is the system's own address preference.
return resolved.map(({ address }) => address);View on GitHub (pinned to 64b1b85502)
Solutions
- Verify the hostname resolves from the machine running the SDK: nslookup <host> or dns.lookup
- For internal hosts, upload the file bytes directly instead of by URL — internal names are intentionally unsupported
- Fix typos or use the canonical public hostname
Example fix
// before
await upload.uploadFileAtUrl('https://myapp.local/report.pdf');
// after
// internal host: fetch it yourself and upload the bytes
const bytes = await fetchInternal();
await upload.uploadFile(bytes); Defensive patterns
Strategy: validation
Validate before calling
import { lookup } from 'node:dns/promises';
try { await lookup(host, { all: true }); } catch { throw new Error('Host unresolvable, reject early'); } Try / catch
try {
await upload.uploadFileAtUrl(url);
} catch (e) {
if (e instanceof ComposioBlockedInternalUrlError && /Could not resolve host/.test(e.message)) {
// fix hostname or upload bytes directly
}
} Prevention
- Verify hostnames resolve publicly before offering them as upload URLs
- Don't pass internal (.local, service names) hosts to URL uploads
- Upload bytes directly when the source is internal
When it happens
Trigger: Passing a URL whose hostname does not exist or cannot be resolved from the server running the SDK — typo'd domains, freshly-dead domains, or environments with broken/filtered DNS.
Common situations: Typos in hostnames; internal hostnames not resolvable from public DNS (myapp.local, internal service names); sandboxed environments without DNS egress.
Understand the failure class
- DNS resolution errors: ENOTFOUND and getaddrinfo failures — how hostname lookups fail and how to debug them.
Related errors
- Could not resolve host "{parsed.hostname}"
- Refusing to fetch "${host}" — it resolves to a private, loop
- Refusing to fetch: too many redirects (max ${maxRedirects})
- Refusing to fetch a malformed or non-http(s) URL
- Refusing to fetch "{parsed.hostname}" because it resolves to
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/e2c6c100c3201b58.
Report an issue: GitHub.