Browser Sniffing via Navigator

ID

javascript.navigator_browser_detection

Severity

low

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

JavaScript

Tags

best-practice, browser

Description

Reports reads of navigator.userAgent, navigator.appVersion, navigator.platform or navigator.vendor. All four are historic browser-sniffing entry points: their values can be spoofed by users, lied about by vendors, and they encode hard-coded assumptions about specific browsers that break new releases and devices.

Only the property access itself is reported; downstream substring or regex checks against the value are out of scope. The intent is to surface the access so that it can be replaced with capability-based feature detection.

Rationale

// Bad — implementations differ, user agents lie, regexes rot.
function isMobile() {
  return /Mobi|Android/i.test(navigator.userAgent);
}

function isChrome() {
  return navigator.vendor.indexOf("Google") !== -1;
}

Feature detection asks the runtime "do you support what I need?" instead of "are you a browser I know?". The first question is forward-compatible; the second one ages badly.

Remediation

Replace the sniff with a capability check. A few common substitutions:

// Touch input / pointer behaviour
const isTouchPrimary = matchMedia("(pointer: coarse)").matches;

// Geolocation availability
const hasGeolocation = "geolocation" in navigator;

// Service worker support
const canCacheOffline = "serviceWorker" in navigator;

// CSS feature
const canGap = CSS.supports("gap: 1rem");

When you must keep a navigator-driven branch (for example, to work around a documented bug in one specific engine), isolate it behind a single, well-named helper and add a comment explaining the bug and the bug-tracker link.