mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-07 23:08:36 +02:00
fix(browse): block direct IPv6 link-local navigation
URL validation centralises link-local (fe80::/10) into BLOCKED_IPV6_PREFIXES alongside ULA (fc00::/7), so direct `http://[fe80::N]/` URLs are rejected the same way `http://[fc00::]/` already was. Previously the link-local guard only fired during DNS AAAA resolution, leaving direct-literal URLs to slip through. Prefix range covers fe80::-febf::: ['fe8','fe9','fea','feb']. Regression test: validateNavigationUrl('http://[fe80::2]/') now throws with /cloud metadata/i. Contributed by @hiSandog (#1249). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
38383f4b06
commit
6896f8b429
@@ -19,14 +19,15 @@ export const BLOCKED_METADATA_HOSTS = new Set([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPv6 prefixes to block (CIDR-style). Any address starting with these
|
* IPv6 prefixes to block (CIDR-style). ULA addresses cover fc00::/7 and
|
||||||
* hex prefixes is rejected. Covers the full ULA range (fc00::/7 = fc00:: and fd00::).
|
* link-local addresses cover fe80::/10.
|
||||||
*/
|
*/
|
||||||
const BLOCKED_IPV6_PREFIXES = ['fc', 'fd'];
|
const BLOCKED_IPV6_PREFIXES = ['fc', 'fd', 'fe8', 'fe9', 'fea', 'feb'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an IPv6 address falls within a blocked prefix range.
|
* Check if an IPv6 address falls within a blocked prefix range.
|
||||||
* Handles the full ULA range (fc00::/7), not just the exact literal fd00::.
|
* Handles the full ULA range (fc00::/7) and link-local range (fe80::/10),
|
||||||
|
* not just exact literals like fd00:: or fe80::1.
|
||||||
* Only matches actual IPv6 addresses (must contain ':'), not hostnames
|
* Only matches actual IPv6 addresses (must contain ':'), not hostnames
|
||||||
* like fd.example.com or fcustomer.com.
|
* like fd.example.com or fcustomer.com.
|
||||||
*/
|
*/
|
||||||
@@ -95,9 +96,7 @@ async function resolvesToBlockedIp(hostname: string): Promise<boolean> {
|
|||||||
const v6Check = resolve6(hostname).then(
|
const v6Check = resolve6(hostname).then(
|
||||||
(addresses) => addresses.some(addr => {
|
(addresses) => addresses.some(addr => {
|
||||||
const normalized = addr.toLowerCase();
|
const normalized = addr.toLowerCase();
|
||||||
return BLOCKED_METADATA_HOSTS.has(normalized) || isBlockedIpv6(normalized) ||
|
return BLOCKED_METADATA_HOSTS.has(normalized) || isBlockedIpv6(normalized);
|
||||||
// fe80::/10 is link-local — always block (covers all fe80:: addresses)
|
|
||||||
normalized.startsWith('fe80:');
|
|
||||||
}),
|
}),
|
||||||
() => false, // ENODATA / ENOTFOUND — no AAAA records, not a risk
|
() => false, // ENODATA / ENOTFOUND — no AAAA records, not a risk
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ describe('validateNavigationUrl', () => {
|
|||||||
await expect(validateNavigationUrl('http://[fc00::]/')).rejects.toThrow(/cloud metadata/i);
|
await expect(validateNavigationUrl('http://[fc00::]/')).rejects.toThrow(/cloud metadata/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('blocks direct IPv6 link-local addresses', async () => {
|
||||||
|
await expect(validateNavigationUrl('http://[fe80::2]/')).rejects.toThrow(/cloud metadata/i);
|
||||||
|
});
|
||||||
|
|
||||||
it('does not block hostnames starting with fd (e.g. fd.example.com)', async () => {
|
it('does not block hostnames starting with fd (e.g. fd.example.com)', async () => {
|
||||||
await expect(validateNavigationUrl('https://fd.example.com/')).resolves.toBe('https://fd.example.com/');
|
await expect(validateNavigationUrl('https://fd.example.com/')).resolves.toBe('https://fd.example.com/');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user