Skip to content

Examples for WebApps (22)

This page lists every canonical WebApp custom rule example shipped with Escape (22 total), grouped by the vulnerability category it targets. Each example is a complete, anonymized rule that round-trips through the SaaS schema. Copy the rule: block, edit hostnames and users to your scan target, and ship.

Categories

Seeders (3)

Navigate to the login page, request a magic link, follow the link from the test inbox, and assert the dashboard rendered.

Magic-link auth is increasingly common (Notion, Slack invites, most B2B tools). The custom-rule action set supports this natively via click_mail_magic_link, which polls the configured test inbox and follows the most recent magic link.

When to use: Apps using passwordless / magic-link sign-in. Pair with a dedicated test mailbox configured in your scan authentication settings.

OWASP: A07:2021 Identification and Authentication Failures · CWE: CWE-1390

Severity rationale: INFO — building block, not a vulnerability rule.

Features used: seed, browser actions (goto, fill, click, click_mail_magic_link, wait_text)

rule:
  id: example-webapp-seeder-magic-link
  type: WEBAPP
  alert:
    name: SSO magic-link bootstrap (template)
    context: |
      Template seeder demonstrating passwordless sign-in via an
      emailed magic link.
    severity: INFO
    category: CUSTOM
  seed:
  - action: goto
    url: https://example.com/login
  - action: fill
    locator: input[name="email"]
    value: tester@example.com
  - action: click
    locator: button[type="submit"]
  - action: click_mail_magic_link
    email_address: tester.escape@scan.escape.tech
    timeout: 60
  - action: wait_text
    value: Sign out
    timeout: 10
  detect:
  - if: page_text
    contains: Sign out

References:


Two-factor (TOTP) bootstrap during sign-in

Sign in with email + password, then complete the TOTP challenge using fill_totp against the configured authenticator secret.

Apps that gate sign-in behind TOTP multi-factor frequently break headless scans. The action set ships fill_totp (HOTP-secret based) and fill_mail_totp (mailbox-delivered code) so the seeder can complete the challenge without human intervention.

When to use: Apps with TOTP MFA enabled for the test account. Configure the HOTP secret in your scan authentication settings; the seeder will compute the current 6-digit code automatically.

OWASP: A07:2021 Identification and Authentication Failures · CWE: CWE-308

Severity rationale: INFO — building block, not a vulnerability rule.

Features used: seed, browser actions (goto, fill, click, fill_totp, wait_text)

rule:
  id: example-webapp-seeder-totp
  type: WEBAPP
  alert:
    name: TOTP MFA bootstrap (template)
    context: |
      Template seeder demonstrating sign-in through a TOTP MFA
      challenge using the `fill_totp` action.
    severity: INFO
    category: CUSTOM
  seed:
  - action: goto
    url: https://example.com/login
  - action: fill
    locator: input[name="email"]
    value: tester@example.com
  - action: fill
    locator: input[name="password"]
    value: tester-password
  - action: click
    locator: button[type="submit"]
  - action: fill_totp
    locator: input[name="otp"]
    secret: JBSWY3DPEHPK3PXP
  - action: click
    locator: button[type="submit"]
  - action: wait_text
    value: Sign out
    timeout: 10
  detect:
  - if: page_text
    contains: Sign out

References:


Authenticated session bootstrap via form

A seed-only template that performs a full email + password sign-in via the browser form. Subsequent rules can stack on top with their own detect and additional seed actions.

The recurring scaffolding for any authenticated WebApp rule is the same: navigate to the login page, fill the form, submit, take a screenshot for evidence. This file documents the canonical shape so other rules can reference it via examples/webapp/_seeders/auth-bootstrap-form.yaml.

The detector here intentionally only checks that the page rendered "Sign out" — it is the cheapest signal that the session was established.

When to use: As a starting template when authoring a new authenticated WebApp rule. Copy the seed block, append your own seed actions and detector.

OWASP: N/A — building block, not a vulnerability rule · CWE: N/A

Severity rationale: INFO — this is a building block, not a vulnerability rule.

Features used: seed, browser actions (goto, fill, click, wait_text), page_text detector

rule:
  id: example-webapp-seeder-auth-form
  type: WEBAPP
  alert:
    name: Authenticated session bootstrap (template)
    context: |
      Template seeder demonstrating an email + password sign-in
      via the browser form. Stack additional seed actions and a
      real detector on top to author a complete rule.
    severity: INFO
    category: CUSTOM
  seed:
  - action: goto
    url: https://example.com/login
  - action: fill
    locator: input[name="email"]
    value: tester@example.com
  - action: fill
    locator: input[name="password"]
    value: tester-password
  - action: click
    locator: button[type="submit"]
  - action: wait_text
    value: Sign out
    timeout: 10
  detect:
  - if: page_text
    contains: Sign out

References:


Access Control (3)

Anonymous /admin route reachable in browser

Navigate to /admin and assert the page loads with admin markers — the panel is reachable without authentication.

The browser-side counterpart to examples/api/access_control/public-admin-route.yaml. The seeder hits /admin directly. The detector requires a 200 status AND a page text marker (Sign out, User Management, Admin panel) so a generic redirect to a login page does not fire.

When to use: Any web app with an admin surface. Combine with the API rule for full coverage.

OWASP: A01:2021 Broken Access Control · CWE: CWE-862

Severity rationale: HIGH — anonymous admin access is reliably exploitable.

Features used: seed, detect, page_status_code, page_text contains, logical or

rule:
  id: example-webapp-admin-route-anonymous
  type: WEBAPP
  alert:
    name: Anonymous /admin route reachable
    context: |
      `/admin` returned a 200 page containing administrative UI
      markers without requiring authentication.
    severity: HIGH
    category: ACCESS_CONTROL
  seed:
  - action: goto
    url: https://example.com/admin
  detect:
  - if: page_status_code
    is: 200
  - if: or
    or:
    - if: page_text
      contains: Sign out
    - if: page_text
      contains: User Management
    - if: page_text
      contains: Admin panel

References:


Plaintext credentials persisted in localStorage

Sign in via the form, then assert no localStorage entry contains the typed password — credentials must never be persisted client-side.

Even short-lived persistence of plaintext credentials in localStorage is a finding: any XSS on any page of the same origin can read them.

The seeder logs in with a marker password, then the js_assertion detector iterates localStorage keys and returns true if the marker substring appears anywhere.

When to use: Any single-page app that exposes a login form. Adjust the selectors and marker password to your form layout.

OWASP: A02:2021 Cryptographic Failures · CWE: CWE-312

Severity rationale: HIGH — combined with any same-origin XSS, this is direct credential theft.

Features used: seed, detect, browser actions (goto, fill, click), js_assertion

rule:
  id: example-webapp-localstorage-credentials
  type: WEBAPP
  alert:
    name: Plaintext credentials persisted in localStorage
    context: |
      The login form persisted the typed password in
      `localStorage`, exposing credentials to any same-origin XSS.
    severity: HIGH
    category: ACCESS_CONTROL
  seed:
  - action: goto
    url: https://example.com/login
  - action: fill
    locator: input[name="email"]
    value: tester@example.com
  - action: fill
    locator: input[name="password"]
    value: escape-marker-password-9213
  - action: click
    locator: button[type="submit"]
  detect:
  - if: js_assertion
    command: |
      return Object.keys(localStorage).some(k => (localStorage.getItem(k) || '').includes('escape-marker-password-9213'));

References:


Sensitive file exposure (.env, .git, backups) reachable in browser

Navigate to a curated list of sensitive paths (.env, .git/config, backup.zip) and alert if any of them returns a 200 page whose DOM contains an OR-list of well-known credential markers.

The webapp counterpart to the API exposed-file rules. Adapted from a real anonymized customer rule (see CSV column 9 for the JSON-encoded pattern) — the seeder loops over a variety of sensitive paths and the detector ORs the most common high-signal credential markers.

When to use: Any web property that may host arbitrary static files (CMS deployments, marketing sites, single-page apps with a static bucket). Run after every deployment.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-200

Severity rationale: HIGH — the markers chosen are extremely high-signal and a hit almost always means a leaked credential.

Features used: seed, detect, browser action goto, page_status_code, page_text contains, logical and, logical or

rule:
  id: example-webapp-sensitive-file-exposure
  type: WEBAPP
  alert:
    name: Sensitive file reachable in browser
    context: |
      A request to a known sensitive path (`.env`, `.git/config`,
      backup archive, ...) returned a 200 page containing a
      credential / configuration marker.
    severity: HIGH
    category: ACCESS_CONTROL
  seed:
  - action: goto
    url: https://example.com/.env
    timeout: 10
  - action: goto
    url: https://example.com/.env.local
    timeout: 10
  - action: goto
    url: https://example.com/.env.production
    timeout: 10
  - action: goto
    url: https://example.com/.git/config
    timeout: 10
  - action: goto
    url: https://example.com/backup.zip
    timeout: 10
  detect:
  - if: and
    and:
    - if: page_status_code
      is: 200
    - if: or
      or:
      - if: page_text
        contains: DB_PASSWORD
      - if: page_text
        contains: DATABASE_URL
      - if: page_text
        contains: AWS_ACCESS_KEY_ID
      - if: page_text
        contains: '[core]'
      - if: page_text
        contains: PRIVATE KEY

References:


Configuration (3)

Missing Content-Security-Policy header

Navigate to the home page and alert if the response did not carry a Content-Security-Policy header — the browser is left to enforce nothing beyond defaults.

A meaningful CSP is the single highest-leverage browser-side XSS control: it blocks inline scripts, third-party JS sources, and prevents eval-style sinks unless explicitly allowlisted. Pages without one fall back to the default permissive policy.

The detector triggers on any successful HTML page and asserts the Content-Security-Policy header is present (any value). Tighten the assertion if your minimum required policy includes specific directives.

When to use: Any web app rendering HTML. CSP is essentially mandatory on customer-facing properties.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-1021

Severity rationale: MEDIUM — CSP absence does not directly compromise the page, but its presence is the strongest browser-side XSS mitigation available.

Features used: seed, detect, browser action goto, header detector, logical not

rule:
  id: example-webapp-missing-csp-header
  type: WEBAPP
  alert:
    name: Missing Content-Security-Policy header
    context: |
      The home page response did not carry a
      `Content-Security-Policy` header, leaving the browser to
      enforce no script-source policy beyond defaults.
    severity: MEDIUM
    category: CONFIGURATION
  seed:
  - action: goto
    url: https://example.com/
  detect:
  - if: not
    not:
      if: header
      key:
        is: Content-Security-Policy

References:


Non-production build exposed in production

Navigate to the home page and alert when the rendered DOM contains a staging / dev / qa build banner — a non-production build was promoted to the production hostname.

Non-production builds typically include verbose logging, debug routes, mock authentication, and feature flags that bypass real authorization. When a staging build accidentally lands on production.example.com, every weakness becomes internet-facing.

The detector OR's three common banner strings ("STAGING BUILD", "DEV BUILD", "QA ENVIRONMENT") that build pipelines often inject into the page header. Tailor to your build's actual marker.

When to use: Any web app with a staging/QA pipeline. Run after every deploy as a deployment-correctness gate.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-1188

Severity rationale: HIGH — non-prod builds bring debug routes and weakened auth that typically chain into deeper compromise.

Features used: seed, detect, page_text, logical or

rule:
  id: example-webapp-non-prod-build-banner
  type: WEBAPP
  alert:
    name: Non-production build banner on production host
    context: |
      The production page rendered a staging / dev / QA build
      banner, indicating a non-production build was promoted to
      the production hostname.
    severity: HIGH
    category: CONFIGURATION
  seed:
  - action: goto
    url: https://example.com/
  detect:
  - if: or
    or:
    - if: page_text
      contains: STAGING BUILD
    - if: page_text
      contains: DEV BUILD
    - if: page_text
      contains: QA ENVIRONMENT

References:


Subdomain takeover via parking-page marker

Visit a subdomain and flag if the page renders a known third-party "no such app" or parking marker, indicating an unclaimed external resource that an attacker could register.

Subdomain takeover happens when a CNAME points to a third-party service (S3, Heroku, GitHub Pages, Azure, …) that no longer hosts a resource for that name. An attacker who registers the resource on the third-party service inherits the subdomain and can host arbitrary content under your origin.

The deterministic detection is to fetch the subdomain and look for the platform-specific "this resource is not configured" marker. The marker list below covers the most common providers; extend or with the providers you actually use.

When to use: Run against every subdomain you own. Particularly important after decommissioning a marketing campaign, microsite, or staging environment, when CNAME records often outlive the underlying resource.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-1104

Severity rationale: HIGH — successful takeover lets an attacker host phishing or malware pages under a trusted hostname, defeating SPF / DKIM / DMARC trust chains and bypassing user suspicion.

Features used: seed, detect, browser action goto, page_text detector, logical or

rule:
  id: example-webapp-subdomain-takeover-marker
  type: WEBAPP
  alert:
    name: Possible subdomain takeover
    context: |
      The subdomain renders a known third-party parking marker,
      indicating an unclaimed external resource that an attacker could
      register and use to host arbitrary content under your origin.
    severity: HIGH
    category: CONFIGURATION
  seed:
  - action: goto
    url: https://example.com/
  detect:
  - if: or
    or:
    - if: page_text
      contains: NoSuchBucket
    - if: page_text
      contains: There is no app configured at that hostname
    - if: page_text
      contains: There isn't a GitHub Pages site here
    - if: page_text
      contains: The specified bucket does not exist
    - if: page_text
      contains: Sorry, this shop is currently unavailable

References:


Information Disclosure (4)

Spring Boot actuator UI publicly exposed

Navigate to /actuator and assert the page returns a successful response containing the health index — the actuator landing page is reachable without authentication.

The Spring Boot actuator landing page lists every enabled endpoint. Confirming the index is reachable is enough to know that more dangerous endpoints (/actuator/env, /actuator/heapdump) are likely also accessible.

See examples/api/information_disclosure/spring-boot-actuator-env.yaml for the API-side counterpart that probes the heavy endpoints directly.

When to use: Any Spring Boot app reachable via a browser. Combine with the API rule for full coverage.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-200

Severity rationale: MEDIUM as standalone exposure (the index alone leaks little); HIGH once paired with the env / heapdump probes.

Features used: seed, detect, page_status_code, page_text contains

rule:
  id: example-webapp-exposed-actuator-ui
  type: WEBAPP
  alert:
    name: Spring Boot actuator UI exposed
    context: |
      The `/actuator` landing page is publicly reachable and lists
      enabled actuator endpoints.
    severity: MEDIUM
    category: INFORMATION_DISCLOSURE
  seed:
  - action: goto
    url: https://example.com/actuator
  detect:
  - if: page_status_code
    is: 200
  - if: page_text
    contains: health

References:


OAuth client secret leaked via source map

Navigate to a JavaScript bundle source map and alert when the body contains client_secret or clientSecret — the OAuth client secret should never reach the browser.

OAuth flows that put client_secret in front-end source are misconfigured by definition: confidential clients run only in server-side processes. When the secret leaks via a source map, any attacker can impersonate the OAuth client and request tokens with its identity.

When to use: Any front-end that integrates a third-party OAuth provider (Google, GitHub, LinkedIn, Slack, Microsoft). Adjust the seed URLs to your bundle layout.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-540

Severity rationale: HIGH — leaked client secrets enable token impersonation and sometimes phishing pages that look identical to the legitimate OAuth consent screen.

Features used: seed, detect, browser action goto, page_text contains, logical or

rule:
  id: example-webapp-source-map-oauth-secret
  type: WEBAPP
  alert:
    name: OAuth client secret leaked via source map
    context: |
      A production source map contains a `client_secret` value,
      indicating the OAuth confidential client secret has been
      shipped to the browser.
    severity: HIGH
    category: INFORMATION_DISCLOSURE
  seed:
  - action: goto
    url: https://example.com/static/js/main.js.map
  - action: goto
    url: https://example.com/assets/index.js.map
  detect:
  - if: or
    or:
    - if: page_text
      contains: client_secret
    - if: page_text
      contains: clientSecret

References:


Firebase config leaked via source map

Probe /static/js/main.js.map and assert the body contains firebaseConfig — production source maps are downloadable and leak the embedded Firebase configuration.

Source maps deployed to production give attackers your pre-minification source. Common leaks include:

  • firebaseConfig (API key, project id, app id) — used to brute-force project paths and email enumeration via the Identity Toolkit;
  • hardcoded OAuth client secrets;
  • private API tokens passed to JS at build time.

The detector requires both a successful response AND a body containing the canonical firebaseConfig marker.

When to use: Any single-page app built with React / Vue / Angular and deployed via a static-site pipeline. Tighten the seed paths to the actual bundle filenames your build emits.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-540

Severity rationale: HIGH — Firebase project credentials are reliably weaponized into account enumeration and sometimes data exfiltration via over-permissive Firestore rules.

Features used: seed, detect, browser action goto, page_text contains

rule:
  id: example-webapp-source-map-firebase-config
  type: WEBAPP
  alert:
    name: Firebase config leaked via source map
    context: |
      A production JavaScript source map exposed the embedded
      `firebaseConfig` object, leaking the project's API key and
      identifier.
    severity: HIGH
    category: INFORMATION_DISCLOSURE
  seed:
  - action: goto
    url: https://example.com/static/js/main.js.map
  detect:
  - if: page_text
    contains: firebaseConfig
  - if: page_text
    contains: apiKey

References:


Verbose error page leaks server paths and stack trace

Navigate to a non-existent path that triggers a server error and alert if the page text exposes a stack trace, framework banner, or absolute filesystem path.

Error pages are an easy-but-useful information disclosure: they leak the framework (Werkzeug, Whitelabel Error Page, RailsErrorBacktrace), absolute paths (/var/www/app/...), and sometimes the exact line of code that failed.

The seeder asks for a clearly-non-existent path to force an error. The detector OR's three high-confidence markers.

When to use: Any web application. Particularly important on staging deployments accidentally promoted to production.

OWASP: A09:2021 Security Logging and Monitoring Failures · CWE: CWE-209

Severity rationale: LOW — exposure aids reconnaissance but is rarely directly exploitable.

Features used: seed, detect, browser action goto, page_text contains, logical or

rule:
  id: example-webapp-verbose-error-stack-trace
  type: WEBAPP
  alert:
    name: Verbose error page exposes server internals
    context: |
      A non-existent path returned a verbose error page exposing
      a framework banner, stack trace, or absolute filesystem
      path.
    severity: LOW
    category: INFORMATION_DISCLOSURE
  seed:
  - action: goto
    url: https://example.com/__escape_does_not_exist__
  detect:
  - if: or
    or:
    - if: page_text
      contains: Whitelabel Error Page
    - if: page_text
      contains: Werkzeug
    - if: page_text
      contains: Traceback (most recent call last)
    - if: page_text
      contains: at com.

References:


Injection (6)

HTML injection in search input field

Submit <u>escape-html</u> in a search input and assert a rendered <u> element appears in the DOM via a CSS selector — proving raw HTML reaches the page.

HTML injection is the precursor to XSS: the page renders user-supplied tags but the specific tag does not execute script. It still enables defacement, phishing overlays, and sometimes link injection that bypasses link-only filters.

The deterministic test searches for the injected <u> element via the page_selector detector. A match means the tag was parsed by the browser as real HTML.

When to use: Pages with search forms or any user-supplied text rendered inline. Pair with dialog-popup-xss.yaml to detect actual script execution as a follow-up.

OWASP: A03:2021 Injection · CWE: CWE-79

Severity rationale: MEDIUM — direct injection of arbitrary HTML, but limited to non-script tags here.

Features used: seed, detect, browser actions (goto, fill, click), page_selector detector

rule:
  id: example-webapp-html-injection-search
  type: WEBAPP
  alert:
    name: HTML injection in search input
    context: |
      The search input rendered an injected `<u>` element verbatim
      in the DOM, indicating user-supplied HTML is parsed and
      attached to the page.
    severity: MEDIUM
    category: INJECTION
  seed:
  - action: goto
    url: https://example.com/
  - action: fill
    locator: input[type="search"]
    value: <u>escape-html</u>
  - action: click
    locator: button[type="submit"]
  detect:
  - if: page_selector
    contains: u
  - if: page_text
    contains: escape-html

References:


DOM XSS via document.cookie reflection

Navigate to a page with an XSS payload in the URL fragment, then assert via JS that document.cookie was modified — proving the payload executed in the DOM.

DOM XSS is rendering-layer XSS that the server never sees: the page reads location.hash (or another DOM source) and writes it to innerHTML without sanitization. Server-side reflection detection misses this entirely.

The deterministic test loads a URL with an attacker-controlled fragment that runs document.cookie='escape-dom-xss=1'. The js_assertion detector returns true if the cookie was set, proving the script executed in the DOM context.

When to use: Any single-page app or page that consumes URL fragments. The technique works equally for query parameter sinks if you adjust the URL accordingly.

OWASP: A03:2021 Injection (DOM XSS) · CWE: CWE-79

Severity rationale: HIGH — same impact as reflected XSS; harder to detect with server-side scanning, which is why this rule matters.

Features used: seed, detect, browser action goto, js_assertion detector

rule:
  id: example-webapp-dom-xss-cookie-marker
  type: WEBAPP
  alert:
    name: DOM XSS via URL fragment
    context: |
      The page set a marker cookie when navigated to with an XSS
      payload in the URL fragment, indicating a DOM-based XSS sink.
    severity: HIGH
    category: INJECTION
  seed:
  - action: goto
    url: https://example.com/page#<img src=x onerror=document.cookie='escape-dom-xss=1'>
  detect:
  - if: js_assertion
    command: return document.cookie.indexOf('escape-dom-xss=1') !== -1;

References:


XSS detected via alert() dialog popup

Navigate with <script>alert("escape-xss")</script> in the URL and assert a dialog appeared whose message contains the marker — fires regardless of where the payload was reflected.

The dialog detector is the most ergonomic XSS proof in headless-browser scanning: it captures any alert(), confirm(), or prompt() dialog the page emits during navigation. If the captured message contains your marker, you have execution.

Unlike the page_text detector, this rule fires regardless of where in the DOM the payload was rendered (SVG, attribute handler, embedded iframe, ...).

When to use: Any web target. This is the closest thing to a universal XSS test for headless-browser scanning.

OWASP: A03:2021 Injection (XSS) · CWE: CWE-79

Severity rationale: HIGH — direct script execution.

Features used: seed, detect, browser action goto, dialog.message detector

rule:
  id: example-webapp-dialog-popup-xss
  type: WEBAPP
  alert:
    name: XSS detected via dialog popup
    context: |
      The page emitted an `alert()` dialog containing the unique
      marker `escape-xss`, proving script execution.
    severity: HIGH
    category: INJECTION
  seed:
  - action: goto
    url: https://example.com/?q=%3Cscript%3Ealert(%22escape-xss%22)%3C%2Fscript%3E
  detect:
  - if: dialog.message
    contains: escape-xss

References:


Reflected XSS in search page

Navigate to a search page with an XSS payload in the query string and flag if the payload is rendered verbatim into the page text — a deterministic signal of reflected XSS.

Reflected XSS happens when user input is echoed into the HTML response without escaping. The cleanest deterministic test is to submit a uniquely-identifiable payload via the URL and look for that exact string in the rendered page, which proves both reflection and lack of escaping.

The seeder navigates the headless browser to a search URL containing <script>console.log("xss")</script>. The detector asserts that string is present in the rendered page text. If it is, the payload survived the server-side escape filter.

When to use: Public search / filter pages that take user-controlled query parameters and reflect them in the rendered HTML. Adjust the URL, the parameter name, and the marker payload to your target.

OWASP: A03:2021 Injection (XSS) · CWE: CWE-79

Severity rationale: HIGH — reflected XSS allows session hijacking, account takeover, and credential theft against any authenticated visitor who follows a crafted link.

Features used: seed, detect, browser action goto, page_text detector

rule:
  id: example-webapp-reflected-xss-search
  type: WEBAPP
  alert:
    name: Reflected XSS in search query
    context: |
      A unique XSS payload submitted via the search query parameter was
      rendered verbatim in the page HTML, indicating reflected
      cross-site scripting.
    severity: HIGH
    category: INJECTION
  seed:
  - action: goto
    url: https://example.com/search?q=%3Cscript%3Econsole.log(%22xss%22)%3C%2Fscript%3E
  detect:
  - if: page_text
    contains: <script>console.log("xss")</script>

References:


Stored XSS — payload survives a form submit + reload

Submit an XSS payload through a form, navigate back to the page, and alert if the rendered DOM contains the payload verbatim — a deterministic stored-XSS test.

Stored XSS is more dangerous than reflected XSS because it persists for every visitor. The deterministic test:

  1. Navigate to the form page.
  2. Fill the textarea with a unique XSS payload.
  3. Click submit.
  4. Navigate back to the same page (forcing a fresh fetch).
  5. Assert the payload appears verbatim in the page text.

A 1:1 reflection on a fresh load proves the payload was stored server-side and is rendered without escaping.

When to use: Any web app with user-generated content (comments, profiles, titles, descriptions, board names). Adapt the locator selectors and submit URL to your form layout.

OWASP: A03:2021 Injection (XSS) · CWE: CWE-79

Severity rationale: HIGH — stored XSS hits every visitor of the affected page; it is one of the most reliably weaponized web vulnerabilities.

Features used: seed, detect, browser actions (goto, fill, click), page_text detector

rule:
  id: example-webapp-stored-xss-form-roundtrip
  type: WEBAPP
  alert:
    name: Stored XSS in user-generated form field
    context: |
      A unique XSS payload submitted through a form was rendered
      verbatim in the page after a fresh navigation, indicating
      the payload was stored server-side and the rendering layer
      does not escape user input.
    severity: HIGH
    category: INJECTION
  seed:
  - action: goto
    url: https://example.com/comments/new
  - action: fill
    locator: textarea[name="comment"]
    value: <script>console.log("escape-stored-xss")</script>
  - action: click
    locator: button[type="submit"]
  - action: goto
    url: https://example.com/comments
  detect:
  - if: page_text
    contains: <script>console.log("escape-stored-xss")</script>

References:


ASP.NET stack trace via malformed input

Navigate to a search-style endpoint with a malformed URL parameter and assert the rendered page exposes an ASP.NET stack trace, indicating customErrors is not configured.

ASP.NET applications with customErrors mode="Off" (or default in development) render a yellow stack-trace page on unhandled exceptions. The page exposes:

  • the .NET version,
  • file paths,
  • the request handler chain,
  • frequently DB connection strings via SqlException.

The deterministic test fetches /?id=null and looks for the canonical Server Error in '/' Application heading.

When to use: Any ASP.NET (Framework or Core) application. Particularly important in production after refactors that may have re-enabled developer mode.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-209

Severity rationale: LOW — exposure aids reconnaissance; elevate when stack traces contain credentials.

Features used: seed, detect, browser action goto, page_text contains

rule:
  id: example-webapp-asp-net-stack-trace
  type: WEBAPP
  alert:
    name: ASP.NET stack trace exposed
    context: |
      A malformed request rendered an ASP.NET error page exposing
      stack trace, server paths, and framework version.
    severity: LOW
    category: INJECTION
  seed:
  - action: goto
    url: https://example.com/?id=null
  detect:
  - if: page_text
    contains: Server Error in '/' Application
  - if: page_text
    contains: 'Stack Trace:'

References:


Request Forgery (1)

POST form rendered without CSRF token

Navigate to a form page and assert at least one <form method="post"> carries a hidden CSRF token input — its absence means the form is exploitable cross-origin.

Server-rendered apps embed CSRF tokens as hidden inputs in <form method="post">. The deterministic test is to load a known form page and use js_assertion to scan all POST forms for at least one hidden input whose name implies a CSRF token.

The detector returns true when no form passes the check — so the rule alerts on the absence.

When to use: Any server-rendered web app with <form method="post"> POSTs. SPAs that POST via fetch use a different mechanism — see the API rule examples/api/access_control/missing-csrf-token-on-state-change.yaml.

OWASP: A01:2021 Broken Access Control (CSRF) · CWE: CWE-352

Severity rationale: MEDIUM — exploitation requires luring a logged-in victim, but the impact is direct.

Features used: seed, detect, js_assertion

rule:
  id: example-webapp-csrf-on-post-form
  type: WEBAPP
  alert:
    name: POST form missing CSRF token
    context: |
      A POST form on the page does not carry a hidden CSRF token
      input, leaving it exploitable from any origin a logged-in
      victim visits.
    severity: MEDIUM
    category: REQUEST_FORGERY
  seed:
  - action: goto
    url: https://example.com/account/settings
  detect:
  - if: js_assertion
    command: |
      return Array.from(document.querySelectorAll('form[method="post" i]'))
        .some(f => !f.querySelector('input[type="hidden"][name*="csrf" i],input[type="hidden"][name*="token" i]'));

References:


Sensitive Data (2)

Session token persisted in browser URL

Sign in, navigate to the dashboard, and alert via js_assertion if window.location.search contains a JWT-shaped value — the session token is being kept in the URL.

Tokens in URLs are persisted in browser history, leak via Referer to third-party scripts on the page, and are recorded by intermediate proxies. They belong in Authorization headers or HttpOnly cookies.

The deterministic test logs in via the form, navigates forward, and asserts the URL search parameters contain no JWT-shaped value.

When to use: Any web app where the auth flow may fall back to URL-encoded tokens (legacy SSO callbacks, magic-link flows).

OWASP: A02:2021 Cryptographic Failures · CWE: CWE-598

Severity rationale: MEDIUM — exposure surface is broad but exploitation requires reading the leaked logs / Referer.

Features used: seed, detect, browser actions (goto, fill, click), js_assertion

rule:
  id: example-webapp-session-token-in-url
  type: WEBAPP
  alert:
    name: Session token persisted in URL
    context: |
      After login, `window.location.search` contains a JWT-shaped
      value, indicating the session token is being persisted in
      the URL.
    severity: MEDIUM
    category: SENSITIVE_DATA
  seed:
  - action: goto
    url: https://example.com/login
  - action: fill
    locator: input[name="email"]
    value: tester@example.com
  - action: fill
    locator: input[name="password"]
    value: tester-password
  - action: click
    locator: button[type="submit"]
  detect:
  - if: js_assertion
    command: |
      return /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/.test(window.location.search);

References:


PII (email addresses) leaked in unauthenticated public page

Navigate to a public listing page and assert via js_assertion that the rendered DOM contains no email addresses — public pages should not expose user emails.

The webapp counterpart to examples/api/sensitive_data/pii-email-in-response.yaml. Public marketing / listing pages frequently leak member emails through Mongoose / Sequelize default serializers when the backend forgets the field allow-list.

The detector returns true when at least one email-shaped string appears in the page text.

When to use: Any public-facing page that renders user-generated content (forums, member directories, comment threads, profiles).

OWASP: A04:2021 Insecure Design · CWE: CWE-359

Severity rationale: MEDIUM — leaked email lists fuel phishing and credential stuffing.

Features used: seed, detect, js_assertion

rule:
  id: example-webapp-pii-emails-in-dom
  type: WEBAPP
  alert:
    name: User emails leaked in public page
    context: |
      The public page contains email addresses in its rendered DOM,
      indicating user PII is exposed to anonymous visitors.
    severity: MEDIUM
    category: SENSITIVE_DATA
  seed:
  - action: goto
    url: https://example.com/members
  detect:
  - if: js_assertion
    command: |
      return /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/.test(document.body.innerText);

References: