SkillAgentSearch skills...

sast-xss

Detect 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).

Install / Use

npx skills add utkusen/sast-skills --skill sast-xss

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

89/100

Category

Security

Supported Platforms

Universal

Our assessment of sast-xss

sast-xss scores 89/100 on our quality scale, 495th of 971 Security skills we index.

Its SKILL.md is 28 KB long, well organised into 41 sections with 25 code examples: a thorough specification that gives an agent plenty to work with.

With 1,321 GitHub stars, it is one of the more widely adopted skills in the catalogue.

Substance
30/30
Structure
20/20
Description
15/15
Adoption
13/20
Freshness
11/15

Maintenance, license and trust

  • The repository was last updated about 6 months ago. That is recent enough to be usable, but agent tooling moves fast, so check the instructions against your agent's current version.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 98/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

sast-xss compared with similar skills

All 4 of these similar skills score higher than sast-xss; compare them before choosing.

SkillScoreStarsUpdatedFormat
sast-xss (this skill)by utkusen891.3k6mo agoSKILL.md
algorithmic-artby anthropics100177.9k8d agoSKILL.md
pptxby anthropics100177.9k8d agoSKILL.md
designby nextlevelbuilder100130.2k9d agoSKILL.md
ui-ux-pro-maxby nextlevelbuilder100130.2k9d agoSKILL.md

Frequently asked questions

How do I install sast-xss?
Run npx skills add utkusen/sast-skills --skill sast-xss. The install tabs above show the steps for each supported agent.
Which AI agents does sast-xss work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is sast-xss safe to use?
It is MIT-licensed and scores 98/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is sast-xss still maintained?
The repository was last updated about 6 months ago. That is recent enough to be usable, but agent tooling moves fast, so check the instructions against your agent's current version.

name: sast-xss description: >- Detect 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/architecture.md (run sast-analysis first). Outputs findings to sast/xss-results.md. Use when asked to find XSS or cross-site scripting bugs.

Cross-Site Scripting (XSS) Detection

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).

Prerequisites: sast/architecture.md must exist. Run the analysis skill first if it doesn't.


What is XSS

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.

The core pattern: unescaped, unsanitized user input reaches an HTML/JS output sink.

XSS Types

  • Reflected XSS: User input is immediately echoed back in the HTTP response (e.g., a search term rendered directly into the page HTML).
  • Stored XSS: User input is saved to persistent storage (database, file) and later rendered in HTML for other users.
  • 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.

What XSS IS

Server-side HTML sinks — rendering user data into HTML responses without escaping:

  • Python/Jinja2: {{ var | safe }}, {% autoescape off %}...{{ var }}...{% endautoescape %}
  • Python/Django: mark_safe(var), format_html(...) with %s and unescaped input, {{ var | safe }} in templates
  • Python/Flask: Markup(var), render_template_string(f"...{var}...")
  • PHP: echo $var, print $var, <?= $var ?> without htmlspecialchars()
  • Ruby/Rails: raw(var), var.html_safe, <%= raw var %>, content_tag with .html_safe
  • Java/JSP: <%= var %>, ${var} without <c:out> or fn:escapeXml()
  • Java/Thymeleaf: th:utext="${var}" (unescaped), [(${var})]
  • Go/html-template misuse: using template.HTML(var), template.JS(var), template.URL(var) to bypass auto-escaping
  • C#/Razor: @Html.Raw(var), MvcHtmlString.Create(var)
  • Node.js/EJS: <%- var %> (unescaped), vs <%= var %> (safe)
  • Node.js/Handlebars: {{{ var }}} (triple-brace, unescaped)
  • Node.js/Pug: !{var} (unescaped)
  • Express: res.send("<html>..." + var + "..."), res.write("<p>" + var + "</p>")

Client-side DOM sinks — JavaScript writing user-controlled data to the DOM unsafely:

  • element.innerHTML = var
  • element.outerHTML = var
  • document.write(var), document.writeln(var)
  • element.insertAdjacentHTML('beforeend', var)
  • jQuery: $(element).html(var), $(element).append(var) (when var contains HTML), $('<div>' + var + '</div>')
  • React: dangerouslySetInnerHTML={{ __html: var }}
  • Angular: [innerHTML]="var", bypassSecurityTrustHtml(var), bypassSecurityTrustScript(var), bypassSecurityTrustUrl(var)
  • Vue: v-html="var"

JavaScript execution sinks — user-controlled data evaluated as code:

  • eval(var)
  • setTimeout(var, delay) / setInterval(var, delay) when var is a string
  • new Function(var)()
  • element.setAttribute('onclick', var), element.setAttribute('href', 'javascript:' + var)
  • location.href = var, location.replace(var), location.assign(var) (when var is user-controlled and can be javascript:...)
  • element.src = var, element.action = var (script injection via javascript: URIs)
  • scriptElement.text = var, scriptElement.textContent = var

DOM-based sources — attacker-controlled inputs read by client-side JavaScript:

  • location.search (URL query string)
  • location.hash (URL fragment)
  • location.href
  • document.referrer
  • document.URL, document.documentURI
  • document.cookie
  • postMessage event data (event.data)
  • window.name
  • localStorage.getItem(...), sessionStorage.getItem(...) (if populated from URL or postMessage)

What XSS is NOT

Do not flag these as XSS:

  • CSRF: Forging requests on behalf of a user — a separate vulnerability class
  • SQLi via XSS: Injecting SQL through an XSS vector — the SQL injection itself is the primary finding
  • Clickjacking: Embedding pages in iframes — different vulnerability class
  • Header injection: Injecting newlines into HTTP response headers — separate class (HTTP Response Splitting)
  • Safe template output: Auto-escaped {{ var }} in Jinja2/Django/Twig/Blade/Handlebars double-brace syntax with auto-escaping on — these are safe
  • textContent / innerText: These write plain text only; no HTML parsing occurs — safe

Patterns That Prevent XSS

When you see these patterns, the code is likely not vulnerable:

1. Context-aware auto-escaping (most template engines default)

# Jinja2 / Django (auto-escape on by default)
{{ var }}          # HTML-escaped → safe

# EJS
<%= var %>         # HTML-escaped → safe

# Handlebars
{{ var }}          # HTML-escaped → safe

# Pug
= var              # HTML-escaped → safe

# Thymeleaf
th:text="${var}"   # HTML-escaped → safe

# Razor (C#)
@var               # HTML-encoded → safe

2. Explicit escaping before output

// PHP
echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
# Rails
<%= h(var) %>
<%= ERB::Util.html_escape(var) %>
// JSP with JSTL
<c:out value="${var}"/>
// or fn:escapeXml()
${fn:escapeXml(var)}
// html/template — auto-escapes by context (HTML, JS, URL, CSS)
{{.Var}}   // safe inside html/template

3. DOM manipulation using safe properties

element.textContent = userInput;   // plain text, no HTML parsing — safe
element.innerText = userInput;     // plain text — safe

4. Sanitization with an allowlisted HTML library

// DOMPurify
element.innerHTML = DOMPurify.sanitize(userInput);

// sanitize-html with strict config
const clean = sanitizeHtml(userInput, { allowedTags: [], allowedAttributes: {} });

5. React / Angular / Vue auto-escaping

// React JSX — auto-escaped
return <div>{userInput}</div>;
<!-- Angular — auto-escaped -->
<div>{{ userInput }}</div>
<!-- Vue — auto-escaped -->
<div>{{ userInput }}</div>

Vulnerable vs. Secure Examples

Python — Flask / Jinja2

# VULNERABLE: Markup() bypasses Jinja2 auto-escaping
@app.route('/greet')
def greet():
    name = request.args.get('name', '')
    return render_template_string(f"<h1>Hello, {name}!</h1>")   # raw f-string, no template escaping

# VULNERABLE: mark_safe equivalent
@app.route('/profile')
def profile():
    bio = request.args.get('bio', '')
    return render_template('profile.html', bio=Markup(bio))      # Markup() marks it as safe, bypassing escaping

# SECURE: use template with auto-escaping (never pass Markup around user input)
@app.route('/greet')
def greet():
    name = request.args.get('name', '')
    return render_template('greet.html', name=name)              # template: {{ name }} — auto-escaped

Python — Django

# VULNERABLE: mark_safe() with user input
def user_bio(request):
    bio = request.GET.get('bio', '')
    safe_bio = mark_safe(bio)   # user input bypasses Django's auto-escaping
    return render(request, 'bio.html', {'bio': safe_bio})

# SECURE: pass raw string; template handles escaping
def user_bio(request):
    bio = request.GET.get('bio', '')
    return render(request, 'bio.html', {'bio': bio})   # template: {{ bio }} — auto-escaped

PHP

// VULNERABLE: echo without escaping
function showUsername($username) {
    echo "<p>Welcome, " . $username . "</p>";
}

// SECURE: htmlspecialchars
function showUsername($username) {
    echo "<p>Welcome, " . htmlspecialchars($username, ENT_QUOTES, 'UTF-8') . "</p>";
}

Node.js — Express (string concatenation)

// VULNERABLE: user input concatenated into HTML response
app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`<h1>Results for: ${query}</h1>`);
});

// SECURE: use a template engine with auto-escaping, or escape manually
const escapeHtml = require('escape-html');
app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`<h1>Results for: ${escapeHtml(query)}</h1>`);
});

Node.js / EJS

<!-- VULNERABLE: unescaped output -->
<div><%- userInput %></div>

<!-- SECURE: escaped output -->
<div><%= userInput %></div>

Node.js / Handlebars

<!-- VULNERABLE: triple-brace, unescaped -->
<div>{{{ userInput }}}</div>

<!-- SECURE: double-brace, auto-escaped -->
<div>{{ userInput }}</div>

JavaScript — DOM Sinks

// VULNERABLE: innerHTML with URL fragment
const name = location.hash.substring(1);
document.getElementById('greeting').innerHTML = 'Hello, ' + name;

// SECURE: textContent
const name = location.hash.substring(1);
document.getElementById('greeting').textContent = 'Hello, ' + name;
// VULNERABLE: eval with postMessage data
window.addEventListener('message', (event) => {
  eval(event.data);
});

// SECURE: parse and validate; never eval postMessage data
window.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  // handle data safely
});

React

// VULNERABLE: dangerouslySetInnerHTML with user input
function Comment({ content }) {
  return <div dangerouslySetInnerHTML={{ __html: content }} />;
}

// SECURE: render as text (auto-escaped by React)
function Comment({ content }) {
  return <div>{content}</div>;
}

Angular

// VULNERABLE: bypassing Angular's DomSanitizer
constructor(private sanitizer: DomSanitizer) {}
getUserHtml(input: string): SafeHtml {
  return this.sanitizer.bypassSecurityTrustHtml(input);  // unsafe if input is user-controlled
}
<!-- VULNERABLE: [innerHTML] with unsanitized value -->
<div [innerHTML]="userInput"></div>

<!-- SECURE: use interpolation (auto-escaped) -->
<div>{{ userInput }}</div>

Ruby on Rails

<%# VULNERABLE: raw() or html_safe with user input %>
<%= raw(@user.bio) %>
<%= @user.bio.html_safe %>

<%# SECURE: default ERB escaping %>
<%= @user.bio %>

Java — JSP

<%-- VULNERABLE: scriptlet echo --%>
<p>Hello, <%= request.getParameter("name") %></p>

<%-- VULNERABLE: EL without c:out --%>
<p>Hello, ${param.name}</p>

<%-- SECURE: c:out escaping --%>
<p>Hello, <c:out value="${param.name}"/></p>

Go — html/template vs. text/template

// VULNERABLE: using text/template (no HTML escaping)
import "text/template"
tmpl := template.Must(template.New("").Parse("<h1>Hello, {{.Name}}!</h1>"))
tmpl.Execute(w, data)

// VULNERABLE: using template.HTML() cast to bypass escaping
import "html/template"
name := template.HTML(r.URL.Query().Get("name"))   // bypasses auto-escaping

// SECURE: html/template with plain string value
import "html/template"
tmpl := template.Must(template.New("").Parse("<h1>Hello, {{.Name}}!</h1>"))
tmpl.Execute(w, data)   // .Name is a plain string — auto-escaped

Execution

This skill runs in three phases using subagents. Pass the contents of sast/architecture.md to all subagents as context.

Phase 1: Find XSS Sink Sites

Launch a subagent with the following instructions:

Goal: Find every location in the codebase where data is rendered into HTML, JavaScript, or the DOM in a way that could allow script injection —

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars1.3k
CategorySecurity
Updated5mo ago
Forks65

Trust signals

98/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

1 info