Skip to main content
interlace
Plugin: browser-securityRules

require-url-validation

CWE-601

Quick Summary

AspectDetails
SeverityHigh (Phishing Risk)
Auto-Fix❌ No (requires allowlist logic)
CategorySecurity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForFrontend apps handling dynamic redirects
Suggestions✅ Advice on using hostname allowlists

Vulnerability and Risk

Vulnerability: An Open Redirect vulnerability occurs when an application accepts a user-controlled input that specifies a link to an external site and uses that link in a Redirection without validation.

Risk: This can be exploited to facilitate phishing attacks. Since the initial URL appears to be from a trusted domain, users are more likely to click it, only to be redirected to a malicious site that looks identical to the original.

Error Message Format

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

🔒 CWE-601 OWASP:M4 | URL Validation Required detected | HIGH [OpenRedirect,Phishing]
   Fix: Validate URLs before using them for navigation | https://cwe.mitre.org/data/definitions/601.html

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-601 OWASP:M4
Issue DescriptionSpecific vulnerabilityURL Validation Required detected
Severity & ComplianceImpact assessmentHIGH [OpenRedirect,Phishing]
Fix InstructionActionable remediationValidate URLs before using them for navigation
Technical TruthOfficial referenceOpen Redirect

Rule Details

This rule owns the navigations that do not write the current document's Location, and reports them when the URL is attacker-steerable and unvalidated:

  • window.open(x) (also self.open, globalThis.open) — opens x in a new browsing context;
  • router.push(x) / router.replace(x) on a router obtained from a routing package's useRouter() (next/navigation, next/router, vue-router, react-router, @tanstack/react-router). A push on anything else (queue.push(…)) is an array, not a navigation.

Attacker-steerable means read from the inbound URL — location.search / location.hash, new URLSearchParams(location.search).get('next'), a router's useSearchParams() — or from request input. A plain function parameter is not proven taint and is not reported. The guards that validate the destination are the ones no-insecure-redirects accepts: an anchored host regexp, a relative-path check that rejects //, an origin equality, or an allowlist / validator call over the URL.

Partition: every write to the current Location (window.location.href = x, location.href = x, window.location = x, location.assign/replace(x)) moved to no-insecure-redirects — the same defect used to be reported under two rule IDs depending on how the receiver was spelled. Linking.openURL and navigation.navigate belong to no-unvalidated-deeplinks.

Why This Matters

IssueImpactSolution
🎣 PhishingUsers tricked into giving credsUse strictly enforced allowlists for domains
🕵️ DetectionBypasses basic security filtersCheck protocol (https only) and hostname
🤝 TrustBrand integrity damageUse relative paths for redirects whenever possible

Configuration

This rule has no configuration options in the current version.

Examples

❌ Incorrect

// window.open with a URL taken from the query string
const params = new URLSearchParams(window.location.search);
window.open(params.get('next'), '_blank'); // ❌ HIGH RISK

// A framework router navigating to a query parameter
import { useRouter, useSearchParams } from 'next/navigation';
const router = useRouter();
const search = useSearchParams();
router.push(search.get('returnTo')); // ❌ HIGH RISK

// Reported by no-insecure-redirects, not this rule:
window.location.href = params.get('next');

✅ Correct

// Validating against a known allowlist
const ALLOWED_DOMAINS = ['example.com', 'docs.example.com'];

function safeNavigate(url) {
  const target = new URL(url, window.location.origin);
  if (ALLOWED_DOMAINS.includes(target.hostname)) {
    window.location.href = target.href;
  }
}

// Relative paths only, rejecting protocol-relative `//host`
const next = new URLSearchParams(location.search).get('next') ?? '/';
if (next.startsWith('/') && !next.startsWith('//')) {
  router.push(next);
}

// A plain parameter is not proven attacker-controlled (validate it anyway)
function openDocs(target) {
  window.open(target, '_blank');
}

Known False Negatives

The following patterns are not detected due to static analysis limitations:

String Concatenation

Why: If the URL is only partially user-controlled, it might not be flagged if the rule only looks for direct variable assignments.

window.location.href = 'https://example.com/login?next=' + userInput; // ❌ NOT DETECTED

Mitigation: Always sanitize and validate any user-provided string.

Object Properties

Why: If the URL is stored in an object property, the rule might miss it.

window.location.href = options.redirectUrl; // ❌ NOT DETECTED

Mitigation: Enforce strict validation at the source of the configuration.

References

Use this rule

npm install -D oxlint-plugin-browser-security
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-browser-security"],  "rules": {    "browser-security/require-url-validation": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-browser-security'],    rules: {      'browser-security/require-url-validation': '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.