ESLint InterlaceESLint Interlace
Plugin: browser-securityRules

no-unvalidated-deeplinks

ESLint rule documentation for no-unvalidated-deeplinks

📡 Live from GitHub — This documentation is fetched directly from no-unvalidated-deeplinks.md and cached for 6 hours.

Requires validation of deep link URLs before navigation

This rule detects when deep link URLs are opened without validation in React Native or mobile web apps

Severity: 🟠 HIGH
CWE: CWE-939: Improper Authorization in Handler for Custom URL Scheme
OWASP Mobile: M4: Insufficient Input/Output Validation

Rule Details

This rule detects when deep link URLs are opened without validation in React Native or mobile web apps. Unvalidated deep links enable phishing attacks, unauthorized actions, and open redirect vulnerabilities. The rule flags Linking.openURL() and navigation.navigate() calls with variable/expression arguments instead of literal strings.

Why This Matters

Deep links allow external apps/websites to trigger actions in your app:

  • Phishing: Attacker crafts malicious deep link to trick users
  • Open redirects: Users redirected to malicious sites
  • Unauthorized actions: Deep links bypass normal auth flows
  • CSRF: Cross-site request forgery via deep links

❌ Incorrect

// React Native - opening URL from variable without validation
import { Linking } from 'react-native';

function handleDeepLink(url: string) {
  Linking.openURL(url); // ❌ Unvalidated URL from external source
}

// Navigation with user-controlled URL
function navigate(destination: string) {
  navigation.navigate(destination); // ❌ No whitelist check
}

// Deep link handler without validation
Linking.addEventListener('url', (event) => {
  const { url } = event;
  Linking.openURL(url); // ❌ Directly opening deep link
});

// Opening URL from props
function ExternalLink({ href }: { href: string }) {
  return (
    <TouchableOpacity onPress={() => Linking.openURL(href)}>
      {/* ❌ No validation of href prop */}
    </TouchableOpacity>
  );
}

✅ Correct

// Validate against whitelist before opening
import { Linking } from 'react-native';

const ALLOWED_SCHEMES = ['myapp:', 'https://myapp.com'];

function handleDeepLink(url: string) {
  const isValid = ALLOWED_SCHEMES.some(scheme => url.startsWith(scheme));

  if (!isValid) {
    console.warn('Blocked unauthorized deep link:', url);
    return;
  }

  Linking.openURL(url); // ✅ Validated against whitelist
}

// Parse and validate deep link parameters
function handleDeepLink Validated(url: string) {
  try {
    const parsed = new URL(url);

    // Whitelist allowed hosts
    const allowedHosts = ['myapp.com', 'api.myapp.com'];
    if (!allowedHosts.includes(parsed.hostname)) {
      throw new Error('Host not allowed');
    }

    // Validate path
    const allowedPaths = ['/profile', '/settings', '/share'];
    if (!allowedPaths.includes(parsed.pathname)) {
      throw new Error('Path not allowed');
    }

    Linking.openURL(url); // ✅ Fully validated
  } catch (error) {
    console.error('Invalid deep link:', error);
  }
}

// Safe navigation with route whitelist
const ALLOWED_ROUTES = ['Home', 'Profile', 'Settings'] as const;
type AllowedRoute = typeof ALLOWED_ROUTES[number];

function navigateSafe(route: string) {
  if (!ALLOWED_ROUTES.includes(route as AllowedRoute)) {
    console.warn('Blocked navigation to:', route);
    return;
  }

  navigation.navigate(route); // ✅ Whitelist validated
}

⚙️ Configuration

This rule has no configuration options.

Known False Negatives

Literal URL Strings

Why: We only flag variable/expression arguments. Literal strings are assumed safe (but still review manually).

// ❌ NOT DETECTED - Literal string
Linking.openURL('https://evil.com'); // Literal, but still dangerous if hardcoded malicious URL

Mitigation: Code review all openURL() calls. Prefer whitelisted literals only.

Why: Validation in separate functions is not traced.

// ❌ NOT DETECTED - Validation in separate function
function validate(url: string): boolean {
  return url.startsWith('myapp:');
}

function handleLink(url: string) {
  if (validate(url)) {
    // Validation exists, but not detected statically
    Linking.openURL(url);
  }
}

Mitigation: Keep validation inline with openURL() call for static analysis.

Why: We only detect Linking.openURL() and navigation.navigate(). Custom libraries not analyzed.

// ❌ NOT DETECTED - Custom library
import { openExternalURL } from './customLinking';
openExternalURL(userProvidedUrl); // Not detected

Mitigation: Apply validation pattern to all URL opening mechanisms.

📚 References

On this page

No Headings