June 11, 2026
Mastering OpenDoor: Low-Noise Recon with Redirects, Endpoint Sniffing, and Bounded Crawl (Part 2)
How OpenDoor turns passive response analysis, redirect metadata, endpoint discovery, and bounded crawl into cleaner reconnaissance…
SWEB
5 min read
How OpenDoor turns passive response analysis, redirect metadata, endpoint discovery, and bounded crawl into cleaner reconnaissance evidence.
In Part 1, we used a deterministic local lab to show how OpenDoor moves beyond plain directory brute forcing. The key idea was simple: a useful recon tool should not only say "this path returned 200". It should preserve context: auth gates, forbidden paths, directory listings, exposed files, stack traces, redirect markers, and machine-readable reports.
Part 2 keeps the same local-only workflow, but raises the bar.
Can OpenDoor learn more from the responses it already fetched without becoming a heavy crawler, a browser automation framework, or a proxy-style request replay tool?
OpenDoor answers that with three practical additions:
- passive redirect classification;
- endpoint sniffing from already-fetched response bodies;
- bounded same-origin crawl enrichment.
The result is still a small, controlled scan, but the evidence is richer.
Lab boundary: everything in this article runs against
127.0.0.1:8080. Do not run these examples against systems you do not own or do not have explicit authorization to test.
The Part 2 lab
The Part 2 lab adds a small client-facing page and a JavaScript asset. The goal is not to simulate a full application. The goal is to create deterministic response evidence for redirect handling, client-side endpoint references, and bounded crawl behavior.
The important paths are:
/client-app
/static/app.js
/linked-admin
/api/search
/old-dashboard -> /admin
/canonical -> /login/client-app
/static/app.js
/linked-admin
/api/search
/old-dashboard -> /admin
/canonical -> /loginThe JavaScript asset references several client-exposed endpoints:
/api/profile
/api/internal/status
ws://127.0.0.1:8080/ws
/socket.io/?EIO=4&transport=polling
/events/api/profile
/api/internal/status
ws://127.0.0.1:8080/ws
/socket.io/?EIO=4&transport=polling
/events
This gives a safe, repeatable way to test endpoint discovery without executing JavaScript and without touching a real target.
Baseline scan: no crawl yet
First, run the baseline scan without crawl.
python opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-299,301,302,401,403,500 \
--exclude-status 404 \
--sniff endpoint,indexof,file,stacktrace,skipempty \
--reports std,html,json,sarif \
--reports-dir reports/mastering-lab-part-2python opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-299,301,302,401,403,500 \
--exclude-status 404 \
--sniff endpoint,indexof,file,stacktrace,skipempty \
--reports std,html,json,sarif \
--reports-dir reports/mastering-lab-part-2The baseline is intentionally small: 17 planned wordlist entries. It finds the usual response buckets from Part 1 and the new redirect examples.
Key output:
OK /client-app
R(internal) /old-dashboard -> /admin
R(login) /canonical -> /login
R(login) /redirect -> /login
success 5
redirect 3
indexof 1
file 1
forbidden 1
auth 1
stacktrace 1OK /client-app
R(internal) /old-dashboard -> /admin
R(login) /canonical -> /login
R(login) /redirect -> /login
success 5
redirect 3
indexof 1
file 1
forbidden 1
auth 1
stacktrace 1
At this stage, OpenDoor has found /client-app, but it has not requested /static/app.js, /linked-admin, or /api/search. That is the controlled baseline.
Bounded crawl: three extra same-origin requests
Now add --crawl.
python opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-299,301,302,401,403,500 \
--exclude-status 404 \
--sniff endpoint,indexof,file,stacktrace,skipempty \
--crawl \
--reports std,html,json,sarif \
--reports-dir reports/mastering-lab-part-2python opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-299,301,302,401,403,500 \
--exclude-status 404 \
--sniff endpoint,indexof,file,stacktrace,skipempty \
--crawl \
--reports std,html,json,sarif \
--reports-dir reports/mastering-lab-part-2The important lines are the crawl-added resources:
OK (Endpoint) [crawl] /static/app.js
OK [crawl] /linked-admin
OK [crawl] /api/searchOK (Endpoint) [crawl] /static/app.js
OK [crawl] /linked-admin
OK [crawl] /api/search
This is the central behavior: --crawl did not turn the scan into an unbounded spider. It enriched the queue with a small same-origin set derived from an already-fetched HTML page.
The progress marker also makes the boundary visible:
[17+3/17][17+3/17]The original wordlist had 17 entries. The crawl added 3 more requests.
Endpoint sniffing: passive client-side discovery
The endpoint sniffer reads response bodies and reports client-exposed endpoints. It does not execute JavaScript. It does not open a WebSocket. It does not expand the queue from every endpoint reference.
In this lab, /static/app.js is discovered by bounded crawl. Then --sniff endpoint analyzes the JavaScript response body and extracts five endpoint references:
ws://127.0.0.1:8080/ws
/socket.io/?EIO=4&transport=polling
/events
/api/profile
/api/internal/statusws://127.0.0.1:8080/ws
/socket.io/?EIO=4&transport=polling
/events
/api/profile
/api/internal/statusThis is the difference between:
I found a JavaScript file.I found a JavaScript file.and:
I found a JavaScript file that exposes API, WebSocket, Socket.IO, and SSE/EventSource surfaces.I found a JavaScript file that exposes API, WebSocket, Socket.IO, and SSE/EventSource surfaces.The HTML report keeps that evidence under the endpoint bucket, with structured endpoint_detection and passive_finding fields.
The JSON report carries the same evidence in a machine-readable form.
A useful detail: the finding source remains response_body. The sniffer is passive. It is not a browser, not a WebSocket client, and not an active API enumerator.
Redirect intelligence: preserve or materialize
By default, OpenDoor keeps redirects as redirect evidence.
R(internal) /old-dashboard -> /admin
R(login) /canonical -> /login
R(login) /redirect -> /loginR(internal) /old-dashboard -> /admin
R(login) /canonical -> /login
R(login) /redirect -> /loginThat is useful when the redirect itself is the finding. For example, a redirect to /login can explain why an interesting path did not return a content-rich response, and an internal redirect can identify canonicalized paths.
When the goal is to classify the final same-host response, use --follow-redirects.
python opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-399,401,403,500 \
--exclude-status 404 \
--follow-redirects \
--reports std,html,json,sarif \
--reports-dir reports/mastering-lab-part-2-redirectspython opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-399,401,403,500 \
--exclude-status 404 \
--follow-redirects \
--reports std,html,json,sarif \
--reports-dir reports/mastering-lab-part-2-redirectsWith redirect following enabled, the same redirecting entries materialize as final responses:
/old-dashboard -> final /admin
/canonical -> final /login
/redirect -> final /login/old-dashboard -> final /admin
/canonical -> final /login
/redirect -> final /loginThis gives two distinct workflows:
passive redirect classification -> preserve redirect evidence
bounded follow-redirects -> classify the final responsepassive redirect classification -> preserve redirect evidence
bounded follow-redirects -> classify the final responseBoth are useful. The important point is that they answer different questions.
Runtime diagnostics show the boundary
With --debug 1, OpenDoor prints runtime diagnostics.
python opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-299,301,302,401,403,500 \
--exclude-status 404 \
--sniff endpoint,indexof,file,stacktrace,skipempty \
--crawl \
--debug 1 \
--reports std,json \
--reports-dir reports/mastering-lab-part-2-debugpython opendoor.py \
--host http://127.0.0.1 \
--port 8080 \
--method GET \
--threads 1 \
--wordlist examples/mastering-lab/wordlist.txt \
--include-status 200-299,301,302,401,403,500 \
--exclude-status 404 \
--sniff endpoint,indexof,file,stacktrace,skipempty \
--crawl \
--debug 1 \
--reports std,json \
--reports-dir reports/mastering-lab-part-2-debugFor this run, the diagnostics show:
queue | 17 consumed, 20 submitted, 0 pre-request skipped
traffic | response bodies 2.2 KB, headers 2.9 KB, requests 20
memory | RSS peak 40.7 MB ...queue | 17 consumed, 20 submitted, 0 pre-request skipped
traffic | response bodies 2.2 KB, headers 2.9 KB, requests 20
memory | RSS peak 40.7 MB ...
This is the operational proof that the scan remained bounded. The crawl added context, not uncontrolled request volume.
Reports turn terminal output into evidence
Terminal output is good for triage. Reports are better for review, automation, and regression checks.
For this workflow, keep at least:
std
html
json
sarifstd
html
json
sarifThe HTML report is useful for human review. The JSON report preserves detailed passive metadata. The SARIF report fits security engineering workflows where findings need to be stored, compared, or imported into CI/CD tooling.
For Part 2, the most useful report fields are:
endpoint_detection
passive_finding
redirect_classificationendpoint_detection
passive_finding
redirect_classificationThose fields are what make the output reviewable after the terminal session is gone.
Boundaries
This workflow is intentionally local and authorized. The lab runs on 127.0.0.1:8080 and produces deterministic results.
--crawl is bounded same-origin enrichment, not a general-purpose spider. --sniff endpoint is passive response analysis, not JavaScript execution. --follow-redirects is bounded redirect materialization, not a request replay workflow.
OpenDoor is not acting as an intercepting proxy, MITM proxy, browser automation layer, or repeater. It is collecting structured evidence from controlled HTTP responses.
Closing
Part 1 showed how OpenDoor classifies what it directly requests.
Part 2 shows how OpenDoor can extract more context from the same small scan:
redirect intelligence
client-exposed endpoint evidence
bounded crawl enrichment
structured report metadata
runtime diagnosticsredirect intelligence
client-exposed endpoint evidence
bounded crawl enrichment
structured report metadata
runtime diagnosticsThe practical benefit is not "more noise". It is better evidence per request.
That is the direction OpenDoor pushes toward: lower-noise reconnaissance where every extra request and every passive finding has a reason.