September 25, 2026
Mastering grep for JavaScript Recon: A Complete Guide to Find Sensitive Data
How a Single Terminal Command Can Uncover API Keys, Tokens, and Hidden Endpoints Buried in Minified Code

By Monika
5 min read
Every modern web application ships massive amounts of JavaScript to the browser โ and that JavaScript often carries more than just UI logic. Developers accidentally leave behind API keys, internal endpoint maps, authentication tokens, and debug information inside these files, assuming minification makes them unreadable. It doesn't. grep a simple, decades-old command-line tool remains one of the most effective weapons a bug bounty hunter has for finding this kind of sensitive information disclosure. This article walks through exactly how to use it, what to look for, and how to avoid wasting time on false positives.
Why JavaScript Files Are a Goldmine
Client-side JavaScript runs entirely in the user's browser, which means anyone can view it there's no server-side protection hiding it. Developers sometimes forget this and:
- Hardcode API keys directly into frontend code because it's just for testing
- Leave internal/staging URLs in production builds
- Expose full API endpoint maps that reveal hidden or undocumented functionality
- Forget to strip debug tokens, Sentry DSNs, or third-party service credentials before deploying
None of this requires exploiting a vulnerability it's sitting in plain text (or lightly obfuscated minified text), waiting to be grepped.
Step 1: Getting the JavaScript File Locally
Before you can grep anything, you need the file on disk. If you already have the URL:
curl -s "https://target.com/static/js/main.abc123.js" -o file.jscurl -s "https://target.com/static/js/main.abc123.js" -o file.jsThe -s flag keeps curl quiet (no progress bar), and -o file.js saves the output to a local file you can now search through repeatedly without re-downloading.
If you want to grab every JS file a page loads at once, tools like GetJS or JSFinder will crawl a page and list every script URL, which you can then feed into a loop (covered later in this article).
Step 2: The Core grep Commands Every Hunter Should Know
Finding keyword-based secrets
This is your first and most important scan โ looking for common naming patterns developers use for sensitive values:
grep -oiE '(api[_-]?key|secret|token|password|bearer|authorization)[^,;"]{0,60}' file.jsgrep -oiE '(api[_-]?key|secret|token|password|bearer|authorization)[^,;"]{0,60}' file.jsBreaking this down:
- -o prints only the matched text, not the whole line (critical for minified files, where a "line" can be 50,000 characters long)
- -i makes it case-insensitive, since developers write apiKey, API_KEY, Api-Key inconsistently
- -E enables extended regex so you can use | for alternation
- [^,;"]{0,60} grabs up to 60 characters of context after the match, so you can see what follows the keyword
Finding real secret formats
Keyword matching alone produces a lot of noise (library code uses words like "token" too). To cut through that, search for the actual structural format that real secrets follow:
grep -oiE 'AKIA[0-9A-Z]{16}' file.js # AWS Access Key
grep -oiE 'sk_live_[0-9a-zA-Z]+' file.js # Stripe live secret key
grep -oiE 'sk_test_[0-9a-zA-Z]+' file.js # Stripe test secret key
grep -oiE 'ghp_[0-9a-zA-Z]+' file.js # GitHub personal access token
grep -oiE 'xox[baprs]-[0-9a-zA-Z-]+' file.js # Slack tokens
grep -oiE 'AIza[0-9A-Za-z_-]{35}' file.js # Google API keygrep -oiE 'AKIA[0-9A-Z]{16}' file.js # AWS Access Key
grep -oiE 'sk_live_[0-9a-zA-Z]+' file.js # Stripe live secret key
grep -oiE 'sk_test_[0-9a-zA-Z]+' file.js # Stripe test secret key
grep -oiE 'ghp_[0-9a-zA-Z]+' file.js # GitHub personal access token
grep -oiE 'xox[baprs]-[0-9a-zA-Z-]+' file.js # Slack tokens
grep -oiE 'AIza[0-9A-Za-z_-]{35}' file.js # Google API keyThese are far more reliable than keyword matching because they check for a fixed prefix and length pattern that legitimate secrets actually follow โ a false positive here is rare.
Finding JWTs (JSON Web Tokens)
JWTs always start with a base64-encoded {"alg":โฆ} header, which comes out to eyJ when base64 encoded. This makes them very easy to fingerprint:
grep -oE 'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' file.jsgrep -oE 'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' file.jsIf you find a match, you can decode it (header and payload, not the signature) at sites like jwt.io โ sometimes JWTs reveal user roles, internal IDs, or even embedded permissions that hint at privilege escalation paths.
Finding internal URLs and infrastructure hints
grep -oiE 'https?://[a-zA-Z0-9./_-]*(internal|staging|dev|admin|test)[a-zA-Z0-9./_-]*' file.jsgrep -oiE 'https?://[a-zA-Z0-9./_-]*(internal|staging|dev|admin|test)[a-zA-Z0-9./_-]*' file.jsThis surfaces internal-only subdomains or environments that were never meant to be publicly known โ sometimes these have weaker security than the production environment.
Combining everything into one scan
Rather than running four separate commands, chain them together so you get a complete picture in one pass:
curl -s "https://target.com/static/js/main.js" -o file.js && \
echo "--- Keywords ---" && grep -oiE '(api[_-]?key|secret|token|password|bearer)[^,;"]{0,60}' file.js && \
echo "--- AWS/Stripe/GitHub ---" && grep -oiE 'AKIA[0-9A-Z]{16}|sk_live_[0-9a-zA-Z]+|ghp_[0-9a-zA-Z]+' file.js && \
echo "--- JWTs ---" && grep -oE 'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' file.jscurl -s "https://target.com/static/js/main.js" -o file.js && \
echo "--- Keywords ---" && grep -oiE '(api[_-]?key|secret|token|password|bearer)[^,;"]{0,60}' file.js && \
echo "--- AWS/Stripe/GitHub ---" && grep -oiE 'AKIA[0-9A-Z]{16}|sk_live_[0-9a-zA-Z]+|ghp_[0-9a-zA-Z]+' file.js && \
echo "--- JWTs ---" && grep -oE 'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' file.jsStep 3: Scanning an Entire Site's JavaScript, Not Just One File
A single JS file rarely tells the whole story. Real recon means pulling every script a target site loads and scanning all of them systematically.
Collect all JS URLs first, using a tool like GetJS:
getJS --url https://target.com --output js_urls.txtgetJS --url https://target.com --output js_urls.txtThen loop through and grep each one:
while read url; do
echo "=== Scanning: $url ==="
curl -s "$url" -o temp.js
grep -oiE '(api[_-]?key|secret|token|bearer)[^,;"]{0,60}' temp.js
grep -oiE 'AKIA[0-9A-Z]{16}|sk_live_[0-9a-zA-Z]+|ghp_[0-9a-zA-Z]+' temp.js
done < js_urls.txtwhile read url; do
echo "=== Scanning: $url ==="
curl -s "$url" -o temp.js
grep -oiE '(api[_-]?key|secret|token|bearer)[^,;"]{0,60}' temp.js
grep -oiE 'AKIA[0-9A-Z]{16}|sk_live_[0-9a-zA-Z]+|ghp_[0-9a-zA-Z]+' temp.js
done < js_urls.txtThis turns a manual, one-file-at-a-time process into something that scans dozens of files in minutes.
Step 4: Deciding Which Files Are Worth Your Time
Not every JS file deserves equal attention. Before grepping, look at the filename itself:
High-priority (check these first):
- config.js, env.js, settings.js โ configuration values are often hardcoded here
- auth.js, login.js, session.js โ authentication logic tends to live here
- main.js, app.js, bundle.js (especially with a company-specific hash) โ this is usually the application's own code
- Any file over ~100โ500KB โ larger files tend to contain more business logic
Low-priority (usually safe to skip):
- vendor.js, chunk-vendors.js, polyfills.js, runtime.js โ these are almost always third-party libraries (React, lodash, etc.) with nothing sensitive inside
- Well-known library filenames like jquery.min.js or react-dom.production.min.js
Always check for .js.map files. If bundle.js exists, try bundle.js.map too:
curl -s "https://target.com/static/js/main.abc123.js.map" -o main.js.mapcurl -s "https://target.com/static/js/main.abc123.js.map" -o main.js.mapA source map, if exposed, reverses minification โ giving you back original variable names, comments, and file structure. It's one of the single highest-value finds in JS recon, because it turns unreadable minified code into something you can actually read line by line.
Step 5: Telling Real Secrets Apart from False Positives
This is where most beginners get tripped up. Minified JavaScript is full of library code that uses words like "token" or "secret" without actually containing one. For example, drag-and-drop libraries define functions like tokenSeparator or tokenize, and prop-types validation libraries use internal strings like SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED โ neither of these is a real leaked secret.
Signs you've found a real secret:
- A long, random-looking alphanumeric string (20+ characters) assigned directly to a variable: apiKey: "AIzaSyD8f7h2โฆ"
- It matches one of the known prefix formats (AKIA, sk_live_, ghp_, etc.)
- It sits next to words like "prod", "live", or a real domain name, rather than inside generic library function names
Signs it's a false positive:
- The match is a function or variable name (tokenize, getAPIKey) rather than a value
- It appears inside a recognizable third-party library (React, Stripe SDK, prop-types) rather than app-specific code
- The "token" is actually referring to a UI concept (like a design token or a parsing token), not an authentication credential
When in doubt, look at the surrounding 50โ100 characters of context โ that's usually enough to tell whether you're looking at a real secret or normal code.
Step 6: What to Do If You Find Something
- Never test the credential by making live API calls unless the program's scope explicitly allows it โ using a found secret without authorization can cross legal lines even in a bug bounty context.
- Document exactly where you found it โ the file URL, the line/context, and a screenshot.
- Assess real impact before reporting โ a public, read-only, rate-limited API key is very different from a live payment-processor secret key with write access.
- Report responsibly through the program's official channel, and let their team confirm and rotate the credential.
A Reusable Scanning Checklist
- Collect all JS URLs from the target (manually or with GetJS/JSFinder)
- Check for .js.map files alongside each .js file
- Skip vendor/library files, prioritize app-specific bundles
- Run keyword-based grep (api_key, secret, token, bearer)
- Run format-based grep (AWS, Stripe, GitHub, Slack, Google patterns)
- Run JWT pattern grep and decode any matches at jwt.io
- Search for internal/staging/admin subdomain references
- Verify each match manually before reporting โ check for false positives
- Document impact clearly in your report
Closing Thought
grep is not a fancy tool, and that's exactly why it's so effective it's fast, precise once you know the right patterns, and works on any machine without setup. Pair it with a source-map check and a filename-based triage system, and you'll consistently catch sensitive information disclosure bugs that automated scanners often miss because they don't understand context the way a human eye reading grep output does.