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

  1. Validate and normalize user URLs at the API boundary before calling isURLLocal (try new URL(), then check hostname length)
  2. Return a 400 for unparseable/oversized URLs instead of letting the internal guard throw
  3. 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

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)