Browser Attack Surface Architecture
Why this chapter matters
The browser is a powerful, hostile runtime. It executes untrusted input, loads third-party code, stores session state, follows redirects, sends cookies, parses URLs, renders HTML, runs extensions, and exposes APIs that can be misused. Frontend security architecture is the discipline of shrinking and governing that attack surface.
The web security source books show that many "frontend bugs" are really boundary failures: unsafe DOM writes, confused CORS policy, weak CSRF assumptions, missing frame protections, dependency trust, and business logic encoded only in the UI. A frontend architect must make secure behavior the default path.
Core mental model
Security architecture starts by asking: what can attacker-controlled input influence?
Trace untrusted influence through:
- URL parameters
- route state
- server-rendered data
- CMS content
- user-generated content
- third-party scripts
- package dependencies
- browser storage
- postMessage events
- HTML, CSS, JavaScript, and URL sinks
Then design policies that reduce what those inputs can reach.
Source-derived architecture notes
- DOM XSS is a data-flow problem: attacker-controlled sources become dangerous when they reach HTML, script, URL, style, or DOM mutation sinks without strong controls.
- Prototype pollution can turn ordinary object merging and dependency behavior into client-side compromise, especially when untrusted objects cross trust boundaries.
- Clickjacking, tabnabbing, and opener attacks exploit browser navigation and framing behavior; they are architectural header and link-policy concerns, not only code review items.
- CORS is not authorization. It is a browser sharing policy. Server-side authorization must still enforce access for every protected operation.
- CSRF risk depends on browser credential behavior, cookie settings, state-changing endpoints, SameSite policy, tokens, and origin verification.
- Third-party scripts and dependencies inherit too much power by default. They need ownership, isolation, monitoring, and removal paths.
Architecture decision framework
| Decision | Conservative option | Flexible option | Tradeoff signal |
|---|---|---|---|
| HTML rendering | Sanitized typed renderer | Raw HTML escape hatch | Use raw HTML only with reviewed source, sanitizer, and sink ownership. |
| Cross-origin access | Minimal explicit allowlist | Broad wildcard policies | Allow only known origins and methods required by product flows. |
| Framing | Deny by default | Partner-specific frame allow | Permit framing only for documented embedding use cases. |
| External links | noopener noreferrer defaults | Per-link manual review | Default when user-generated or third-party links exist. |
| Dependencies | Reviewed and monitored | Install-on-demand | Require review when package runs in browser, parses input, or affects auth. |
Implementation patterns
Baseline pattern for small teams
Create a browser attack-surface inventory:
- all raw HTML paths
- all URL construction paths
- all
postMessagelisteners - all third-party scripts
- all auth/session storage paths
- all cross-origin APIs
- all frame/embed use cases
- all packages that parse or render user content
Then define secure defaults: sanitizer wrapper, no raw DOM sinks outside approved modules, secure headers, SameSite cookies, CSRF policy, frame-ancestors, safe external link component, and dependency review for browser-executed packages.
Scale pattern for multi-team organizations
Move security rules into shared contracts:
- lint rules for dangerous DOM sinks and unsafe URL APIs
- shared HTML sanitizer and Trusted Types policy
- security header baseline with route-level exceptions
- CORS policy registry with owner and reason
- package risk scorecard and dependency update SLAs
- threat-model review for new cross-origin, embedded, payment, admin, or user-content surfaces
Security review should focus on exceptions and new trust boundaries, not rediscovering baseline issues in every pull request.
Migration-safe pattern for legacy systems
Do not start by enforcing everything. Start with visibility:
- Inventory sinks and third-party scripts.
- Add CSP and Trusted Types in report-only mode where feasible.
- Replace the most dangerous raw HTML paths with a sanitizer wrapper.
- Lock frame and opener policies.
- Tighten CORS and cookies route by route.
- Convert repeated review comments into lint rules.
Anti-patterns and failure modes
- Symptom: stored content executes script for later users. Root cause: user content reaches an HTML sink without sanitization. Prevention control: typed sanitized rendering path and sink linting.
- Symptom: a partner origin gets unintended API access. Root cause: CORS allowlist is broad and treated as authorization. Prevention control: narrow CORS plus server authorization tests.
- Symptom: admin pages can be embedded in hostile sites. Root cause: missing
frame-ancestorsor frame policy. Prevention control: deny framing by default. - Symptom: external link opens a page that controls the original tab. Root cause: missing opener protection. Prevention control: safe link component with
noopener. - Symptom: dependency update becomes urgent incident work. Root cause: browser dependency ownership and update SLA are undefined. Prevention control: dependency risk scorecard and monitored advisories.
Verification checklist
- Dangerous DOM sinks are inventoried, linted, and wrapped.
- CSP and Trusted Types rollout path exists for high-risk apps.
- CORS rules are narrow, owned, and tested against authorization.
- CSRF strategy is documented for state-changing requests.
- Cookies use appropriate
HttpOnly,Secure, andSameSiteattributes. - Framing and opener policies are enforced through headers and components.
- Third-party scripts have owners, purpose, consent posture, and kill switches.
- Browser dependencies are tracked with update and vulnerability SLAs.
Metrics and scorecards
Track leading indicators:
- dangerous sink count
- CSP and Trusted Types violation trend
- CORS exception count and age
- high-risk browser dependency age
- third-party script count by owner
- unresolved security review findings
Track lagging indicators:
- XSS-class findings escaping review
- auth/session incidents
- time to revoke risky third-party code
Deep architecture model
Source-to-sink map
The most useful browser security artifact is a source-to-sink map. It connects untrusted influence to dangerous browser capabilities:
| Source | Typical path | Dangerous sinks |
|---|---|---|
| URL query/hash | router, filters, redirect targets | navigation, DOM insertion, analytics payloads |
| User content | CMS, comments, profile fields | HTML, Markdown, rich text, image URLs |
| API response | server-rendered props, JSON, cache | templates, DOM attributes, script config |
| Third-party script | tag manager, SDK, ad code | DOM, cookies, storage, network, globals |
| postMessage | iframe integrations, widgets | privileged actions, token exchange, navigation |
| Browser storage | localStorage/sessionStorage/cache | auth snapshots, feature flags, stale permissions |
For each row, decide which transformations are allowed and which sinks are prohibited. The rule should be enforced by wrappers, linting, CSP/Trusted Types, and review templates, not memory.
DOM XSS architecture
DOM XSS prevention needs more than "sanitize input." Architect the rendering path:
- Untrusted rich content must enter through one typed content boundary.
- That boundary chooses the allowed content model: plain text, Markdown subset, sanitized HTML, or trusted system HTML.
- Sanitization configuration is owned and tested.
- Rendering APIs expose safe outputs, not arbitrary strings.
- Dangerous sinks are linted outside the approved renderer.
- CSP and Trusted Types reduce blast radius if a path is missed.
The dangerous pattern is distributed trust: every feature decides whether a string is safe. Centralize that decision.
CORS, cookies, and CSRF as one system
CORS, cookies, and CSRF should be reviewed together because browser credential behavior connects them.
| Concern | Architectural question |
|---|---|
| CORS origin | Which origins may read responses in browsers? |
| Credentials | Are cookies or auth headers sent cross-origin? |
| Authorization | Does the server enforce permission independent of CORS? |
| CSRF | Can another site cause a state-changing request with user credentials? |
| SameSite | Does cookie policy match login, embed, and cross-site flows? |
| Preflight | Are methods and headers narrow enough to reveal intent? |
Never approve a CORS change without naming the caller, credential mode, server authorization, CSRF posture, and owner.
Third-party script threat model
A browser script is not a passive dependency. It may read DOM, observe user behavior, send network requests, block the main thread, and interact with storage. Require a register:
| Field | Required answer |
|---|---|
| Business owner | Who can justify and remove it? |
| Technical owner | Who debugs and updates it? |
| Data access | What DOM, events, identifiers, and user data can it observe? |
| Load timing | Before consent, after consent, after interaction, lazy, or server-side? |
| Isolation | iframe sandbox, worker, server-side proxy, or full page access? |
| Security policy | CSP entry, SRI where possible, allowed connect targets |
| Kill switch | How is it disabled without redeploy? |
| Monitoring | Error, latency, network, and policy violation signals |
Tag-manager sprawl is an architectural failure because it separates runtime power from engineering review.
Browser storage rules
Treat browser storage as hostile persistence:
- do not store long-lived secrets in localStorage
- distinguish auth truth from UI session hints
- expire cached permissions and profile snapshots
- clear sensitive state on logout across tabs
- avoid storing personal data in analytics replay or debug logs
- version persisted state so old clients do not misinterpret new semantics
Storage bugs often become security bugs when stale state makes the UI imply access that the server no longer grants.
Security review drill
For a new embedded partner widget, review:
- Is the partner framed by us or are we framed by the partner?
- What
frame-ancestors, sandbox, andpostMessagerules apply? - Which origins are allowed to send messages?
- How are messages authenticated, typed, and rejected?
- Can the partner trigger navigation, payment, account change, or data export?
- What data can the partner observe before consent?
- How is the integration disabled during incident response?
This drill surfaces browser risks that normal API review misses.
Exercises
- Trace one user-generated content field from input to render. Identify every transformation and sink.
- Write a CORS decision record for one API: origins, methods, credentials, authorization, and owner.
- Build a safe external-link component contract.
- Threat-model a new third-party script integration: permissions, data access, failure behavior, and removal path.
Source Lens
This chapter is synthesized from:
- Web Application Security, 2nd Edition - XSS, CSRF, CORS, client-side attacks, prototype pollution, clickjacking, tabnabbing, dependency risk, and defensive architecture.
- Web Security for Developers - browser/server fundamentals, sessions, permissions, injection, TLS, third-party code, and attack mitigation.
- Grokking Web Application Security - practical vulnerability models, dependency risk, secure configuration, information leakage, SSRF, open redirects, and incident response.