# Find Production Sourcemap

> Check whether a production web app ships its JavaScript sourcemaps before doing the hard work of reverse-engineering minified bundles. Surprisingly often the `.js.map` sibling URL is right there. Use when reverse-engineering a web app, a webview-based mobile app, or any system where the client is shipped as compiled JS.

- Skill: `abedegno/find-production-sourcemap` (Agent Skill)
- Install (CLI): `npx skillmds@latest add abedegno/find-production-sourcemap`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abedegno/find-production-sourcemap/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: abedegno (https://skillmd.com/u/abedegno)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/abedegno/find-production-sourcemap

---


# Find Production Sourcemap

Before spending a week reverse-engineering minified JavaScript, check if the deployment ships sourcemaps. Often they're right there at `<bundle-url>.js.map`. When they are, you get back the original source — variable names, file boundaries, comments, sometimes the TypeScript types — and the task transforms from "deobfuscate" to "read code".

This skill is **cheap to apply**: 30 seconds to test the hypothesis. When it pays off, it can collapse weeks of work to hours.

## When this skill applies

- The target is a web app, webview-wrapped mobile app, or hybrid app (Cordova / Capacitor / React Native bundled via Metro).
- You have the URL of a JavaScript bundle loaded by the app, typically from DevTools' Network panel or from mitm capture.
- You want to read the original source, not the minified blob.

If the app is pure native (Swift / Kotlin / Java) there's no sourcemap to find — go back to decompilation.

## Workflow

### Step 1: Identify a bundle URL

Open the target in Chrome with DevTools. In the Network panel, filter to `JS` and pick the largest bundle (usually the entry chunk or vendor chunk):

```
https://cdn.example.com/assets/index-Xx1Yy2Zz.js
```

Or pull bundle URLs from a mitm capture:

```bash
mitmdump -nr session.flows -f "~u .js$ & ~q" | head
```

### Step 2: Try the obvious — fetch `<url>.map`

```bash
BUNDLE_URL="https://cdn.example.com/assets/index-Xx1Yy2Zz.js"
curl -sSI "${BUNDLE_URL}.map" | head -3
```

If you see `HTTP/2 200` and `content-type: application/json` (or `text/plain`), congratulations — the sourcemap is shipped. Fetch it:

```bash
curl -sS "${BUNDLE_URL}.map" -o /tmp/bundle.map
file /tmp/bundle.map
#   /tmp/bundle.map: ASCII text, with very long lines  (or JSON data)
```

If it's a 403 / 404 / `signature mismatch`, sourcemap isn't shipped at this URL. Try variants:

- **Inline `sourceMappingURL` comment.** The bundle often has `//# sourceMappingURL=index-Xx1Yy2Zz.js.map` as its last line. Check:
  ```bash
  curl -sS "$BUNDLE_URL" | tail -1
  ```
  If that's a non-obvious filename, fetch that one.
- **Hidden sourcemap (`hidden-source-map` Webpack option).** Bundle has no `sourceMappingURL` comment but the `.map` file IS deployed. Try the `.map` URL directly anyway — sometimes it 200s.
- **Different path.** Sourcemaps sometimes live at `/sourcemaps/<hash>.map` or in a separate CDN bucket. Grep the bundle for any `/.+\.map` URLs.
- **Available only with a header / cookie.** Sentry sometimes gates them behind a specific `X-Sentry-Token`. Less common.

### Step 3: Reconstruct the source tree

A few tools, in increasing capability:

```bash
# Option A: unwebpack-sourcemap (Webpack-style bundles)
npx unwebpack-sourcemap --output ./recovered /tmp/bundle.map

# Option B: source-map-unpack
npx source-map-unpack /tmp/bundle.map ./recovered

# Option C: the source-map npm library directly, for custom unpacking
node -e "
const sm = require('source-map');
const fs = require('fs');
const path = require('path');
const map = JSON.parse(fs.readFileSync('/tmp/bundle.map'));
sm.SourceMapConsumer.with(map, null, consumer => {
    consumer.sources.forEach(src => {
        const content = consumer.sourceContentFor(src);
        if (!content) return;
        const out = path.join('./recovered', src.replace(/^webpack:\\/\\/.*?\\//, ''));
        fs.mkdirSync(path.dirname(out), { recursive: true });
        fs.writeFileSync(out, content);
    });
});
"
```

For the source-map library route to work, the sourcemap must include `sourcesContent` (most do; some `hidden-source-map` configs strip it). If `sourcesContent` is missing you can sometimes still pull the source from the original URLs listed under `sources[]`, but those are often gone from prod.

### Step 4: Browse the recovered source

```bash
find ./recovered -type f | head -20
find ./recovered -name '*.ts' -o -name '*.tsx' -o -name '*.vue' -o -name '*.svelte' | wc -l
```

What you'll typically find:

- **TypeScript source** with type annotations intact. Comments may or may not survive depending on the terser config.
- **Original file structure** (`src/components/Login.tsx`, `src/services/api.ts`, etc.).
- **Named symbols**: function names, variable names, exported types. Single-letter `a, b, c` becomes `customerId, paymentInfo, sessionToken`.
- **Configuration files**: API endpoints, feature flag keys, third-party SDK initialization strings, *sometimes* embedded constants that should never have shipped to the client.

What you typically *won't* find:

- Server-side code (sourcemaps only cover what's bundled into the JS).
- Comments stripped by a `comments: false` minifier config.
- Type definitions that were `tsconfig.json declaration: false` (they're erased at compile time).

### Step 5: Search for high-value patterns

Once unpacked, grep is your friend:

```bash
cd recovered

# PRNG or seed handling
grep -rn 'Math.random\|seed\|Date.now()' --include='*.ts' | head -20
grep -rn 'xorshift\|mulberry\|sfc32\|xxhash' --include='*.ts' | head

# Authentication
grep -rn 'authorize\|client_id\|audience\|PKCE\|code_challenge' --include='*.ts' | head

# API endpoints
grep -rn 'apiBaseUrl\|API_BASE\|process.env.REACT_APP\|VITE_' --include='*.ts' | head

# Server / WS protocol
grep -rn 'websocket\|colyseus\|socket.io\|new WebSocket' --include='*.ts' | head

# Feature flags / experiments
grep -rn 'LaunchDarkly\|launchdarkly\|optimizely\|Statsig\|growthbook' --include='*.ts' | head

# Hardcoded secrets that should not have shipped
grep -rn '[A-Za-z0-9_-]\{32,\}' --include='*.ts' \
    | grep -v 'sourceMappingURL\|webpack' | head
```

## Why sourcemaps end up shipped

A sample of reasons people leave `.js.map` in prod:

- **Sentry / Datadog RUM / Rollbar** all need sourcemaps to resolve stack traces in production errors. Many teams deploy the maps to CDN so the error tracker can fetch them. Most of those teams forget to gate the maps from public download.
- **Default Vite / Webpack / Next.js / Rollup config** emits sourcemaps. Disabling for prod requires an explicit `sourcemap: false`.
- **CI pipeline copies `dist/**`** to the CDN. Whatever's in `dist/` ships.
- **CDN provider** sometimes serves any file in a deployed directory regardless of intent.

The combination of these means production sourcemaps are common — surprisingly so.

## When the sourcemap isn't there

You've got the minified blob and need to work with it. Strategies:

- Use Chrome DevTools' built-in pretty-printer (`{}` icon) for readable structure even without symbol names.
- `js-beautify` for offline pretty-printing.
- For named-symbol recovery from minified-but-not-obfuscated code, `prettier --parser=babel` + manual rename via your editor's rename-symbol is slow but works for small targets.
- For Webpack bundles specifically, `webcrack` (<https://github.com/j4k0xb/webcrack>) splits chunks into modules and can sometimes recover module names from `webpackChunkName` magic comments.

## Pitfalls

- **Don't run the recovered source.** The unpacked files were minified for browser execution; they may have side effects on load, reference CDN URLs that 404, or trigger network calls. Read them; don't execute them.
- **Beware embedded credentials.** Sourcemaps occasionally include `.env`-style files inlined by the bundler. Treat them as you would any leaked secret — don't share publicly, don't commit to a public repo, report to the operator if material.
- **Comments may be the most valuable part.** Many devs document PRNG choice, auth contract details, "TODO: rotate this key" notes in comments that survive minification with default terser. Read them.
- **TS type definitions and runtime types are different.** The recovered `.ts` shows what the developer believed about types; the runtime may diverge if there are casts or any types.

## Disclosure considerations

A shipped sourcemap is not in itself a vulnerability — it's an information disclosure that lowers the cost of further analysis. Whether it's worth reporting depends on what was in it:

- API endpoints + structure: probably not worth reporting separately (those are visible from any HTTP capture anyway).
- Embedded secrets, internal-only endpoints, debug interfaces: worth reporting.
- Comments revealing intended-private security mechanisms: judgement call; usually worth a private note.

If you do report, lead with the *concrete impact* (specific endpoint, specific embedded value), not the abstract presence of sourcemaps.

## Pairs with

- [`mobile-auth-replay`](../mobile-auth-replay/SKILL.md) — once you have the auth client source, the PKCE / state / nonce / audience values are right there in plain TS.
- [`bit-exact-sim-validation`](../bit-exact-sim-validation/SKILL.md) — if the source includes a PRNG or simulation logic, you can port it and validate.
- [`time-window-seed-bruteforce`](../time-window-seed-bruteforce/SKILL.md) — recovered PRNG source tells you the exact algorithm to port for seed recovery.

