Point it at a web app. Get the API its JavaScript talks to.
jsrecon unpacks source maps, parses every bundle with a real JavaScript grammar and hands you the endpoints, GraphQL operations, routes and leaked keys the front-end knows about — as a table, JSON, Markdown and an OpenAPI 3.1 skeleton.
English · Русский
Install · Try it · What it finds · Use · Output · How it works · Intended use · Roadmap
Install
pipx install jsrecon # or: pip install jsrecon inside a venv
Python 3.10+. No config, no API keys, no browser.
Try it in 30 seconds
The repo ships a small demo shop — TypeScript sources, bundled and minified with esbuild, with its source map next to the bundle, exactly as a lot of production front-ends go out:
git clone https://github.com/web3daemon/jsrecon && cd jsrecon
python -m http.server 8080 -d examples/shop/dist # in one terminal
jsrecon map http://localhost:8080 -o recon # in another
That is the run in the demo above: 16 endpoints with their methods and path parameters,
4 GraphQL operations, 8 screens, one AWS key that should never have shipped — each one pointing at
the original file and line, src/api/orders.ts:18, not main-RU4RXFFD.js:1.
Why
Grepping a minified bundle for /api/ gives you noise: strings that were never URLs, and URLs
that were built with a + or a template, so the regex only sees half of them.
jsrecon reads the code the way the engine does. It parses every bundle with
tree-sitter, so a URL split across a +
("/api/" + "orders"), a minified s.get(…), a method hidden in the options object
(fetch(url, { method: "POST" })) and a templated path (`/orders/${id}/refund` →
/orders/{id}/refund) all resolve.
And when the bundle ships a source map, the original TypeScript is sitting right inside it. jsrecon writes that tree back to disk and reads that instead of the minified soup — real names, real comments, real file layout. Every finding points at the original file and line.
What it finds
| 🌐 HTTP endpoints | fetch / axios / ky / $.ajax / XHR calls with their method, plus API-shaped URL and path literals |
| 🧩 Path & query parameters | `/orders/${id}` → /orders/{id}, ?page=${page} → a query parameter — straight into OpenAPI |
| 🧬 GraphQL | query / mutation / subscription operations, in gql tags and plain strings |
| 🗺 Source maps → sources | sourcesContent unpacked into the original file tree, then analysed first |
| 🧭 Client-side routes | router tables and <Route path> — every screen of the app before you click |
| 🔑 Leaked server secrets | a narrow, defensive scan for credentials that must never reach a browser — always masked |
| 📍 Where it came from | every finding carries file:line, in the original source when a map exists |
| 📤 Reports | live table, findings.json, findings.md, openapi.json and the sources/ tree |
Use
jsrecon map https://app.example.com # crawl a page's <script> and modulepreload bundles
jsrecon map https://app.example.com/main.js # one bundle
jsrecon map ./dist # a local build directory
jsrecon map ./dist -o recon # + write reports and recovered sources
jsrecon map ./app.min.js --json | jq '.endpoints' # JSON to stdout
jsrecon <target> is shorthand for jsrecon map <target>.
| flag | |
|---|---|
-o, --out DIR |
write findings.json, findings.md, openapi.json and sources/ into DIR |
--json |
print the findings as JSON to stdout (the table moves to stderr) |
--no-secrets |
skip the client-secret audit |
--timeout N |
HTTP timeout in seconds (default 20) |
Output
jsrecon map … -o recon writes:
recon/
├── findings.json every finding with method, confidence, source file and line
├── findings.md the same as Markdown tables, ready for a ticket or a report
├── openapi.json an OpenAPI 3.1 skeleton: paths, methods, path/query parameters
└── sources/ the original tree recovered from source maps
└── src/api/orders.ts …
The OpenAPI file is a skeleton — paths and parameters, not request or response schemas. It's a starting point for a client or for httpcrabber's traffic-based spec, not a finished contract.
"/orders/{id}/refund": {
"post": {
"summary": "seen in client JS at src/api/orders.ts:18 (call)",
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }]
}
}
How it works
target ─► fetch ─► [bundles] ─┬─► source map? ─► original sources ─┐
(URL, .js, ./dist) │ ├─► tree-sitter ─► strings + calls
└────────────────────────────────────┘ │
┌──────────────┼─────────────┬──────────┐
endpoints graphql routes secrets
└──────────────┴──── dedupe ─┴──────────┘
│
table · json · md · openapi · sources/
Originals are analysed before the bundle, so when both mention an endpoint the finding keeps the readable location. If a tree-sitter grammar can't load, a regex fallback yields the same shapes at lower precision — jsrecon still runs.
src/jsrecon/
fetch.py page → <script>/modulepreload bundles, or local files
sourcemaps.py sourceMappingURL → sourcesContent → a path-safe tree
parse.py tree-sitter JS/TS → strings and calls (regex fallback)
extract/ endpoints · graphql · routes · secrets, dedupe
report.py json · markdown · openapi 3.1
cli.py jsrecon map
Recon suite
jsrecon is the sibling of httpcrabber: httpcrabber records what an app does on the wire, jsrecon reads what its code can do. Run both on the same app and you get the traffic you saw and the endpoints you haven't triggered yet.
Intended use
jsrecon reads the JavaScript a site already serves to every visitor — the same bytes your browser downloads — and makes sense of it. Point it at an app whose API you need to understand:
- a third-party service with no public SDK or docs, so you can build a client;
- a public API you want an OpenAPI spec or a typed client for;
- a bug-bounty target within its stated scope, or a pentest you are authorised for;
- your own front-ends — including catching a secret that slipped into a build before someone else does.
It only reads what a normal page load fetches. It never breaks authentication, bypasses bot
protection or hides what it is (its User-Agent says jsrecon). The secret scan is defensive and
masks every value. Don't point it at systems you aren't authorised to test — you are responsible
for how you use it. See SECURITY.md.
Roadmap
- method from
fetchoptions andXMLHttpRequest.open - client-side routes
- path & query parameters,
file:linefor every finding - typed async
httpx+ pydantic client generated from the recovered API - an MCP server, so an agent can ask "what endpoints does this app expose?"
-
jsrecon watch— re-run on a schedule and alert on API drift - read httpcrabber sessions as input
Building it in public: t.me/web3daemon_social · X @web3daemon.
Contributing
Issues and pull requests are welcome.
pip install -e ".[dev]"
ruff check src tests scripts && pytest
python examples/shop/build.py # rebuild the demo shop (needs Node.js)
python scripts/gen_demo.py # re-record assets/demo.svg from a real run
License
Release files for jsrecon 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jsrecon-0.2.0.tar.gz | 25.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jsrecon-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 50.0 kB
Release files / jsrecon-0.2.0.tar.gz
| Download URL | jsrecon-0.2.0.tar.gz |
|---|---|
| Size | 25.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
196ac98e85b9930879523e78a2f62a60d709e62fecf35c024a25357bf4368b1a
|
|
BLAKE2b-256 checksum How to use checksums |
01008b1a2b26e3a33437fbf02cec6644fa3a2cc77594c1042997dbf14ec5552a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / jsrecon-0.2.0-py3-none-any.whl
| Download URL | jsrecon-0.2.0-py3-none-any.whl |
|---|---|
| Size | 24.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5159a49b4d00c1ed410d54a4baeeb464f88b0c1b236f7edc384a4c9338edd35e
|
|
BLAKE2b-256 checksum How to use checksums |
8c9cdbacaf2ebd561da436ed874433a7030d94f562b6637d0b3486dc17486877
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log