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
byteLengthon anArrayBuffergetter to0xFFFFFFFC. The resultingUint32Arrayreads 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.
Limitations:
- Same-origin iframes with
document.domaincan 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:
- The user has a logged-in tab of
bank.com. Its DOM, cookies, and tokens sit in the renderer’s memory. - The user opens
evil.com, and the browser places it in the same renderer process. - JavaScript on
evil.comruns a Spectre variant 1 gadget. It trains a bounds check, then passes an out-of-bounds index that points intobank.comdata. - The speculative read leaves a trace in the cache. The script times reads of a probe array to recover 1 byte.
- 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
==treats0,'0', andfalseas 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
AccessingObject.prototypebuiltins directly breaks when an object has its ownhasOwnProperty. UseObject.hasOwninstead. - Automatic semicolon insertion
Changes meaning across line breaks. A leading;before(or[disambiguates. - Octal literals
A leading zero makes a number octal, so010is 8. Strict mode rejects it. - Implicit globals
Assignment withoutvar,let, orconstcreates 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.