ruvnet/ruflo · warning
Invalid hostname
Error message
Invalid hostname
What it means
isURLLocal() performs an SSRF check on URLs before the server fetches them: non-IP hostnames go through assertValidHostname (isURLLocal.ts:16). The first rule rejects empty hostnames and hostnames longer than 253 characters (the RFC 1035 limit) before any DNS lookup, throwing "Invalid hostname".
Solutions
- Validate and normalize user URLs at the API boundary before calling isURLLocal (try new URL(), then check hostname length)
- Return a 400 for unparseable/oversized URLs instead of letting the internal guard throw
- Trim/limit hostname length (<253) in the same pre-check
Example fix
// before
const isLocal = await isURLLocal(new URL(userUrl)); // throws: Invalid hostname for "http:///x"
// after
let parsed: URL;
try {
parsed = new URL(userUrl);
} catch {
throw error(400, "Invalid URL");
}
if (!parsed.hostname || parsed.hostname.length > 253) {
throw error(400, "Invalid hostname");
}
const isLocal = await isURLLocal(parsed); Defensive patterns
Strategy: validation
Validate before calling
let u: URL;
try {
u = new URL(userUrl);
} catch {
throw error(400, "Invalid URL");
}
if (!u.hostname || u.hostname.length > 253) throw error(400, "Invalid hostname");
const isLocal = await isURLLocal(u); Type guard
function hasPlausibleHostname(u: URL): boolean {
return u.hostname.length > 0 && u.hostname.length <= 253;
} Try / catch
try {
await isURLLocal(u);
} catch (e) {
if (e instanceof Error && e.message === "Invalid hostname") throw error(400, "Bad URL");
throw e;
} Prevention
- Validate URL shape at the API boundary (parse + hostname length) before any fetch-url handling
- Rate-limit and size-cap user-supplied URL fields so 253+ char hostnames never reach the guard
- Treat guard throws as 400s, not 500s, in route error handlers
When it happens
Trigger: Calling isURLLocal(new URL(u)) with a URL whose hostname is empty ("http:///path", "http://:8080/x") or a DNS name exceeding 253 chars (deeply nested junk subdomains) — typical of user-supplied links passed to the fetch-url/link-preview endpoints.
Common situations: Malformed user input reaching the server unvalidated; test fixtures with degenerate URLs; abuse probes sending oversized hostnames to scan the fetch endpoint.
Related errors
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/f9129b3a3a205266.
Report an issue: GitHub.
Appendix: source
Thrown at ruflo/src/ruvocal/src/lib/server/isURLLocal.ts:16
import { Address6, Address4 } from "ip-address";
import dns from "node:dns";
import { isIP } from "node:net";
const dnsLookup = (hostname: string): Promise<{ address: string; family: number }> => {
return new Promise((resolve, reject) => {
dns.lookup(hostname, (err, address, family) => {
if (err) return reject(err);
resolve({ address, family });
});
});
};
function assertValidHostname(hostname: string): void {
if (!hostname || hostname.length > 253) {
throw new Error("Invalid hostname");
}
const labels = hostname.split(".");
for (const label of labels) {
if (!label || label.length > 63) {
throw new Error("Invalid hostname");
}
if (!/^[A-Za-z0-9-]+$/.test(label)) {
throw new Error("Invalid hostname");
}
if (label.startsWith("-") || label.endsWith("-")) {
throw new Error("Invalid hostname");
}
}
}View on GitHub (pinned to fa13ee4ad6)