Extracting Source Code from JavaScript Source Maps with Bash
Hook
Thousands of production websites accidentally expose their entire application source code through a single .js.map file—and you can extract it all with just bash and curl.
Context
Modern JavaScript applications are minified and bundled before deployment. Webpack, Rollup, and similar tools compress dozens of source files into compact bundles, renaming variables to single letters and stripping whitespace. To enable debugging in production, these tools can generate source maps: JSON files that map minified code back to original sources.
The problem? Many teams enable source maps in production environments without realizing they're shipping their entire codebase—comments, internal API endpoints, business logic, and all—to anyone who knows where to look. Browser DevTools can reconstruct sources from these maps, but that requires manual clicking through file trees. Security researchers and penetration testers need a programmatic way to extract these sources quickly. That's where sourcemapper comes in: a 50-line bash script that automates the entire process using tools already installed on most Unix systems.
Technical Insight
Sourcemapper's elegance lies in its simplicity. The entire tool is a single bash script that leverages curl for HTTP requests and jq for JSON parsing. When you point it at a source map URL, it downloads the .map file, extracts the sources array and sourcesContent field, then reconstructs the original directory structure on disk.
Here's how you use it:
./sourcemapper.sh https://example.com/static/js/main.abc123.js.map
This single command downloads the source map and writes all original sources to a local directory matching the project structure. The script's core logic parses the source map JSON to extract two critical fields: sources (an array of original file paths) and sourcesContent (an array of the actual source code). It then iterates through these arrays, creating directories as needed and writing each file.
The implementation uses jq's array indexing to pair filenames with content:
for i in $(seq 0 $((length - 1))); do
source_path=$(echo "$sources" | jq -r ".[$i]")
source_content=$(echo "$source_content_array" | jq -r ".[$i]")
# Create directory structure
mkdir -p "$(dirname "$source_path")"
# Write content to file
if [ "$source_content" != "null" ]; then
echo "$source_content" > "$source_path"
else
echo "Not Found" > "$source_path"
fi
done
This approach works because source maps follow a standardized format defined in the Source Map Revision 3 specification. The sourcesContent field is optional but commonly included by bundlers when using the devtool: 'source-map' setting in Webpack or equivalent configurations in other tools.
What makes this tool particularly useful for security research is its handling of relative paths. Production source maps often reference sources like webpack:///src/components/PaymentForm.jsx or ../../../utils/api.js. Sourcemapper preserves these paths, recreating the project structure so you can navigate it just as developers would. You end up with a directory tree that might look like:
webpack:///
├── src/
│ ├── components/
│ │ ├── PaymentForm.jsx
│ │ └── UserProfile.jsx
│ ├── utils/
│ │ ├── api.js
│ │ └── auth.js
│ └── config/
│ └── endpoints.js
The tool's reliance on standard Unix utilities makes it incredibly portable. No Node.js installation, no npm dependencies, no virtual environments—just bash, curl, and jq. This matters when you're working on minimal penetration testing distributions or locked-down corporate systems where installing software requires approval workflows.
One architectural decision worth noting: sourcemapper creates placeholder files containing "Not Found" when a source is listed in the sources array but missing from sourcesContent. This happens when bundlers reference files they didn't inline. While these placeholders clutter the output, they serve a purpose: they show you what the project structure looked like, revealing component names and architectural decisions even when the actual code isn't available.
Gotcha
Sourcemapper's simplicity is both its strength and weakness. The tool only works when the source map includes the sourcesContent field—it cannot decode VLQ mappings to reconstruct sources from line/column positions. Many production source maps omit sourcesContent to reduce file size, instead expecting browsers to fetch sources from original URLs. When those URLs are internal development paths or corporate VPN addresses, you're stuck with an array of filenames but no actual code.
The script also lacks error handling for malformed JSON or network failures. If curl times out or jq encounters unexpected JSON structure, you'll get cryptic error messages rather than helpful guidance. Large source maps (10MB+) can cause jq to consume significant memory, and the script processes everything synchronously, so you're waiting for each file to write before the next begins. For massive monorepo source maps with thousands of files, this becomes noticeably slow. Browser DevTools, by contrast, lazy-load sources on demand.
Verdict
Use if: You're conducting security audits on web applications, need to quickly extract production source code for vulnerability analysis, prefer command-line tools over GUI clicking, or work in environments where installing Node.js isn't practical. This tool excels at one-off extractions during penetration tests or when learning from open-source projects that accidentally ship source maps. Skip if: You're dealing with source maps that lack sourcesContent fields, need robust error handling for production tooling, want to programmatically analyze mappings rather than just extract sources, or prefer the convenience of browser DevTools for casual debugging. For those cases, reach for the source-map npm package or just use Chrome DevTools' built-in source extraction features.