A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 2.7 kB View raw
1/** 2 * SSRF-guarded fetch for the read-through renderer. 3 * 4 * The renderer fetches hostnames derived from UNTRUSTED input — the `@handle` in the 5 * URL, a `did:web` host, and the PDS `serviceEndpoint` from a DID document. Without 6 * guarding, a request like `/@169.254.169.254/x` or a `did:web` pointing at an internal 7 * host would make the server fetch loopback / cloud-metadata / private addresses (SSRF). 8 * 9 * `assertSafeUrl` allows only `https://` to syntactically-valid **public** domains — 10 * rejecting IP literals, single-label/`localhost`, and reserved/internal TLDs — and 11 * `safeFetch` additionally refuses to follow redirects (which could pivot to an internal 12 * host). 13 * 14 * Residual risk: a public domain whose DNS resolves to a private IP (DNS rebinding). 15 * That can't be closed portably without resolving DNS and inspecting the address; it is 16 * best handled at the network/egress layer of the deploy host (tracked for SP7). 17 */ 18 19const RESERVED_TLDS = new Set( [ 20 'localhost', 'local', 'internal', 'intranet', 'lan', 'home', 'corp', 21 'test', 'example', 'invalid', 'arpa', 'onion', 'localdomain', 22] ); 23 24/** True only for a syntactically valid, public, non-IP domain name. */ 25export function isPublicHostname( hostname: string ): boolean { 26 if ( ! hostname ) { 27 return false; 28 } 29 const host = hostname.toLowerCase().replace( /\.$/, '' ); 30 if ( host.includes( ':' ) || host.includes( '[' ) ) { 31 return false; // IPv6 literal or embedded port 32 } 33 if ( /^\d{1,3}(\.\d{1,3}){3}$/.test( host ) ) { 34 return false; // IPv4 literal 35 } 36 const labels = host.split( '.' ); 37 if ( labels.length < 2 ) { 38 return false; // single-label (localhost, etc.) 39 } 40 for ( const label of labels ) { 41 if ( ! /^[a-z0-9-]{1,63}$/.test( label ) || label.startsWith( '-' ) || label.endsWith( '-' ) ) { 42 return false; 43 } 44 } 45 const tld = labels[ labels.length - 1 ]; 46 if ( /^\d+$/.test( tld ) || RESERVED_TLDS.has( tld ) ) { 47 return false; 48 } 49 return true; 50} 51 52/** Parse + validate a URL for outbound fetching; throws if it isn't a safe public https target. */ 53export function assertSafeUrl( rawUrl: string ): URL { 54 let url: URL; 55 try { 56 url = new URL( rawUrl ); 57 } catch { 58 throw new Error( `Invalid URL: ${ rawUrl }` ); 59 } 60 if ( url.protocol !== 'https:' ) { 61 throw new Error( `Refusing non-https URL (${ url.protocol })` ); 62 } 63 if ( ! isPublicHostname( url.hostname ) ) { 64 throw new Error( `Refusing non-public host: ${ url.hostname }` ); 65 } 66 return url; 67} 68 69/** `fetch` restricted to safe public https targets, without following redirects. */ 70export function safeFetch( rawUrl: string, init: RequestInit = {} ): Promise< Response > { 71 const url = assertSafeUrl( rawUrl ); 72 return fetch( url, { ...init, redirect: 'manual' } ); 73}