Browser-side hardening: XSS and safe text binding, per-sink URL policy, DOM clobbering, nonce-based CSP, Trusted Types, subresource integrity, iframe capability minimization, postMessage validation, and where client state may live. Use when generating HTML, JSX, Vue, or Svelte templates, setting response headers in a web app, embedding third-party scripts or frames, or storing anything client-side.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add ShieldNet-360/secure-vibe --skill frontend-security --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Frontend Security?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/shieldnet-360-frontend-security-dab1ea0a)More formats (shields.io, HTML) on the badges page.
---
id: frontend-security
version: "2.0.0"
title: "Frontend Security"
description: "Browser-side hardening: XSS and safe text binding, per-sink URL policy, DOM clobbering, nonce-based CSP, Trusted Types, subresource integrity, iframe capability minimization, postMessage validation, and where client state may live. Use when generating HTML, JSX, Vue, or Svelte templates, setting response headers in a web app, embedding third-party scripts or frames, or storing anything client-side."
category: prevention
severity: high
applies_to:
- "when generating HTML / JSX / Vue / Svelte templates"
- "when wiring up response headers in a web app"
- "when adding third-party script tags, CDN resources, or embedded frames"
- "when passing messages between windows, frames, or workers"
- "when storing state client-side"
languages: ["html", "javascript", "typescript", "tsx", "jsx", "vue", "svelte"]
token_budget:
minimal: 1650
compact: 2250
full: 2900
rules_path: "rules/"
related_skills: ["cors-security", "auth-security", "api-security"]
last_updated: "2026-08-12"
sources:
- "OWASP XSS Prevention Cheat Sheet"
- "OWASP Content Security Policy Cheat Sheet"
- "OWASP DOM Clobbering Prevention Cheat Sheet"
- "OWASP HTML5 Security Cheat Sheet"
- "W3C Trusted Types specification"
- "CWE-79: Improper Neutralization of Input During Web Page Generation"
- "CWE-346: Origin Validation Error"
---
# Frontend Security
## Rules (for AI agents)
### ALWAYS
- Render untrusted data through the framework's ordinary text interpolation, which
escapes for the output context. Do not reach for a raw-HTML API merely to display
user data. Where rich HTML genuinely must be rendered, put it through a maintained
sanitizer (DOMPurify) with an explicit allowlist first.
- Validate URL-bearing attributes **per sink**, not against one global scheme list. A
navigation target (`href`, `action`, `formaction`, an `<iframe src>`) and an image
source do not share a trust model: `javascript:` is never acceptable anywhere, while
a narrowly constrained `data:image/…` or `blob:` may be exactly what an image sink
requires. Decide the permitted schemes for each sink and check against that list.
- Read security-sensitive configuration and control-flow values from lexical variables
or an object you own — never from a named `window` / `document` property
(`window.config`, `document.redirectTo`, an implicit global). The browser exposes
elements by `id` and `name` as named properties, so injected markup can shadow the
value your code expected. An explicit `getElementById()` lookup is not the problem;
trusting an ambient global is. Type-check what you read before using it in a
privileged way.
- Build the `Content-Security-Policy` around a **nonce or hash as the trust root**:
`script-src 'nonce-{value}' 'strict-dynamic'; object-src 'none'; base-uri 'none'`.
Keeping `'self'` beside the nonce means every same-origin script is still trusted —
that is a host-allowlist policy wearing a nonce, which may be the right trade-off
but is not a strict CSP, so do not call it one. Generate the nonce from a CSPRNG
afresh for **every HTML response**, and apply it only to scripts the server itself
authorizes.
- Where you deploy Trusted Types, enforce `require-trusted-types-for 'script'` **and**
restrict which policies may exist with the `trusted-types` directive. The first
makes DOM sinks demand a typed value; without the second, any code can mint a
pass-through policy (`createHTML: s => s`) and the guarantee is gone. Keep policies
few, named, and centrally reviewed.
- Load third-party scripts and stylesheets from **version-pinned, immutable** URLs
with `integrity="sha384-…"` and `crossorigin="anonymous"`. A hash pinned against a
mutable URL breaks the page the first time the provider ships a legitimate update,
which is how integrity checks come to be quietly deleted. If a provider cannot offer
stable bytes with CORS, self-host rather than drop SRI.
- Sandbox every `<iframe>`, starting from **no** capability tokens and adding only
what the embedded content needs. Do not combine `allow-scripts` with
`allow-same-origin`: together they let the framed document reach the parent and
remove its own `sandbox` attribute, so the sandbox stops meaning anything. If the
content genuinely needs both, isolate it on a separate origin instead.
- On `postMessage`, name an explicit `targetOrigin` when sending — never `*` — and on
receipt verify `event.origin` against an allowlist, validate the message's shape
before reading any field, and where the channel expects one particular frame, check
`event.source` as well. Origin, schema and sender are three separate checks.
- Set `X-Content-Type-Options: nosniff`, `Referrer-Policy:
strict-origin-when-cross-origin` or stricter, `Permissions-Policy` dropping unused
features, and `Cross-Origin-Opener-Policy: same-origin` to sever the opener
relationship at the document level. Remove framework version banners here too
(`X-Powered-By`, a detailed `Server`). Note that `no-referrer-when-downgrade` is
**weaker** than the current browser default — configuring it is a step backwards.
- Send HSTS on production HTTPS. Add `includeSubDomains` only after confirming every
subdomain serves HTTPS, and `preload` only as a deliberate, hard-to-reverse
commitment for the registrable domain: the token makes a domain *eligible* for the
preload list, it does not enrol it, and reversing it is slow.
- Keep authentication secrets out of JavaScript-readable storage — no access tokens,
refresh tokens, JWTs, or sensitive personal or business data in `localStorage` or
`sessionStorage`, where a single XSS reads all of it — and clear authenticated
client-side state on logout. Server-issued session cookies are the alternative:
`Secure`, `HttpOnly`, an application-appropriate `SameSite`, and the `__Host-`
prefix for host-only cookies at `Path=/`. `auth-security` owns issuing them; this
rule exists so a frontend review recognises an auth value that has escaped into
JavaScript's reach.
### NEVER
- Use `dangerouslySetInnerHTML`, `v-html`, `{@html …}`, `innerHTML =`, or
`document.write` with untrusted input.
- Use `eval`, `new Function`, `setTimeout(string)`, or `setInterval(string)`.
- Read or write `document.cookie` from JavaScript for an auth cookie — it should be
`HttpOnly`, which means JavaScript cannot see it and code that does is reaching for
a cookie that was never hardened.
- Treat **tightening the HTML sanitizer** as the fix when the exploit rides on content
the sanitizer allows **by design** — a valid link, an `<img src>`, a permitted
attribute. The sanitizer is working. The vulnerable behaviour is downstream, where
some sink turns that allowed value into a navigation, a command, or a native
capability. Fix the sink and reduce its authority.
### KNOWN FALSE POSITIVES
- Internal admin tools rendering Markdown or rich text from trusted authors may use a
raw-HTML API after a sanitizer pass; document the sanitizer call inline.
- A deliberately **sandboxed** browser-extension page may use a more permissive CSP
for code that needs eval-like behaviour, provided it stays isolated from the
extension APIs. That is not a licence to add `'unsafe-eval'` to MV3
`extension_pages` — Chrome rejects that policy outright, and WebAssembly has its own
`'wasm-unsafe-eval'` token.
- A WebSocket to a non-same-origin endpoint where the server validates `Origin`.
- A native or WASM decoder that is merely registered or configured is not yet an
attack surface. Before flagging one, establish a reachable runtime path from
untrusted input to that decoder — including an implementation fetched lazily at
runtime, which does not have to be in the shipped bundle to be reachable.
## Context (for humans)
Escaping is still the base layer and CSP is still what turns one missed escape into a
report instead of a stolen session. What has changed is where the browser now draws
its boundaries: opener isolation, cross-origin isolation, and Trusted Types are
document-level controls that a per-element attribute cannot replace.
Two failure shapes account for most of what goes wrong in review. The first is a
control that is present but hollow — a nonce alongside `'self'`, a sandbox with
`allow-scripts allow-same-origin`, Trusted Types enforced without restricting who may
create a policy. Each looks like the hardened version and grants what it appears to
withhold. The second is a defence recommended against an old default: browsers have
moved, and a `Referrer-Policy` copied from a 2019 guide now configures something
weaker than doing nothing at all.
The corollary for anything written here: prefer the browser's current default to a
remembered value, and when a rule names a header value or a token, check it against
the platform rather than against an example.
## References
- `references/verifying-findings.md` — confirm or refute a finding, then lock it
- `references/browser-controls.md` — per-framework text binding, strict versus
allowlist CSP, the response-header matrix, when COOP / COEP / CORP each apply, and
extension-CSP specifics
- `rules/csp_defaults.json`
- `rules/xss_sinks.json`
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).
- [OWASP CSP Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html).
- [OWASP DOM Clobbering Prevention](https://cheatsheetseries.owasp.org/cheatsheets/DOM_Clobbering_Prevention_Cheat_Sheet.html).
- [Trusted Types specification (W3C)](https://www.w3.org/TR/trusted-types/).
- [CWE-79](https://cwe.mitre.org/data/definitions/79.html) · [CWE-346](https://cwe.mitre.org/data/definitions/346.html).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!