How It Works
Core Concepts
States and the State Graph
A State is a DOM snapshot identified bySHA256(strippedDOM)[:16]. The State Graph is a directed graph where nodes are states and edges are actions that caused transitions. Navigation between states uses Dijkstra’s shortest path (with Yen’s K-shortest as fallback).
Near-duplicate detection uses normalized Levenshtein distance (threshold: 10%). For large DOMs (>10K chars), a sampling-based distance is used for performance.
Actions and Candidate Elements
Candidate clickable elements are discovered via CSS selectors (a, button, [onclick], [role=button], input[type=submit], framework-specific bindings like [ng-click], [v-on:click], etc.). Each candidate becomes an Eventable (graph edge) once fired, linking a source state to a target state with an event type (click, hover, enter).
Fragments (Visual Page Segmentation)
Pages are decomposed into Fragments - DOM regions identified by XPath, bounding box, subtree size, and content hash. Two modes:- Landmark (default): fast DOM-based extraction
- VIPS: vision-based page segmentation with multi-pass decreasing thresholds
Form Handling
The Form Handler detects and fills forms with smart value generation:- Field-name-aware values (email, password, phone, URL, etc.)
- Constraint-aware generation (respects
pattern,min/max,minlength/maxlength) - Pairwise fallback when filling all inputs at once fails
- File upload support with type-aware file selection
Submission and link-priming crawlers
Filling a form only exercises its inputs — the endpoints behind those forms and links stay invisible until something submits them. After a page settles, the crawler runs a set of bounded, default-on priming passes so those routes enter discovery:- GET-form submission (
SubmitGetForms) — synthesizes the submit URL for each filled GET form and fetches it, so the resulting query-string endpoints are captured as traffic. - Same-origin POST-form submission (
SubmitPostForms) — exercises JS-driven POST endpoints (a common home for XXE and body/cookie SQLi) by triggering their submit handlers. Both submission passes shareSubmitFormMaxVariants, a cap on how many distinct submit variants are fetched per page. - Anchor-link priming (
AnchorLinkPriming, capped byAnchorLinkMaxAssets) — on each settled page, fetches same-origin anchor-link targets so their routes are pulled into discovery even when nothing clicks them. - DOM-quiescence settle (
WaitDOMQuiescence) — before harvesting a page, the settle waits for the DOM to stop mutating, so late-rendered forms and links are seen instead of being missed mid-render.
--no-forms flag that disables form filling. Destructive forms and paths are deliberately skipped — delete/logout actions, _method-override forms, and similar state-changing submits are never fetched.
DOM-based XSS (Browser-Confirmed)
As the final step of the crawl, the browser crawler runs a browser-confirmed DOM-based XSS probe over the client-side routes it already visited. It reuses the live browser and the routes discovered during the crawl rather than sending a separate fuzzing pass:- A marker-reflection gate first checks whether a route reflects an injected marker into the client-rendered DOM.
- Only then does an execution canary run — the payload must actually fire in the browser (a real script execution, not just a textual reflection) before anything is reported, which keeps this free of the reflection false positives that plague raw-response DOM-XSS heuristics.
spider-dom-xss), with severity reported by the browser-accurate probe. The probe is bounded — at most ~16 confirmations under a ~45-second budget — so it corroborates real DOM-XSS on reflected client routes without ever dominating the crawl.
Default-credential login attempts
To reach areas hidden behind a login, the crawler can try a short list of common default credentials against a confirmed local login form so the browser session stays authenticated and the crawl continues into the now-unlocked area. This pass is discovery-focused, not a brute-force, and is heavily constrained:- It runs only against a form the crawler has confirmed is a local login (a single password field + an identity field + a submit, posting to an in-scope host — never an external IdP).
- It tries a small, documented default list (
admin:admin,admin:123456, …) plus any identity the crawl registered earlier. The identity field’s type is respected (email inputs get email-style defaults). - It is negative-control gated: an improbable random pair must be rejected first, or the whole spray is abandoned (so a form that “accepts” anything can’t produce a false “logged in”).
- It is single-flighted per host and capped (≤ 10 attempts, never a wordlist), so it cannot lock accounts.
- No finding is emitted — the attempts are captured as ordinary traffic and any unlocked pages simply become more crawlable surface.
balanced uses the minimal list, deep uses the full documented list. An ordinary crawl leaves it off, keeping active login attempts explicit.
Session carry-forward
While crawling, the browser often clears an interstitial WAF/bot challenge (a JS or cookie challenge) or picks up session cookies. Those cleared cookies and the browser’s User-Agent are carried forward into the subsequent discovery and active-scanning phases, so content discovery and module probes reuse the already-cleared session instead of tripping the challenge again.- On by default whenever
--spiderruns; scoped per host to the same host the browser visited. Disable with--no-carry-browser-session. - Respects your own
-H/--headeroverrides — an explicitly supplied header always wins. - The User-Agent is pinned from the browser only when you haven’t set a non-default UA yourself.
Exploration Strategies
Browser Management
- Embedded binaries: ships Chromium (macOS/Windows/Linux) and ungoogled-Chromium (Linux). Extracted on first run, cached by version.
- Headless mode: uses
headless=newwhen extensions are loaded (supports Chrome extensions unlike legacy headless). - Extensions: loaded via
--load-extension(e.g., uBlock Origin Lite for ad blocking during crawl). - Security flags disabled for crawling:
--disable-web-security,--ignore-certificate-errors,--allow-running-insecure-content. - Pool: multiple browser instances with round-robin selection.
Network Capture
Traffic is captured at the browser level (not page level) via CDP events, covering all tabs, popups, and iframes:NetworkRequestWillBeSent→ record requestNetworkResponseReceived→ record response headersNetworkLoadingFinished→ fetch response body
httpmsg.HttpRequestResponse and saved via the RecordSaver interface with source "spidering".
Termination Conditions
The crawl stops when any of these are met:- Maximum states discovered
- Maximum duration elapsed
- Maximum crawl depth reached
- Maximum consecutive failures
- No more candidate actions to explore
- Context cancellation
Entry Point
SpiderResult with: states discovered, actions executed/failed, forms submitted, duration, and records saved.