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.
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 installReview the instructions the agent will receive before you install.
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.
| Framework / engine | Escape hatches to trace |
|---|---|
| React / Preact / Solid | dangerouslySetInnerHTML, innerHTML props, refs writing el.innerHTML |
| Vue | v-html, render functions setting innerHTML |
| Angular | bypassSecurityTrustHtml/Url/ResourceUrl/Script/Style ([innerHTML] alone is sanitised) |
| Svelte | {@html …} |
| DOM / jQuery | innerHTML, outerHTML, insertAdjacentHTML, document.write, .html(), $(userString), iframe.srcdoc |
| Jinja2 / Django | ` |
| Go | template.HTML(userValue), text/template used to render HTML |
| JVM | Thymeleaf th:utext, JSP <%= %> or escapeXml="false", FreeMarker ?no_esc |
| Node templates | Handlebars {{{ }}}, 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.
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).
Authorization: Bearer header are not CSRF-able.GET route is CSRF-able even with SameSite=Lax.csrf_exempt, ignoringRequestMatchers) is a removed guard.// ❌ 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).
// ❌ 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.
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.
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.
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.
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.allowlist/capabilities (shell open, fs scopes of **), dangerousRemoteDomainIpcAccess.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.
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.importScripts of a third-party URL without integrity control.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.
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).
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.
{user.name} in JSX, {{ name }} in Vue/Jinja, or [innerHTML] in Angular.X-Frame-Options or HSTS as a vulnerability.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.BrowserWindow created with nodeIntegration: true or loading remote URLs.Origin.cache.puts responses from authenticated API routes.id/name next to code that reads window.<name>.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
No reviews yet. Tell others what you thought.
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.
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.
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.
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.
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.
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.
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.