$npx -y skills add utkusen/sast-skills --skill sast-xssDetect Cross-Site Scripting (XSS) vulnerabilities in a codebase using a three-phase approach: recon (find HTML/JS/DOM sink sites), batched verify (trace user input to sinks in parallel subagents, 3 sink sites each), and merge (consolidate batch results). Requires sast/architectur
| 1 | # Cross-Site Scripting (XSS) Detection |
| 2 | |
| 3 | You are performing a focused security assessment to find Cross-Site Scripting vulnerabilities in a codebase. This skill uses a three-phase approach with subagents: **recon** (find sink sites), **batched verify** (trace taint for parallel batches of up to 3 sinks each), and **merge** (consolidate batch results into one report). |
| 4 | |
| 5 | **Prerequisites**: `sast/architecture.md` must exist. Run the analysis skill first if it doesn't. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## What is XSS |
| 10 | |
| 11 | XSS occurs when user-supplied input is incorporated into a web page's HTML, JavaScript, or DOM without proper escaping or sanitization. This allows attackers to inject and execute arbitrary scripts in victims' browsers, leading to session hijacking, credential theft, defacement, and malware distribution. |
| 12 | |
| 13 | The core pattern: *unescaped, unsanitized user input reaches an HTML/JS output sink.* |
| 14 | |
| 15 | ### XSS Types |
| 16 | |
| 17 | - **Reflected XSS**: User input is immediately echoed back in the HTTP response (e.g., a search term rendered directly into the page HTML). |
| 18 | - **Stored XSS**: User input is saved to persistent storage (database, file) and later rendered in HTML for other users. |
| 19 | - **DOM-based XSS**: Client-side JavaScript reads from an attacker-controlled source (`location.search`, `location.hash`, `document.cookie`) and writes to a dangerous DOM sink (`innerHTML`, `eval`, `document.write`) without server involvement. |
| 20 | |
| 21 | ### What XSS IS |
| 22 | |
| 23 | **Server-side HTML sinks** — rendering user data into HTML responses without escaping: |
| 24 | - Python/Jinja2: `{{ var | safe }}`, `{% autoescape off %}...{{ var }}...{% endautoescape %}` |
| 25 | - Python/Django: `mark_safe(var)`, `format_html(...)` with `%s` and unescaped input, `{{ var | safe }}` in templates |
| 26 | - Python/Flask: `Markup(var)`, `render_template_string(f"...{var}...")` |
| 27 | - PHP: `echo $var`, `print $var`, `<?= $var ?>` without `htmlspecialchars()` |
| 28 | - Ruby/Rails: `raw(var)`, `var.html_safe`, `<%= raw var %>`, `content_tag` with `.html_safe` |
| 29 | - Java/JSP: `<%= var %>`, `${var}` without `<c:out>` or `fn:escapeXml()` |
| 30 | - Java/Thymeleaf: `th:utext="${var}"` (unescaped), `[(${var})]` |
| 31 | - Go/html-template misuse: using `template.HTML(var)`, `template.JS(var)`, `template.URL(var)` to bypass auto-escaping |
| 32 | - C#/Razor: `@Html.Raw(var)`, `MvcHtmlString.Create(var)` |
| 33 | - Node.js/EJS: `<%- var %>` (unescaped), vs `<%= var %>` (safe) |
| 34 | - Node.js/Handlebars: `{{{ var }}}` (triple-brace, unescaped) |
| 35 | - Node.js/Pug: `!{var}` (unescaped) |
| 36 | - Express: `res.send("<html>..." + var + "...")`, `res.write("<p>" + var + "</p>")` |
| 37 | |
| 38 | **Client-side DOM sinks** — JavaScript writing user-controlled data to the DOM unsafely: |
| 39 | - `element.innerHTML = var` |
| 40 | - `element.outerHTML = var` |
| 41 | - `document.write(var)`, `document.writeln(var)` |
| 42 | - `element.insertAdjacentHTML('beforeend', var)` |
| 43 | - jQuery: `$(element).html(var)`, `$(element).append(var)` (when var contains HTML), `$('<div>' + var + '</div>')` |
| 44 | - React: `dangerouslySetInnerHTML={{ __html: var }}` |
| 45 | - Angular: `[innerHTML]="var"`, `bypassSecurityTrustHtml(var)`, `bypassSecurityTrustScript(var)`, `bypassSecurityTrustUrl(var)` |
| 46 | - Vue: `v-html="var"` |
| 47 | |
| 48 | **JavaScript execution sinks** — user-controlled data evaluated as code: |
| 49 | - `eval(var)` |
| 50 | - `setTimeout(var, delay)` / `setInterval(var, delay)` when `var` is a string |
| 51 | - `new Function(var)()` |
| 52 | - `element.setAttribute('onclick', var)`, `element.setAttribute('href', 'javascript:' + var)` |
| 53 | - `location.href = var`, `location.replace(var)`, `location.assign(var)` (when var is user-controlled and can be `javascript:...`) |
| 54 | - `element.src = var`, `element.action = var` (script injection via `javascript:` URIs) |
| 55 | - `scriptElement.text = var`, `scriptElement.textContent = var` |
| 56 | |
| 57 | **DOM-based sources** — attacker-controlled inputs read by client-side JavaScript: |
| 58 | - `location.search` (URL query string) |
| 59 | - `location.hash` (URL fragment) |
| 60 | - `location.href` |
| 61 | - `document.referrer` |
| 62 | - `document.URL`, `document.documentURI` |
| 63 | - `document.cookie` |
| 64 | - `postMessage` event data (`event.data`) |
| 65 | - `window.name` |
| 66 | - `localStorage.getItem(...)`, `sessionStorage.getItem(...)` (if populated from URL or postMessage) |
| 67 | |
| 68 | ### What XSS is NOT |
| 69 | |
| 70 | Do not flag these as XSS: |
| 71 | |
| 72 | - **CSRF**: Forging requests on behalf of a user — a separate vulnerability class |
| 73 | - **SQLi via XSS**: Injecting SQL through an XSS vector — the SQL injection itself is the primary finding |
| 74 | - **Clickjacking**: Embedding pages in iframes — different vulnerability class |
| 75 | - **Header injection**: Injecting newlines into HTTP response headers — separate class (HTTP Response Splitting) |
| 76 | - **Safe template output**: Auto-escaped `{ |