no-insecure-redirects
Detects open redirects: a navigation or HTTP redirect whose destination an attacker can choose.
CWE: CWE-601
OWASP: Unvalidated Redirects and Forwards
Detects open redirects: a navigation or HTTP redirect whose destination an attacker can choose. This rule is part of eslint-plugin-browser-security.
Quick Summary
| Aspect | Details |
|---|---|
| CWE Reference | CWE-601 (Open Redirect) |
| Severity | Medium (CVSS 6.1) |
| Auto-Fix | ❌ No |
| Category | Security |
| Best For | Web applications with redirection logic |
Vulnerability and Risk
Vulnerability: An open redirect sends the user to a URL taken from untrusted input — the query string, the fragment, a request parameter — without proving where it points.
Risk: The link starts on your domain, so users and mail filters trust it, and it ends on a phishing or malware page the attacker chose. Redirects are also how OAuth tokens and session-bound links get exfiltrated.
Rule Details
The rule reports when an attacker-steerable value reaches a sink that moves the user, and no guard in front of it proves the destination.
Sinks this rule owns (by the navigation API that receives the URL, never by how the receiver is spelled):
| Sink | Example |
|---|---|
Any write to the current document's Location or its .href | window.location.href = x, top.location = x, window['location'].href = x, a bare global location = x |
location.assign(x) / location.replace(x) | location.assign(next) |
.redirect(x) | res.redirect(x), res['redirect'](x) |
Attacker-steerable values: the inbound URL (location.search, location.hash, location.href, document.URL), values parsed out of it (new URLSearchParams(location.search).get('next'), new URL(location.href).searchParams), router hooks that return the query (useSearchParams() from Next.js / React Router), and request input (req.query.next, req.params, req.body.url). A TypeScript assertion (get('next')!, get('next') as string) narrows a type and validates nothing, so the value stays steerable. In a template URL the value decides the destination when it leads the URL (`${next}/x`) or lands in its host (`https://${host}/home`, `//${host}`).
Guards that prove the destination (all read from the AST): an anchored, dot-escaped host regexp (/^https:\/\/app\.example\.com\//.test(next)), a relative-path check that also rejects // (next.startsWith('/') && !next.startsWith('//')), and an equality against a parsed origin (new URL(next, location.origin).origin === location.origin). A prefix check such as next.startsWith('https://trusted.com') is not a guard: https://trusted.com.evil.test passes it.
Navigating to the page you are already on (location.assign(location.href)) is a reload, not a redirect, and is not reported.
Rule partition
| Sink | Owner |
|---|---|
a Location write, location.assign/replace, .redirect(x) | no-insecure-redirects |
window.open(x), router.push/replace(x) on a resolved router | require-url-validation |
Linking.openURL(x), navigation.navigate(x) | no-unvalidated-deeplinks |
| a credential inside the URL string | no-password-in-url (complementary, not partitioned) |
Examples
❌ Incorrect
const next = new URLSearchParams(location.search).get('next');
window.location.href = next;
// A non-null assertion is not validation
window.location.href = new URLSearchParams(location.search).get('next')!;
// The bare global is the same Location
location = new URLSearchParams(location.search).get('next');
// The attacker picks the host
window.location.href = `https://${new URLSearchParams(location.search).get('host')}/home`;
app.get('/login', (req, res) => {
res.redirect(req.query.returnTo);
});✅ Correct
const next = new URLSearchParams(location.search).get('next') ?? '/';
// Same-origin relative paths only (rejects `//evil.test`)
location.assign(next.startsWith('/') && !next.startsWith('//') ? next : '/');
// Parsed-origin equality
const target = new URL(next, location.origin);
if (target.origin === location.origin) window.location.href = target.href;
// A fixed host with a steerable path stays on that host
window.location.href = `https://app.example.com/${encodeURIComponent(page)}`;
res.redirect('/dashboard');Configuration
// eslint.config.mjs
export default [
{
rules: {
'browser-security/no-insecure-redirects': 'error',
},
},
];Options
| Option | Type | Default | Description |
|---|---|---|---|
ignoreInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
allowedDomains | string[] | [] | Redirect target domains treated as safe |
Related Rules
require-url-validation-window.openand framework router navigationsno-unvalidated-deeplinks- React Native deep linksno-password-in-url- credentials inside a URL
Known False Negatives
A Location reached through a local alias
Why: Proving that w in w.location.assign(x) is a Window needs type information, and matching any .location by name is the name inference this rule refuses.
// ❌ NOT DETECTED
const w = window;
w.location.assign(new URLSearchParams(location.search).get('next'));Mitigation: Navigate through window / location directly.
Values passed through opaque functions
Why: An unknown function may validate its argument; only a local helper the rule can read to be the identity passes taint through.
// ❌ NOT DETECTED
location.assign(
normalizeExternally(new URLSearchParams(location.search).get('next')),
);Mitigation: Keep the validation visible at the redirect.
Further Reading
- OWASP Unvalidated Redirects Cheat Sheet - Protection guide
- CWE-601: URL Redirection to Untrusted Site - Official CWE entry
Use this rule
npm install -D oxlint-plugin-browser-security{ "jsPlugins": ["oxlint-plugin-browser-security"], "rules": { "browser-security/no-insecure-redirects": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-browser-security'], rules: { 'browser-security/no-insecure-redirects': 'error', }, },});oxlint-plugin-browser-security re-exports eslint-plugin-browser-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.