Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/__tests__/network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ describe('validateUrl', () => {
expect(validateUrl('http://127.0.0.1:8000', { allowInsecureLocalhost: true }).hostname).toBe(
'127.0.0.1',
);
// Full 127.0.0.0/8 range — not just .1
expect(validateUrl('http://127.0.0.2:8000', { allowInsecureLocalhost: true }).hostname).toBe(
'127.0.0.2',
);
// IPv6 loopback — WHATWG URL parser always returns the bracketed form
expect(validateUrl('http://[::1]:8000', { allowInsecureLocalhost: true }).hostname).toBe(
'[::1]',
);
});

it('rejects http on loopback addresses unless opted in', () => {
expect(() => validateUrl('http://127.0.0.2:8000')).toThrow(VeroError);
expect(() => validateUrl('http://[::1]:8000')).toThrow(VeroError);
});

it('does not permit http on a remote host even when localhost is opted in', () => {
Expand Down
17 changes: 15 additions & 2 deletions src/network/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,20 @@ export const MAINNET: NetworkConfig = {
network: 'mainnet',
};

const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
/**
* Returns true for hostnames that are loopback addresses:
* - "localhost"
* - IPv4 127.0.0.0/8 (e.g. 127.0.0.1, 127.0.0.2, …)
* - IPv6 [::1] (as returned by the WHATWG URL parser — always bracketed)
*
* Note: the WHATWG URL parser always brackets IPv6 addresses, so bare "::1"
* can never appear in `parsed.hostname` and is intentionally excluded here.
*/
function isLoopback(hostname: string): boolean {
if (hostname === 'localhost' || hostname === '[::1]') return true;
// Match 127.x.x.x (the full 127.0.0.0/8 loopback range)
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
}

export interface ValidateUrlOptions {
/**
Expand Down Expand Up @@ -69,7 +82,7 @@ export function validateUrl(url: string, opts: ValidateUrlOptions = {}): URL {
if (parsed.protocol === 'https:') return parsed;

if (parsed.protocol === 'http:') {
const isLocal = LOCAL_HOSTS.has(parsed.hostname);
const isLocal = isLoopback(parsed.hostname);
if (isLocal && opts.allowInsecureLocalhost) return parsed;

throw new VeroError(
Expand Down
Loading