Skip to content
cccskills
FreePublished on GitHub

web-frontend-security

Use when the diff renders user-controlled content as HTML, builds links or redirects, changes CSP or CORS, adds a cookie-authenticated state change or WebSocket, a postMessage handler, a markdown renderer, a service worker, browser storage of tokens or private data, a frameable sensitive action, or an Electron/Tauri window

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md10.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Web Frontend Security

Overview

Modern template engines and UI frameworks escape by default. XSS comes back through the doors that switch escaping off, through the wrong escaper for the context, and through URLs. CSRF comes back whenever cookies authenticate a state change.

Core principle: find the escape hatch, then trace what flows into it. {value} in JSX is the control working — not a finding.

1. XSS Sinks by Framework (CWE-79)

Framework / engineEscape hatches to trace
React / Preact / SoliddangerouslySetInnerHTML, innerHTML props, refs writing el.innerHTML
Vuev-html, render functions setting innerHTML
AngularbypassSecurityTrustHtml/Url/ResourceUrl/Script/Style ([innerHTML] alone is sanitised)
Svelte{@html …}
DOM / jQueryinnerHTML, outerHTML, insertAdjacentHTML, document.write, .html(), $(userString), iframe.srcdoc
Jinja2 / Django`
Gotemplate.HTML(userValue), text/template used to render HTML
JVMThymeleaf th:utext, JSP <%= %> or escapeXml="false", FreeMarker ?no_esc
Node templatesHandlebars {{{ }}}, EJS <%- %>, Pug !=
.NET@Html.Raw, MarkupString
// ❌ user-authored bio rendered as HTML
<div dangerouslySetInnerHTML={{ __html: user.bio }} />
// ✅ sanitised with an allowlist sanitiser, or rendered as text
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(user.bio) }} />

Markdown renderers count as HTML sinks when raw HTML is enabled: marked without a sanitiser, markdown-it with html: true, react-markdown with rehype-raw and no rehype-sanitize.

URLs are sinks too. href, src, formaction, location.href = …, window.open(…) with user data accept javascript: (and data: for some sinks). Validate the scheme (https:, mailto:) rather than relying on the framework's version-dependent handling.

<!-- ❌ profile website link -->
<a :href="user.website">site</a>
<!-- ✅ -->
<a :href="safeHttpUrl(user.website)">site</a>   <!-- returns "#" unless new URL(v).protocol is http: or https: -->

Wrong escaper for the context: HTML-escaping a value placed inside a <script> block, an event-handler attribute or a CSS value does not make it safe. Data embedded into an inline script needs JSON serialisation that escapes < (</script> breaks out).

Stored XSS (one user's content shown to another) → HIGH. Reflected → MEDIUM to HIGH by reach. Self-XSS → not a finding.

2. CSRF (CWE-352)

A finding when all hold: the endpoint changes state, the browser authenticates it automatically (cookies, HTTP auth), and nothing ties the request to the app's own pages (CSRF token, SameSite=Lax/Strict on the session cookie with no state-changing GET, an Origin/Sec-Fetch-Site check, a custom-header requirement the CORS policy enforces).

  • APIs authenticated only by an Authorization: Bearer header are not CSRF-able.
  • A new state-changing GET route is CSRF-able even with SameSite=Lax.
  • Removing or bypassing the CSRF middleware for a route (csrf_exempt, ignoringRequestMatchers) is a removed guard.

3. CORS

// ❌ any site can read authenticated responses: the request's Origin is reflected with credentials
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
// ✅ exact allowlist
if (ALLOWED_ORIGINS.has(req.headers.origin)) res.setHeader("Access-Control-Allow-Origin", req.headers.origin);

Access-Control-Allow-Origin: * without credentials is fine for public data; browsers refuse * with credentials. Watch for null origin allowed and suffix-matched origins (ssrf-and-url-allowlists has the matching pitfalls).

4. postMessage (CWE-346)

// ❌ any window can send a "token" message
window.addEventListener("message", (e) => { localStorage.setItem("token", e.data.token); });
// ✅
window.addEventListener("message", (e) => { if (e.origin !== "https://auth.example.com") return; … });

Also target.postMessage(secret, "*") — any framing or opening page can receive it.

5. CSP

A missing CSP is not reported. Adding 'unsafe-inline', 'unsafe-eval', a wildcard source or a JSONP-capable CDN to an existing policy is a weakened control: a note, or part of the finding when the same diff adds an injection sink.

6. Open Redirect (CWE-601) — high confidence only

Report only with a concrete chain: a post-login ?next= that can point off-site in an OAuth or SSO flow (code or token leakage), or a redirect used to pass an SSRF allowlist. A bare redirect on a marketing page is dropped.

7. Desktop Shells (Electron, Tauri)

In a desktop shell, renderer XSS can become code execution. This section covers the window and renderer settings; the main-process IPC handlers, URL schemes and loopback servers are desktop-and-local-ipc-security.

  • Electron: nodeIntegration: true, contextIsolation: false, sandbox: false, webSecurity: false; a preload exposing ipcRenderer or require wholesale through contextBridge; shell.openExternal(url) with an unvalidated scheme; IPC handlers that run commands or read paths from renderer arguments without validation; remote content loaded into a privileged window.
  • Tauri: broad allowlist/capabilities (shell open, fs scopes of **), dangerousRemoteDomainIpcAccess.

8. Cross-Site WebSocket Hijacking (CWE-1385)

A WebSocket upgrade carries the browser's cookies and no CORS check. A handshake authenticated by cookie that does not verify Origin against an exact allowlist (or require a token the page must fetch first) lets any site open the socket as the victim and read or send messages.

// ❌ cookie session, any origin
wss.on("connection", (ws, req) => { const user = sessionFrom(req.headers.cookie); … });
// ✅
const ok = ALLOWED_ORIGINS.has(req.headers.origin ?? "");
if (!ok) { ws.close(1008); return; }

Not a finding when the socket authenticates with a token sent in the first message or a header the page cannot be made to send.

9. Service Workers

  • The script URL passed to navigator.serviceWorker.register built from input, or served from a path user content can occupy; a Service-Worker-Allowed header that widens the scope beyond what the worker needs.
  • A fetch handler that caches personalized or authenticated responses without the user in the cache name or key, and serves them after logout or an account switch — or as the offline fallback for another user.
  • importScripts of a third-party URL without integrity control.

10. Browser Storage and Account Switching

A token in localStorage is not a finding by itself (authn-sessions-and-tokens §3). It is when a realistic lower-authority reader exists, or when the data outlives the session: private responses, drafts or auth decisions left in localStorage, IndexedDB or Cache Storage after logout and shown to the next account on a shared machine; a storage event or BroadcastChannel message that carries a token or an auth command between tabs without binding it to the current session.

11. DOM Clobbering and Prototype-Pollution Gadgets

Both need two halves in the trace. DOM clobbering: markup the attacker controls that keeps an id or name attribute (a sanitizer that allows them) and code that reads the clobbered global as configuration (window.config || defaults, document.getElementById("cdn").href for a script URL). Prototype pollution: an attacker-chosen key reaching a recursive merge or path setter and a gadget that reads the polluted property in a security decision, a script URL or an HTML sink (node-typescript-security-pitfalls §5).

12. Clickjacking

A frameable page with a one-click state change — confirming a payment, granting an OAuth scope, deleting an account, changing an email — and no frame-ancestors or X-Frame-Options. Missing frame headers on read-only or multi-step pages are hardening, not reported.

Common Mistakes

  • Flagging {user.name} in JSX, {{ name }} in Vue/Jinja, or [innerHTML] in Angular.
  • Reporting CSRF on a bearer-token API.
  • Reporting a missing CSP, X-Frame-Options or HSTS as a vulnerability.
  • Reporting an open redirect with no chain.

Red Flags

  • An escape hatch whose input traces back to another user's content.
  • DOMPurify imported but the sink fed the unsanitised variable.
  • Access-Control-Allow-Credentials: true next to a reflected origin.
  • addEventListener("message" with no origin comparison.
  • An Electron BrowserWindow created with nodeIntegration: true or loading remote URLs.
  • A WebSocket server that reads the session cookie and never reads Origin.
  • A service worker that cache.puts responses from authenticated API routes.
  • A sanitizer configured to keep id/name next to code that reads window.<name>.

References (names and links only)

OWASP Top 10:2025 A05 Injection, A01 Broken Access Control · ASVS 5.0 V3 Web Frontend Security · CWE-79, 346, 352, 601, 942, 1021, 1321, 1385 · Electron security checklist

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

See all skills by makifbaysal

Report a problem with this skill