Skip to main content

Security for Frontend Engineers

(Reducing exploitability in the browser)

Senior rule:

The frontend is not the authority, but it is still part of the attack surface.

Atlas contains account data, revenue reports, roles, saved views, and analytics scripts. A senior frontend engineer must understand how browser code can expose data or widen blast radius.

Conceptual Model

Frontend security focuses on:

  • untrusted input
  • unsafe rendering
  • session exposure
  • cross-site requests
  • third-party scripts
  • dependency and supply-chain risk
  • stale permission assumptions

Server authorization is the source of truth. Frontend security reduces exposure and preserves trust.

XSS

XSS happens when untrusted data becomes executable script or trusted markup.

Dangerous pattern:

export function UnsafeComment({ html }: { html: string }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

Safer default:

export function Comment({ text }: { text: string }) {
return <p>{text}</p>;
}

If HTML is required, sanitize it with a reviewed library, restrict allowed tags, and pair it with CSP.

Watch for:

  • Markdown rendering
  • rich text editors
  • third-party embeds
  • user-controlled URLs
  • HTML strings in translations
  • dangerous DOM APIs

Content Security Policy

CSP limits what the browser is allowed to execute or load.

export const securityHeaders = [
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' https://analytics.example.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
].join("; "),
},
];

Treat CSP as architecture: it defines allowed runtime behavior. Start with reporting if the app is not ready for enforcement.

SRI for External Resources

Subresource Integrity lets the browser verify that an external static resource matches an expected hash.

<script
src="https://cdn.example.com/chart-lib.1.2.3.js"
integrity="sha384-BASE64_HASH"
crossorigin="anonymous"
></script>

Use SRI for versioned CDN scripts and styles where feasible. Do not use mutable CDN URLs for critical code.

CSRF and Cookies

CSRF matters when browsers automatically attach credentials to state-changing requests.

Senior frontend engineers should understand:

  • HttpOnly protects cookies from JavaScript access
  • Secure requires HTTPS
  • SameSite=Lax or SameSite=Strict reduces cross-site sending
  • anti-CSRF tokens may still be needed depending on flow
  • GET should not mutate state
type SessionCookiePolicy = {
httpOnly: true;
secure: true;
sameSite: "lax" | "strict";
path: "/";
};

AuthN, AuthZ, and RBAC

The UI can hide controls, but the server must enforce permissions.

type Permission = "reports:view" | "reports:export" | "users:invite";

export function ExportButton({
permissions,
}: {
permissions: Permission[];
}) {
if (!permissions.includes("reports:export")) return null;
return <button>Export report</button>;
}

This improves UX, not security. The export endpoint must still authorize the request.

Design for:

  • permission refresh
  • stale role changes
  • SSO callback and session bootstrap states
  • session expiry
  • logout across tabs
  • no tokens in local logs, URLs, or analytics

SSO and Session Bootstrap

Single Sign-On often creates frontend edge cases:

  • callback routes may briefly have no complete session
  • role and profile data may arrive after identity is known
  • users may return to a deep link after login
  • session refresh can fail while a form is dirty
  • logout needs to clear local UI state across tabs
type SessionState =
| { status: "checking" }
| { status: "authenticated"; userId: string; roles: string[] }
| { status: "unauthenticated"; returnTo: string };

Do not treat SSO as a button. Treat it as a state machine around identity, routing, permissions, and recovery.

Third-Party Scripts

Every third-party script can affect:

  • execution
  • privacy
  • performance
  • data exposure
  • CSP policy
type ScriptThreatReview = {
vendor: string;
dataAccessed: string[];
routes: string[];
cspSource: string;
consentRequired: boolean;
owner: string;
removalPlan: string;
};

If nobody owns the script, the architecture owns the risk badly.

Mini Case Study: Export Button Leak

Atlas hid the export button for viewers, but the API accepted the request if a user knew the URL.

Fix:

  • enforce authorization server-side
  • refresh permissions after role changes
  • add UI tests for hidden controls
  • add API contract tests for forbidden export
  • log denied access attempts

The frontend mistake was assuming visibility equals authority.

Common Failure Modes

  • Trusting client-side RBAC.
  • Treating SSO callback and permission loading as a single boolean.
  • Storing tokens in localStorage without threat review.
  • Rendering unsanitized HTML.
  • Adding scripts that force weak CSP.
  • Logging PII or tokens.
  • Using mutable third-party URLs.
  • Treating CORS as authorization.

Review Checklist

  • Is untrusted content rendered safely?
  • Is CSP documented and tested?
  • Are external static assets protected with SRI where feasible?
  • Are cookies configured with secure attributes?
  • Are state-changing requests protected from CSRF?
  • Is authorization enforced server-side?
  • Are SSO callback, deep-link, and session-refresh states designed?
  • Are tokens excluded from URLs, logs, and analytics?
  • Are third-party scripts reviewed?

Senior Deep Dive: Browser Trust Boundaries

Senior frontend security is not memorizing attack names. It is knowing where trust changes inside the browser and making unsafe paths hard to create.

Frontend trust-boundary map

BoundaryRiskSenior control
server data to DOMXSS through unsafe renderingsafe renderers, sanitizer, Trusted Types, CSP
URL to app stateopen redirect, injection, confused filterstyped parsing, allowlists, canonicalization
browser storage to UIstale auth or leaked sensitive datastorage rules, expiry, logout cleanup
third-party script to pagedata exfiltration, performance, mutationownership, consent, CSP, isolation, kill switch
iframe/postMessage to appspoofed commands or token leakageorigin checks, schema validation, minimal messages
UI permission to server actionhidden controls mistaken for authserver authorization and negative tests

The weakness this corrects: mid-level engineers often think "the backend handles security." The backend is authoritative, but frontend code can still leak data, widen exploitability, and mislead users.

Secure rendering contract

Any feature that renders rich content needs a written contract:

  • source of content: user, admin, CMS, translation, partner, internal system
  • allowed content model: plain text, Markdown subset, sanitized HTML, trusted system markup
  • sanitizer owner and configuration
  • allowed URL schemes
  • image and iframe policy
  • test fixtures for malicious content
  • CSP and Trusted Types compatibility
  • review requirement for new allowed tags or attributes

Do not let each component decide whether a string is safe. Safety should be centralized, typed, and tested.

Session and permission drift

Frontend apps often keep session snapshots, role lists, feature flags, tenant context, and cached profile data. These can drift from server truth. Senior engineers design for drift: refetch permissions after tenant switch or privilege-sensitive navigation, handle 401/403 as normal states, clear cached privileged data on logout and account switch, avoid showing irreversible actions based only on stale client state, and treat server denial as authoritative even when UI thought action was allowed.

Third-party and dependency risk review

Before adding a browser dependency or script, answer:

QuestionWhy
Does it execute in the user's browser?browser execution can access page context
Does it parse untrusted input?parsers often create security risk
Does it add transitive dependencies?supply-chain and bundle exposure
Does it phone home?privacy, compliance, and debugging
Can it be isolated or lazy-loaded?reduces blast radius
Who updates it after advisory?prevents orphaned risk

This is senior behavior because it weighs security, performance, privacy, and maintainability together.

Security test placement

Security checks should exist at multiple layers: unit tests for URL parsing and sanitizer policy, integration tests for 401/403 and stale-session behavior, E2E tests for logout and protected actions, header tests for CSP and cookies, dependency scanning with owner review, and production reporting for CSP violations and auth errors. Security that exists only as a checklist will drift.

Exercises

Exercise 1 - XSS Sink Audit

Search the codebase for dangerous sinks and document why each is safe or unsafe.

Exercise 2 - Permission Drift Drill

Simulate a user losing a permission while the app is open. What does the UI do?

Exercise 3 - Script Threat Review

Pick one third-party script and complete a script threat review record.

Further Reading