Frontend Security

SkillWeb & browsing

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.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Frontend Security skill

What this skill tells your AI

The instructions your AI receives, as published by shieldnet-360/secure-vibe in skills/frontend-security/SKILL.md and read by ahel’s review.

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

Signals

GitHub stars
22
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
frontend-security
Source
github.com/shieldnet-360/secure-vibe