All untrusted inputs must be hardened at their security boundaries: (1) for network calls, validate the destination on every redirect hop (and ideally after DNS resolution), and (2) for any rendered or embedded output, sanitize/encode for the exact output context to prevent XSS and script-breaking.
Apply this standard:
redirect is enabled.<script> block, escape </script> (and other context-breaking sequences) before interpolation.</script> and unquoted onerror payloads).Example pattern for safe redirects (sketch):
async function fetchWithValidatedRedirects(initialUrl, validateHop) {
let url = initialUrl;
for (let hops = 0; hops < 5; hops++) {
validateHop(url); // block private/loopback/reserved + enforce allowlist
const res = await fetch(url, { redirect: 'manual' });
if (res.status < 300 || res.status >= 400) return res;
const loc = res.headers.get('location');
if (!loc) return res;
url = new URL(loc, url).href;
}
throw new Error('Too many redirects');
}
And for JSON-in-script embedding, ensure you escape for script context (e.g., replace </script> with <\/script> before injecting).