Repository navigation
Route Exposure Scan
📖 Canonical version: read this page on the official docs site — https://www.redamon.org/docs/route-exposure-scan. The GitHub wiki is a mirror.
The Route Exposure scan is RedAmon's active check for routes that answer without authentication and files a web server should never serve. It requests every route the earlier recon stages found, plus routes it generates next to them, once without credentials and once logged in where your authentication profile covers the host, then compares the two answers.
It runs in GROUP 6 Phase A, beside Nuclei, GraphQL, Subdomain Takeover, VHost & SNI, Web Cache Poisoning and Origin Discovery, whenever active scanning runs. Active, and off by default. Enable it in the project settings, tab Vulnerability Scanning, card Route Exposure.
- Why two halves
- What it finds
- How a run works
- Settings
- Safety
- Partial recon
- What the agent does with the results
- Jev decisions
- Where the results show
- Related
A route that answers 200 without a session is not, by itself, a vulnerability: a help page, a
status endpoint or a product catalogue is public by design. Telling the two apart needs
judgement, and some exposures (a CORS hole, a page hidden only by the browser) need a browser or
a second request to prove. So the work is split:
| Half | Where | What it does |
|---|---|---|
| Recon (this scan) | GROUP 6 Phase A, deterministic | Requests the routes, compares the anonymous and logged-in answers, proves what a signature can prove on its own (an exposed .git, an .env, a debug console), and writes the rest as leads
|
| Agent | the Access Control, Path Traversal and RCE skills | Confirms or rejects each lead with a minimal, read-only proof, and records the proof against the finding |
A finding is proven by its own content and carries its real severity. A lead carries
needs_agent_confirmation: true and severity info: it sits at the bottom of the
Priority Board (T4), and reports and Insights leave it out, until the agent
confirms it. Then it moves to T1 with a CONFIRMS edge from the agent's proof.
| What | How it is found | Written as |
|---|---|---|
| An internal, admin, export or debug API that answers anonymously | the route answers 2xx without credentials although something says it should not: the OpenAPI document declares security, its siblings answer 401/403, a newer version is locked, the logged-in answer varies on the session, or a discovery tool saw it denied | a lead (anonymous_access) |
| Too much data in a response | sensitive key names (password_hash, reset_token, apiKey, role...) or three or more other people's e-mails or phones, in an anonymous OR a logged-in answer |
a lead (sensitive_data) |
| An older API version left open |
/api/v1/x answers anonymously while /api/v2/x is locked |
a lead, route class old_version
|
| Uploaded files open to anyone | a file under an uploads folder answers anonymously | a lead, route class uploaded_content
|
| An API hidden only by the browser | the same JavaScript bundle defines a client-side router page and calls the API under that page's first segment (/admin/dashboard → /api/admin/stats), and the API answers anonymously |
a lead, route class client_gated
|
| A credentialed CORS hole | a personalised logged-in answer that carries a leak signal echoes a hostile Origin (reflected, null, suffix or prefix variants) with Access-Control-Allow-Credentials: true
|
a lead (cors_misconfig), with cors_variants
|
| Logged-in content a cache serves to others | a personalised logged-in answer is marked cacheable (public, s-maxage or a plain max-age), a cache layer answers in front of it, and nothing varies the cache on the session |
a lead (auth_cacheable) |
| Exposed version control and env files |
.git/HEAD, .git/config, .svn, .hg, .env*, web.config with their real content; for .git a bounded walk from HEAD to its ref and commit object proves a real repository (git_walk_proof: head>ref>commit) without ever fetching a tree or a file |
a finding |
| Backup and temporary files |
name.bak, name~, .name.swp, backup.zip, db.sql... whose first kilobyte is an archive, a dump or PHP source |
a finding |
| Debug consoles and endpoints | a Werkzeug debugger, Laravel Ignition with command execution, a Symfony profiler, Spring actuator env and heapdump, Go pprof, Apache server-status |
a finding |
| Detailed error pages | Django DEBUG = True, Rails, ASP.NET and Spring stack traces, phpinfo() (a real one answers 500, and is still found) |
a finding |
| Route catalogs | actuator mappings, the Rails route table, an API specification that lists internal routes | a finding |
| Secrets in a response | the JS Recon secret matcher fires on an anonymous answer | a finding |
Each result is one Vulnerability {source: 'route_exposure'} node, keyed by its URL and its
exposure_kind:
exposure_kind |
vulnerability_type |
Severity |
|---|---|---|
anonymous_access |
unauthenticated_endpoint |
info (lead) |
sensitive_data |
sensitive_data_exposure |
info (lead) |
cors_misconfig |
cors_credentialed_origin_reflection |
info (lead) |
auth_cacheable |
authenticated_content_cacheable |
info (lead) |
vcs_exposure |
exposed_version_control |
high |
env_file |
exposed_env_file |
high |
debug_console |
exposed_debug_console |
high |
config_file |
exposed_config_file |
medium |
backup_file |
exposed_backup_file |
medium |
debug_endpoint |
exposed_debug_endpoint |
medium (high for actuator env and heapdump) |
secret_in_response |
secret_exposure |
medium |
debug_page |
debug_error_page |
low (phpinfo medium) |
route_catalog |
exposed_route_catalog |
low |
Every result also carries a route_class, the scan's reading of what kind of route it is. It
decides how the agent judges a lead, and it never changes the node's identity:
route_class |
Meaning |
|---|---|
old_version |
an older API version the scan generated next to a newer one |
client_gated |
an API a client-side router page calls; only the browser hides it |
debug_management |
a debug, health, metrics, actuator or console path |
admin_function |
an admin, management or dashboard path (or category) |
data_export |
an export, report, dump or download path |
uploaded_content |
a file under an uploads, attachments or documents folder |
internal_api |
an internal, private or system path, or any API route |
unknown |
none of the above |
A lead says why the scan expected a login, in auth_expected_by. These are the reasons the
route was ranked high, and the agent reads them to judge the lead:
| Hint | Means |
|---|---|
openapi |
the site's OpenAPI document declares security on this operation |
openapi_tag |
the operation is tagged internal or admin in that document |
declared_only |
only the OpenAPI document names this route; nothing links to it |
seen_denied |
a discovery tool (crawler, fuzzer) saw this route answer 401 or 403 |
path_word |
a path segment says privileged (internal, private, admin, actuator, debug, export, backup...) |
host_word |
the host name itself reads as internal |
siblings_locked |
the routes next to it answer 401/403 and this one returns the same kind of content |
version_locked |
a newer version of this route is locked |
vary_auth |
the logged-in answer differs from the anonymous one |
-
Harvest every route recon holds: resource enumeration (crawlers, ffuf, kiterunner, arjun,
the OpenAPI documents with their security and tags), the JS Recon endpoints, and the probed
base URLs with their technologies. An API that a bundle's client-side router page calls is
tagged
client_gatedhere. - Generate routes next to them, one generator per switch (see What it requests), each with its own per-host cap.
-
Rank with a deterministic score whose reasons become
auth_expected_by. Routes scoring 5 or more, and every version-control or env guess, always get a request; the rest fill the per-host and total budgets, hosts taking turns. - Request each route anonymously, then logged in where the profile covers the host (whatever the anonymous answer was), and send a control request to a made-up path next to every 2xx. A route that answers like the made-up path (an SPA shell, a JSON "not found" with status 200) is dropped as a catch-all; a login page, an error page or an empty holding page is dropped as a wall.
- Follow up, once and never recursively: the routes an open catalog lists, and, with Cross-host on, the paths that hit tried on the sibling hosts.
- Ride along on each personalised logged-in 2xx that carries a leak signal: the CORS check and the cache check.
- Assess: signatures first, then the lead rules, then the neighbour hints.
-
Write to the graph:
Endpoint.anon_statuson the routes recon had, new Endpoints for generated routes that answered with content (never an exposed file), and one Vulnerability per result, linked from its Endpoint and BaseURL.
Project settings → Vulnerability Scanning → Route Exposure. The card's own switch runs the scan; Run partial recon runs it alone against the current graph.

| Setting | Default | What it does |
|---|---|---|
Seed paths (routeExposureSeedPaths) |
on | Well-known internal, admin and debug paths at each host and under each API prefix recon saw (/internal/, /admin/, /api/admin/export...), at most 60 per host. |
Version control and env files (routeExposureVcsFiles) |
on |
.git/HEAD, .git/config, .svn/entries, .svn/wc.db, .hg/requires, .env, .env.bak, .env.local, .env.production and web.config at each host and under each top-level folder, at most 30 per host. These always get a request, whatever the budget. |
Backup file variants (routeExposureBackupFiles) |
on | Backup copies of the server-side files recon found (.bak, ~, .old, .orig, .save, .dist and the editor's .name.swp) plus root archives and dumps (backup.zip, backup.tar.gz, site.zip, db.sql, dump.sql...), at most 40 per host. Each is read with HEAD and then only its first kilobyte (Range: bytes=0-1023); nothing is ever downloaded. |
Older API versions (routeExposureVersionVariants) |
on | Tries /api/v1/x next to a /api/v2/x route recon found, at most 3 per route. An old version that answers while the new one is locked is a strong lead (version_locked). |
Sibling routes (routeExposureSiblings) |
on | From each API route (one under /api, a version segment, or an API category): its collection (/api/orders/7 → /api/orders), /export, /all and /list, and variants with an admin or internal segment inserted, at most 6 per route and 100 per host. |
Framework paths (routeExposureFrameworkSeeds) |
on | Debug and management paths for the frameworks recon detected on the host: Spring actuator (/actuator/env, /actuator/mappings, /actuator/heapdump...), Django, Laravel (Ignition), Rails, Go pprof, .NET, PHP and Apache, at most 30 per host. The Spring heap dump is checked with HEAD only: its size and type prove it, and the agent decides whether to read it. |
Follow route catalogs (routeExposureFollowCatalogs) |
on | When an open list of routes answers (actuator mappings, an anonymous OpenAPI document, the Rails route table), requests the routes it names, once (the follow-up round is never recursive), at most 500 per host. Templated routes (/users/{id}) are skipped. |
Git walk proof (routeExposureGitWalk) |
on | After an exposed .git/HEAD, fetches the branch ref and one commit object and inflates it (at most 64 KiB) to prove the repository can be rebuilt. It stores only the proof (git_walk_proof: head>ref>commit and the tree hash prefix), never a tree, a blob or any source. |
Cross-host (routeExposureCrossHost) |
off | Tries each path that hit on one host against the other in-scope hosts that share its page title or its technology set, with the same request shape (HEAD-only stays HEAD-only), at most 50. Useful for a fleet of identical deployments; off because it multiplies requests. |
| Setting | Default | What it does |
|---|---|---|
Compare with the logged-in answer (routeExposureCompareLoggedIn) |
on | Requests each route again with your authentication profile, where it covers the host, whatever the anonymous answer was. That tells a personal page from a public one (vary_auth), and finds routes that return too much to their own user (sensitive_data on a logged-in answer). |
It needs an authentication profile: Target & Modules → Authenticated Session (see
Authenticated Session Recording). Only the hosts the profile's
scope covers get the logged-in request; without a profile the scan still finds routes open to
anyone, but no sensitive_data on logged-in answers and no CORS or cache check. Bearer, basic,
cookie and header profiles all work.
| Setting | Default | What it does |
|---|---|---|
Personal data detection (routeExposurePiiDetection) |
on | Recognises e-mails, phone numbers and similar personal data in each answer and stores only their kinds and one count (pii_types, pii_record_estimate), never a value. Three or more other people's records make a sensitive_data lead. |
Both checks run only on a route whose logged-in answer is personalised and carries a leak signal (personal data, sensitive key names or a secret), so the extra requests go where an exposure would matter.
| Setting | Default | What it does |
|---|---|---|
Cross-origin (CORS) (routeExposureCorsRider) |
on | Resends the logged-in request with crafted Origin headers: an arbitrary origin, null, a suffix variant (https://<host>.evil.invalid) and a prefix variant (https://evil<registrable domain>). An origin echoed back in Access-Control-Allow-Origin together with Access-Control-Allow-Credentials: true makes a cors_misconfig lead; the variants that worked are listed in cors_variants. It reads the response headers only, never a body. A * without credentials is not a lead: browsers refuse credentials to it. |
Cacheable logged-in content (routeExposureCacheRider) |
on | Checks whether the logged-in answer is marked cacheable (Cache-Control: public, s-maxage, or a positive max-age that is neither private nor no-store), whether a cache layer actually answers in front of it (its own cache headers: Age, X-Cache, CF-Cache-Status...), and that no Vary on Cookie or Authorization keeps sessions apart. All three make an auth_cacheable lead with the signals in cache_signals; a cacheable header with no cache in front is only intent, and is not a lead. It never plants anything in a cache and never reads from it; the agent proves it later with a unique cache-buster. |
Three optional hooks that let TypeSafe Jev steer the scan: which routes fill the request budget, and the order in which the agent confirms the leads. They rank and order only: no lead and no always-requested route is ever dropped. See Jev decisions below.
| Setting | Default | What Jev is asked | What its answer changes |
|---|---|---|---|
Rank routes with Jev (routeExposureJevRank) |
off | per route: is it meant only for logged-in, internal or administrative callers? | which routes fill the budget after the always-requested ones |
Spot disguised login walls with Jev (routeExposureJevWallCheck) |
off | per HTML lead: is the page really a login form, an error page or a placeholder? | a lead it places as a wall moves to the end |
Assess leads with Jev (routeExposureJevAssess) |
off | per lead: which kind of route is it, and how sensitive is its answer? | the agent confirms the most sensitive leads first |
Bounds, defaults and descriptions come from the settings registry, so the form and the API enforce the same ones.
| Setting | Default | Bounds | What it does |
|---|---|---|---|
Max rps (routeExposureMaxRps) |
5 | 1-50 | Requests per second across all hosts, shared by every worker and counting control requests, logged-in comparisons and backup reads. One clock serves the whole run, so a Domain batch's groups never reset it. It runs at the same time as the other Phase A scanners, so the load on a target is the sum of their rates. Capped to the engagement rate ceiling at scan start. |
Workers (routeExposureWorkers) |
4 | 1-16 | How many routes are requested at once. The rate stays capped by Max rps whatever this is; the memory governor scales it down when the host is short of RAM. |
Timeout (routeExposureTimeout) |
10 s | 2-60 | How long to wait for one answer. A timeout costs that route only; a host is skipped once it stops answering altogether. |
Max routes per host (routeExposureMaxRoutesPerHost) |
300 | 10-5000 | The most routes requested on one host, per scope group of a Domain batch. High-scoring routes and every version-control or env guess go first; the rest are cut and counted as budget_cut. |
Max routes total (routeExposureMaxRoutesTotal) |
3000 | 50-50000 | The most routes requested in one run across all hosts, per scope group. Hosts take turns, so one host never spends the whole budget. |
The Web App Pentester, API Security Audit, Full Pipeline - Active Only,
Full Pipeline - Maximum and Bug Bounty - Deep Dive recon presets turn the
scan on; applying a preset keeps the three Jev switches as they were. Every setting above is
settable over the MCP server (update_recon_settings); switching a Jev hook on
there is refused unless the project owner holds a Jev token, as in the form.
-
GET and HEAD only. A route seen only as POST, PUT or DELETE is skipped. A path whose
segments name a state change (
logout,delete,reset,cancel,revoke,pay...) is never requested, and neither is a login or other authentication flow. -
Backups are never downloaded: HEAD, then the first kilobyte with a
Rangerequest. A heap dump gets HEAD only. - No redirect is followed, bodies are read up to 256 KiB, and no cookie set by an answer is ever sent again, so an anonymous request can never carry a session.
- Scope: the run's own hosts follow the pipeline's address rule (private addresses are scanned, cloud metadata is not); a host named only in a JavaScript bundle must be in scope AND pass the stricter rule for attacker-influenced URLs. RoE-excluded hosts are never requested; the RoE time window is re-checked and fails closed.
- One clock paces every request, control request and logged-in comparison, capped to the engagement ceiling.
- Personal data is stored as its kinds and one count, never a value. Evidence snippets are masked (e-mails, long digit runs and tokens replaced) and, for JSON, are the key names only.
-
A
.gitis proven, not cloned: the walk stops at the commit object and inflates at most 64 KiB, so a compression bomb behind a.gitis bounded.
The scan also runs on its own from partial recon: the card's Run partial recon button, or the Route Exposure node of the workflow view.

-
Inputs come from the graph: the BaseURLs, their Endpoints and the hosts' Technologies (which
drive the framework paths). The pairing between a JavaScript bundle's router pages and the APIs
it calls is read back too, so a partial run tags
client_gatedexactly as a full one does. - Custom URLs, one per line, are requested with the routes generated next to them. A URL outside the project's scope is refused. Attach them to an existing BaseURL, or to a generic UserInput node.
- Results merge into the graph like a full run's. The scan's own settings apply, including the Jev hooks.
Three existing attack skills gain steps while this scan is on, with no new skill to enable:
-
Access Control confirms or rejects each lead with an anonymous and a logged-in request
(the pair), each route class with its own proof standard, then hands exposed files and consoles
to the next two. It proves the browser leads its own way:
-
CORS: the logged-in request once per hostile origin, and a proof-of-concept page written
to the sandbox's
evidence/folder, never served or opened; -
cache: a unique
?rdmncb=<12 hex>cache-buster, read logged in then anonymously; the anonymous answer must be the logged-in body with a cache hit; never aPURGE; -
client-side gating: the hidden pages recon read from the same bundle are found in the
graph, rendered with
execute_playwrightwithout the session, then the pair runs on the API they fire. A hidden page whose API answers 401 is correct and rejected.
-
CORS: the logged-in request once per hostile origin, and a proof-of-concept page written
to the sandbox's
-
Path Traversal proves the exposed files with the minimum read (one
.gitobject inflated to itscommitheader, the first bytes of a backup, theKEY=valuelines of an.envcounted, never printed). -
RCE proves each debug console's real state without exploiting it: a PIN-locked Werkzeug
console is reported as locked; an unlocked one gets exactly one
1+1evaluation, in the exploitation phase only. It never POSTs to an actuator path and never runs an Ignition chain.
A proof is recorded against the finding's exact id, which adds a CONFIRMS edge and moves it to
T1. Bodies stay in files in the Kali sandbox, so no value reaches the conversation. With Access
Control off, the card shows a Half a cycle alert: the scan would write leads nothing confirms.
Three deeper steps are switches in Agent Skills, off by default and gated by the Rules of Engagement:
| Switch | Skill | Also needs |
|---|---|---|
| Reconstruct exposed .git repositories | Path Traversal | data exfiltration allowed in the RoE |
| Validate secrets found in exposed files | Path Traversal | (one identity-only call per secret to its provider) |
| Analyse exposed heap dumps | RCE | data exfiltration allowed, and sensitive-data handling above "no access" |
Each step is described in Agent Skills.
With AI in Pipeline on, each of the three hooks has a switch in this card and a card in the AI in Pipeline panel of the Target tab; both are the same field. Switching one on needs a Jev token on the project owner's account.

| Hook | When it runs | What Jev sees | Bounds |
|---|---|---|---|
| Rank routes | after the prior score, before the budget is chosen | the path, the method, a closed category and four flags (the API document requires credentials; only that document names it; only the JavaScript names it; the host reads as internal) | 400 routes per scan, highest prior score first |
| Disguised login walls | after the assessment | each HTML anonymous lead's page (title, size, masked body) | 100 pages, 60 s |
| Assess leads | after the assessment | evidence only: the statuses with and without credentials, content class and size, whether the bodies matched, masked key names, the largest array, and the closed hint and signal labels. Never the route class or a value | 200 leads, 60 s |
All three act on Jev's answers, and each answer is saved on the finding in the graph:
| Hook | What its answer changes | Saved on the finding as |
|---|---|---|
| Rank routes | The routes always requested (the floor: a prior score of 5 or more, every version-control and env seed) come first, as before. The rest of each host's budget is filled by Jev's answer, the routes it reads as private first; a route it did not answer follows by prior score |
route_jev_privacy (0-100) |
| Disguised login walls | A lead Jev places as a login form, an error page, a placeholder, a parked or a default page, at confidence 70 or more, moves to the end of the list |
anon_page_class, anon_page_class_confidence, anon_page_class_source
|
| Assess leads | The leads are ordered by Jev's sensitivity, most sensitive first; a lead it reads as public content at confidence 70 or more goes last |
route_jev_kind, route_jev_kind_confidence, route_jev_sensitivity, route_jev_source
|
The agent reads the leads in that same order: the Access Control skill's lead query puts a wall or
public-content reading last and the most sensitive first, and it shows jev, jev_kind and
page_class beside each lead. Jev's reading only sets the order: every lead is still proven or
rejected by the agent's own requests, no lead is ever dropped, and the route class the scan
decided is never rewritten.
A partial recon runs the same hooks and writes the same fields. Each decision is also recorded
next to the scan's own one, in the recon output under jev_shadow.<hook> (rollout act) and as
jev-shadow <hook> drawer lines. A Jev failure (no token, no credit, a timeout) falls back to the
deterministic order and writes no Jev field, so the scan runs exactly as it does without Jev; it
never waits on Jev. The guide to Jev itself is TypeSafe Jev.
Red Zone → Route Exposure (the Data Table tab's picker, DoorOpen icon) lists every result: host, path, kind, route class, severity, status (Finding, Pending or Confirmed), the anonymous and logged-in statuses, why a login was expected, leak signals, the generator that produced the route, and the evidence. Search, filters, mute and the CSV / JSON / Markdown exports work as on every Red Zone table.

The same table searched for /api/, where the leads are: the agent has confirmed the
client-gated API and the CORS and cache leads on /api/profile; the rest are pending, each with
its route class and the generator that found it.

Node Inspector and the node drawer. A route exposure Vulnerability opens with a Route
exposure block of labelled rows (exposure, status, route class, route, both answers, the
anonymous body, leaks, personal data, sensitive fields, how it was found, confidence, evidence)
before its raw properties. The status reads the agent's CONFIRMS edge.

-
Graph:
Vulnerability {source: 'route_exposure'}nodes, linked from their Endpoint and BaseURL; an Endpoint's drawer shows itsanon_status. -
Priority Board: findings are classed (
source_disclosure,credential_exposure,rcefor an open console...) and the same exposed path on several hosts is grouped as one fix; pending leads wait at T4. - Reports: findings labelled "Route Exposure"; leads only once confirmed.
The lab that validates every row of this page is testing/guinea_pigs/route_exposure_lab.
- Agent Skills: the agent's route-exposure steps
- Red Zone: the Route Exposure table and the other finding tables
- Priority Board: how leads and findings are ranked
- Authenticated Session Recording: the profile the logged-in comparison uses
- TypeSafe Jev and AI in the Recon Pipeline: the Jev hooks
- Recon Pipeline Workflow: where GROUP 6 Phase A sits, and partial recon
- Rules of Engagement: the rate ceiling, time window and exfiltration rules
Getting Started
- Getting Started
- Deploying to a Server
- User Management & Roles
- Creating a Project
- Recon Presets
- Global Settings
Core Workflow
- Red Zone
- Recon Pipeline Workflow
- Running Reconnaissance
- Scan Timeline
- AI Agent Guide
- Fireteam — Parallel Specialists
- Exploit-Path Search (LATS)
- Agent Workspace
- Reverse Shells
Scanning & OSINT
- AI in the Recon Pipeline
- Adversarial AI Recon
- AI Gauntlet
- JS Reconnaissance
- GraphQL Security Testing
- Subdomain Takeover Detection
- VHost & SNI Enumeration
- TLS Certificate Grab
- Web Cache Poisoning
- Serialized Object Detection
- Route Exposure Scan
- Origin Discovery
- GVM Vulnerability Scanning
- GitHub Secret Hunting
- Secret Multiscanner
- Supply-Chain Scanning
AI & Automation
- AI Model Providers
- MCP Tool Plugins
- MCP Server
- Knowledge Base & Web Search
- Agent Skills
- Chat Skills
- Tradecraft Lookup
- CVE Intel
- Playwright Browser Automation
- CypherFix — Automated Remediation
- Priority Board
- Rules of Engagement (RoE)
Plugins
HackLab
Analysis & Reporting
- Insights Dashboard
- TrafficMind
- Authenticated Session Recording
- proxy_brain — web hacking in code
- Pentest Reports
- Attack Surface Graph
- Surface Shaper
- EvoGraph — Attack Chain Evolution
- Data Export & Import
Contributing
Reference & Help