Web Security: XSS, CSRF, CORS, SOP#
Problem statement (interviewer prompt)
A web app on app.example.com calls APIs on api.example.com and embeds third-party widgets. Explain how the browser's Same-Origin Policy, CORS, and Content Security Policy combine to prevent classic web attacks (XSS, CSRF, clickjacking).
Browsers enforce same-origin policy: scripts from one origin can't read responses from another. CORS is the explicit relaxation; CSP is the explicit lockdown. CSRF and XSS are the attacks each layer addresses.
flowchart TB
Browser[Browser] -->|SOP enforced| Origins[Origin = scheme + host + port]
Origins --> CORS[CORS<br/>server opt-in for cross-origin]
Origins --> CSP[CSP<br/>limit allowed scripts, sources]
Browser --> XSS[XSS<br/>attacker JS in page]
Browser --> CSRF[CSRF<br/>cross-site state-changing requests]
XSS -. mitigated by .-> CSP
CSRF -. mitigated by .-> SameSite[SameSite cookies + token]
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class Browser client;
class CORS,CSP,SameSite edge;
class Origins,XSS,CSRF service;
These are not abstract; misconfigure any one and you ship an exploitable app.
The browser's security model is the application's first line of defence. Misunderstanding it is the most common source of web vulns.
Same-Origin Policy#
Origin = scheme + host + port
https://a.example.com != https://b.example.com (host)
https://a.example.com:443 != https://a.example.com:8080 (port)
http://a.example.com != https://a.example.com (scheme)
Scripts from origin A cannot read responses from origin B by default. They can still trigger requests (forms, images, links) - that's why CSRF exists.
CORS - relaxing SOP#
The server explicitly allows cross-origin reads:
Request:
Origin: https://app.example.com
Response:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Authorization, Content-Type
For "non-simple" requests (custom headers, JSON, methods other than GET/HEAD/POST), the browser sends a OPTIONS preflight first.
sequenceDiagram
participant B as Browser
participant S as api.example.com
B->>S: OPTIONS /resource (Origin, Access-Control-Request-Method, ...)
S->>B: 204 + Access-Control-Allow-* headers
B->>S: GET /resource
S->>B: 200 + Access-Control-Allow-Origin
Pitfalls:
- Access-Control-Allow-Origin: * cannot combine with credentials.
- Echoing arbitrary Origin is a vulnerability when combined with credentials.
- CORS is browser-side; non-browser clients ignore it.
XSS - cross-site scripting#
Attacker injects JavaScript into a page; it runs in the victim's session.
| Variant | Description |
|---|---|
| Reflected | Input echoed in response: ?q=<script> |
| Stored | Input persisted, served to others (comments, names) |
| DOM-based | Sink in client JS: innerHTML = location.hash |
Mitigations:
- Context-aware output encoding: HTML, attribute, JS, URL contexts each escape differently.
- Template engines that auto-escape (Jinja, Handlebars, React JSX).
-
Content Security Policy:
Blocks inline scripts unless they carry the matching nonce. Tightens what<script>can run. -
Trusted Types API: forces all DOM XSS sinks through a typing function.
CSRF - cross-site request forgery#
An attacker tricks the victim's browser into making a state-changing request to a site they're logged into:
The browser auto-attaches the bank's session cookies; the bank can't tell apart legitimate vs forged requests.
Mitigations:
- SameSite cookies:
SameSite=Lax(default in modern browsers) blocks cookies on cross-site requests.Strictis even safer. - CSRF tokens: per-session random token in a header or hidden field; server verifies on every state-changing request.
- Double-submit cookie: same token in cookie and request body; server checks match.
- Origin/Referer header check: reject if
Originnot in allowlist. - SOP-bound APIs: APIs that only accept JSON with custom headers can't be triggered by classical CSRF (preflight required).
Clickjacking#
Attacker iframes your site over an invisible UI; tricks user into clicking what they don't see.
Mitigation: Content-Security-Policy: frame-ancestors 'self' or the older X-Frame-Options: DENY.
Modern security headers#
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: ...
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=()
Use securityheaders.com to grade.
Cookies#
- HttpOnly: JS can't read - mitigates XSS-driven session theft.
- Secure: HTTPS only.
- SameSite: Lax / Strict / None (with Secure).
__Host-/__Secure-prefixes give extra guarantees.
Subresource Integrity#
When loading scripts from a CDN, bind to a hash:
If the CDN is compromised and the file changes, the hash mismatches and the script is rejected.
Testing#
- ZAP / Burp for dynamic scanning.
- ESLint +
eslint-plugin-securityfor static. - CSP-Evaluator (Google) for policy review.
How web security fits#
flowchart TB
WEB((Web<br/>security))
OWASP[OWASP Top 10<br/>broader categorisation]
SEC[Security fundamentals<br/>AuthN/AuthZ parent]
JWT[JWT internals<br/>bearer-token sibling]
TLS[TLS / mTLS<br/>transport layer]
OWASP --> WEB
SEC --> WEB
WEB -. complements .- JWT
TLS --> WEB
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class WEB service;
class OWASP,SEC,JWT,TLS datastore;
Glossary & fundamentals#
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
OWASP Top 10 | A01, A02, A03 cover most of this | owasp-top-10 |
HLD |
Security fundamentals | parent topic | security-fundamentals |
HLD |
TLS mTLS | the transport encryption layer | tls-mtls |
HLD |
JWT internals | bearer-token alternative to sessions | jwt-internals |
Quick reference#
Same-Origin Policy#
Origin = scheme + host + port. Different origin = no cross-origin reads.
CORS#
Server opts in to cross-origin reads via Access-Control-Allow-* headers.
Non-simple requests trigger OPTIONS preflight.
Don't: Allow-Origin: * + credentials. Don't echo arbitrary Origin.
XSS#
| Variant | Defense |
|---|---|
| Reflected | Output encoding |
| Stored | Encode + CSP |
| DOM-based | Trusted Types, safe sinks |
CSP example:
CSRF#
Defenses: - SameSite=Lax cookie (default browser) - Anti-CSRF token in header or hidden field - Double-submit cookie - Check Origin/Referer - JSON-only API + custom header → forces preflight
Cookie flags#
- HttpOnly, Secure, SameSite=Lax
__Host-prefix for tightest scope
Modern headers#
- HSTS
- CSP
- X-Content-Type-Options: nosniff
- X-Frame-Options or frame-ancestors
- Referrer-Policy
- Permissions-Policy
Tools#
- ZAP / Burp - DAST
- CSP-Evaluator
- securityheaders.com
Refs#
- MDN SOP, CORS
- OWASP XSS / CSRF Cheat Sheets
FAQ#
What is the same-origin policy?#
A browser rule that blocks scripts on one origin from reading responses or DOM content from another origin. It is the foundation of web security and the reason CORS exists.
How does CORS work?#
The server opts in by sending Access-Control-Allow-Origin and related headers. For non-simple requests, the browser sends a preflight OPTIONS request to check the policy before the real call.
What is the difference between CSRF and XSS?#
CSRF tricks an authenticated user into submitting an unwanted request. XSS executes attacker JavaScript in the victim's browser. CSRF is solved by tokens and SameSite cookies; XSS is solved by escaping and CSP.
How does Content Security Policy help?#
CSP restricts which sources of scripts, styles, images, and frames the browser will load. It is a defense-in-depth layer that limits damage even if an XSS vulnerability slips into the app.
What does the SameSite cookie attribute do?#
It controls whether a cookie is sent on cross-site requests. SameSite=Lax or Strict blocks most CSRF vectors because the session cookie no longer travels with attacker-initiated requests.
Related Topics#
- OWASP Top 10: the broader categorisation; XSS = A03 injection, broken access = A01
- Security Fundamentals: AuthN/AuthZ/Encryption parent topic
- JWT Internals: bearer-token sibling of cookies for cross-origin APIs
Further reading#
- Doc - MDN - Same-origin policy
- Doc - MDN - CORS
- Doc - OWASP - XSS Prevention Cheat Sheet
- Doc - OWASP - CSRF Prevention Cheat Sheet
- Tool - securityheaders.com