Skip to content
Athenian Tech

Vulnerability Management Threat Hunting

Front-End Security: Where the Browser Becomes the Battleground

6 min read1,292 words

There is a comfortable myth in software teams that security is something the back end handles. The API validates, the database enforces constraints, the WAF filters the ugly stuff, so the front end can just render buttons and trust that grown-ups are watching the door. That assumption is wrong in a specific and dangerous way: the front end is the one part of your application that runs on a machine you do not control, inside a browser you did not configure, exposed to code you did not write. Every line of JavaScript you ship is source code you have handed directly to your attacker. Once you internalize that, front-end security stops looking optional.

The catch is that the browser is also a shared execution environment. Your code, third-party analytics, an ad SDK, a chat widget, and potentially an attacker’s injected payload all run in the same context with access to the same DOM, the same cookies, and the same logged-in session. Front-end security is largely the discipline of keeping those tenants from stealing from each other.

The Attacks That Actually Land

A handful of attack classes account for the overwhelming majority of real front-end incidents. They are worth understanding concretely rather than as acronyms.

  • Cross-site scripting (XSS) is the flagship. It happens whenever untrusted data ends up executed as code in a victim’s browser. If a comment field renders <img src=x onerror=alert(1)> verbatim, you have stored XSS. If a search page echoes ?q=<script>... into the HTML, that is reflected XSS. And DOM-based XSS never touches the server at all — it lives entirely in client-side code that reads from location.hash and writes to innerHTML.
  • Cross-site request forgery (CSRF) abuses the fact that browsers attach cookies automatically. A malicious page can trigger a state-changing request to your site, and the victim’s session rides along.
  • Clickjacking frames your real UI invisibly and tricks users into clicking things they cannot see.
  • Third-party and supply-chain compromise is the modern favorite. You do not need to be breached directly; an attacker who compromises a script you load (an npm dependency, a CDN, a tag manager) inherits your entire page. Magecart-style card skimmers work exactly this way.
  • Sensitive data exposure is the quiet one: API keys, tokens, internal endpoints, and business logic sitting in a JavaScript bundle that anyone can pretty-print.

The reason XSS deserves the top spot is that it collapses every other boundary. An attacker running JavaScript in your origin can read the DOM, exfiltrate tokens from localStorage, forge requests that pass CSRF checks, and rewrite the page. It is remote code execution against your users.

Why “Escape Everything” Is Still the Core Defense

Almost all injection defense reduces to one rule: never let data cross into a code context without being neutralized for that context. Modern frameworks help enormously here because they escape by default. React’s {userInput} is safe; the danger is when you reach around the framework.

JSX
// Safe: React escapes this automatically
<div>{comment.body}</div>

// Dangerous: you have explicitly opted out of escaping
<div dangerouslySetInnerHTML={{ __html: comment.body }} />

If you genuinely must render user-supplied HTML (rich text, for example), sanitize it with a vetted library rather than a regex you invented at 2 a.m.

JavaScript
import DOMPurify from 'dompurify';

// Strips <script>, onerror=, javascript: URLs, and other executable vectors
const clean = DOMPurify.sanitize(dirtyHtml);
element.innerHTML = clean;

The equivalent discipline on the raw DOM is to prefer textContent over innerHTML whenever you are inserting text, because textContent cannot execute markup.

Let the Browser Enforce Your Rules with Headers

Some of the highest-leverage front-end controls are not code at all — they are HTTP response headers that instruct the browser to constrain itself. A Content Security Policy (CSP) is the single most valuable one. It tells the browser which sources of script, style, and other resources are allowed, so that even if an attacker injects a <script>, the browser refuses to run it.

HTTP
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none'

Each directive does specific work. script-src 'self' 'nonce-...' allows only your own scripts plus inline scripts carrying a server-generated nonce, which defeats most injected inline payloads. object-src 'none' kills legacy plugin vectors. frame-ancestors 'none' is the modern, robust replacement for the old X-Frame-Options header and stops clickjacking by refusing to be framed at all. base-uri 'self' blocks an attacker from hijacking relative URLs.

A few other headers round out a solid baseline:

HTTP
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

HSTS forces HTTPS and prevents downgrade attacks; nosniff stops the browser from guessing content types and executing a “text” file as script; a tight referrer policy avoids leaking full URLs (and any tokens in them) to other sites.

Cookies, Tokens, and Where You Store Secrets

Session handling is a front-end decision even when the server issues the credential. Cookies carrying sessions should be marked defensively:

HTTP
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/

HttpOnly makes the cookie unreadable to JavaScript, so XSS cannot steal it directly. Secure restricts it to HTTPS. SameSite=Lax (or Strict) means the browser will not send it on most cross-site requests, which neutralizes the bulk of CSRF without any token machinery.

This is also why storing authentication tokens in localStorage is a recurring mistake. localStorage is plain JavaScript-readable memory; any XSS on your page can scrape it in one line. An HttpOnly cookie survives that same XSS. If you must use bearer tokens in JS, keep their lifetime short and pair them with server-side revocation.

Guarding the Code You Did Not Write

Because so much front-end risk now comes from dependencies, treat your supply chain as part of the attack surface. Subresource Integrity (SRI) lets you pin the exact hash of an externally hosted script; if a CDN serves anything different, the browser refuses to run it.

HTML
<script src="https://cdn.example.com/lib.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous"></script>

Beyond that, audit dependencies routinely and lock versions:

Shell
npm audit --production        # flag known-vulnerable packages
npm ci                        # install exactly what the lockfile pins

Use the Permissions-Policy header to switch off browser features (camera, geolocation, USB) you do not use, shrinking what a compromised script can reach.

The Honest Limit: The Front End Is Only Half the Wall

Here is the part teams skip. Everything above hardens the client, but the client is fundamentally untrusted, and no amount of front-end validation is a security boundary. An attacker does not have to use your UI. They can hit your API directly with curl, disable your JavaScript, edit the DOM, or replay requests. Client-side validation is a usability feature; the authoritative check must live on the server.

Shell
# Your form's client-side length check means nothing here
curl -X POST https://api.example.com/transfer \
  -H "Content-Type: application/json" \
  -d '{"amount": -100000, "to": "attacker"}'

So front-end security is real and essential, but it protects your users far more than it protects your data. Authorization, input validation, rate limiting, and business-logic enforcement all have to be duplicated server-side, because the browser is a place you visit, not a place you own.

Takeaway

The practical program is not complicated: escape output by context, sanitize any HTML you must render, ship a strict Content Security Policy, set defensive cookie flags and security headers, keep tokens out of localStorage, and treat every third-party script as a potential intruder with SRI and dependency audits. Do those consistently and you eliminate most of the front-end attacks people actually suffer. Just remember what layer you are working in — the front end is the surface your users touch, and hardening it is about protecting the humans on the other side of the screen while the server keeps the last, non-negotiable line of defense.