Web Browsers

A browser is a large, complex, network-facing codebase written in memory-unsafe languages, with features added constantly.

Browser Attack Surface

Memory Unsafety

A Microsoft study found roughly 70% of vulnerabilities fixed by security updates each year are memory safety issues. As of March 2019, all but about 5 of 130 public Critical Chrome bugs were memory corruption.

Examples:

  • One Chrome RCE overrides byteLength on an ArrayBuffer getter to 0xFFFFFFFC. The resulting Uint32Array reads and writes arbitrary memory.

The Rule of 2: for a given task, pick at most 2 of these 3.

  • Untrusted input
  • Unsafe language
  • No sandbox

If a task needs all 3, split the work.

Process Isolation

Multi-Process Architecture

Renderer processes have no direct disk, network, or device access. They run in a restricted sandbox, which limits damage from a renderer exploit. The split also gives parallelism across tabs and survives a single renderer crash.

Browser Site Isolation The browser process coordinates two renderer processes. Each renderer is permitted to access only one site's data through the browser process. Browser process network, storage, permission broker Renderer: bank.com bank DOM, cookies, heap allowed site data only Renderer: evil.com evil DOM, cookies, heap separate address space no direct memory access across sites
Site isolation gives each site a separate renderer address space. Cross-site requests go through the browser process, which enforces policy.

Limitations:

  • Same-origin iframes with document.domain can force cross-origin pages into one process.
  • Memory pressure exhausts process budgets.
  • Every renderer can request cross-origin images and scripts, so it effectively sees many cookies.

Auto-update shipped from the start, and it is now the delivery mechanism for the whole platform.

Renderer Design Tradeoffs

A single renderer process can host 1 thread per website. Uses less memory because threads share the address space, code, and runtime structures. Thread creation and context switches also cost less. The shared state weakens fault isolation. A crash, memory-corruption exploit, or resource exhaustion in 1 thread can affect every website in the renderer. A compromised thread can also attempt to access another website’s data through a renderer vulnerability or a speculative side channel.

A renderer process per website gives each website a separate virtual-memory boundary. Ordinary reads and writes cannot cross that boundary. A renderer crash normally destroys only 1 website’s state. The isolation costs more memory because every renderer needs its own address space, heap, stack, and runtime state. Cross-process messaging and process creation also cost more than equivalent thread operations.

Site Isolation

Site isolation puts content from one site in its own renderer process. The unit is a site, not a tab or an origin. The browser process can then restrict each renderer to its own site’s data.

It defends against Spectre-style speculative side channels from JavaScript, universal XSS (UXSS), and fully compromised renderers.

Goals:

  • Stop a renderer receiving cross-site cookies, stored data, saved passwords, or another site’s granted permissions.
  • Block cross-site pages in iframes per X-Frame-Options.
  • Keep cross-site DOM out of reach of UXSS bugs.
  • Contain speculative side channels, so JavaScript in a renderer cannot read another site’s memory.

Cross-Origin Read Blocking (CORB) blocks cross-site delivery of HTML, XML, and JSON responses. It works reliably when the response has a correct Content-Type and X-Content-Type-Options: nosniff. Without nosniff, Chrome sniffs the start of the file and protects it only if it can confirm the type, avoiding breaking mislabeled JavaScript files.

Sites Sharing a Process

Site isolation can fail under memory pressure or through document.domain, leaving pages from different sites in 1 renderer process. Every page in that process shares 1 address space, so the same-origin policy is enforced only by the browser’s software checks, not by process boundaries.

Attack scenario using Spectre:

  1. The user has a logged-in tab of bank.com. Its DOM, cookies, and tokens sit in the renderer’s memory.
  2. The user opens evil.com, and the browser places it in the same renderer process.
  3. JavaScript on evil.com runs a Spectre variant 1 gadget. It trains a bounds check, then passes an out-of-bounds index that points into bank.com data.
  4. The speculative read leaves a trace in the cache. The script times reads of a probe array to recover 1 byte.
  5. Repeating this leaks session tokens or account details from bank.com, without any bug in the same-origin policy.

The same layout also lets a renderer memory corruption exploit or a UXSS bug reach bank.com data directly.

Secure JavaScript

Secure Coding in JavaScript

Use strict mode and a linter such as ESLint or StandardJS.

  • Abstract equality
    == treats 0, '0', and false as all mutually equal. Use === instead.
  • Duplicate declarations
    Duplicate function parameters and duplicate object keys are allowed by default. Strict mode or a linter catches them.
  • Prototype builtins
    Accessing Object.prototype builtins directly breaks when an object has its own hasOwnProperty. Use Object.hasOwn instead.
  • Automatic semicolon insertion
    Changes meaning across line breaks. A leading ; before ( or [ disambiguates.
  • Octal literals
    A leading zero makes a number octal, so 010 is 8. Strict mode rejects it.
  • Implicit globals
    Assignment without var, let, or const creates a global. Strict mode makes it a ReferenceError.
  • Silent failures
    Assigning to non-writable globals or setting properties on primitives fails silently without strict mode.

Style and Testing Principles

Source code is for humans, so readability is preferred over brevity, and clever one-liners that lean on obscure language features are avoided. Dangerous code should look dangerous so it stands out in review. Untested code is broken code, so tests are automated in CI. Explicit code is safer than magical code.

Written by October 30, 2026 5 min read
Was this helpful?