Skip to content
Back to skills

Frontend Security

ASecurity

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.

  • 22 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 6, 2026
securityjavascripttypescriptrustgojavavueawsgitapifrontend

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned September 6, 2026

npx -y skills add ShieldNet-360/secure-vibe --skill frontend-security --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Frontend Security?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Frontend Security
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shieldnet-360-frontend-security-dab1ea0a/badge)](https://www.skillsdirectory.com/skills/shieldnet-360-frontend-security-dab1ea0a)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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).

Files in this skill

  • SKILL.md9.7 KB
  • references/browser-controls.md5.9 KB
  • references/verifying-findings.md3.3 KB
  • rules/csp_defaults.json1.4 KB
  • rules/xss_sinks.json3.1 KB
  • tests/corpus.json4.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.