7 min read

Secrets Exposure

Hardcoded credentials, API keys, tokens, and other secrets in web applications represent one of the most common and impactful vulnerabilities. Secrets can be found in JavaScript source, HTML comments, exposed configuration files, git repositories, and API responses.

Danger

A single exposed AWS key can lead to complete cloud infrastructure compromise in minutes. Automated bots scan GitHub and public sites for leaked secrets 24/7.

Frameworks: WSTG-CONF-04, WSTG-INFO-05 · ASVS 5.0 V13.3.1, V14.1.1 · CWE-798, CWE-312 · T1552.001

What Makes a Secret a Finding

A secret is a finding when three things line up: it is a credential rather than an identifier, it is reachable by the audience you are testing as, and it still works. Most of the work is the second and third. A key in a bundle every visitor downloads is reachable; the same string in a server-side log is not, unless the log is exposed. A key that has been rotated is evidence of exposure, not of access.

Where Secrets Actually Leak

Four artefacts account for most real leaks. Build output: framework-prefixed variables (NEXT_PUBLIC_, VITE_, PUBLIC_ in Astro) are inlined at build time by design, so anything named that way ships to the browser. Source maps: a .map with sourcesContent reconstructs the modules, comments and hard-coded fallbacks included. CI logs: a masked variable leaks through set -x, an env dump, or a tool that prints its configuration in an error. Git history: a key deleted in the next commit is still in the object store, and so is every fork.

Name the retrieval step, because each needs a different one: a bundle is a request, a map needs the sourceMappingURL, CI logs need pipeline access the client must grant, and history needs the repository or a dumpable .git.

Client-Side Secret Discovery

client-side-secret-scan.sh
# Search JavaScript files for secrets:
curl -s https://target.com/js/app.js | grep -iE \
  'api[_-]?key|apikey|secret|token|password|passwd|auth|credential|private[_-]?key'

# Search all JS files:
curl -s https://target.com/sitemap.xml | grep -oP 'https?://[^<]+\.js' | while read url; do
  echo "--- $url ---"
  curl -s "$url" | grep -inE 'api[_-]?key|secret|token|password|aws|firebase'
done

# Check HTML source for comments with secrets:
curl -s https://target.com | grep -iE '<!--.*(?:password|secret|key|token|TODO|FIXME|HACK)'

# Check for hardcoded Firebase config:
curl -s https://target.com | grep -oE 'AIza[0-9A-Za-z_-]{35}'

# LinkFinder - extract hidden URLs and parameters from JS:
python3 linkfinder.py -i https://target.com/js/app.js -o cli

Source maps are another client-side artefact that can reveal original code; recognising and fetching them is covered on the Information Disclosure guide. → Source-code exposure (canonical guide)

Server-Side Secret Discovery

Server-side secrets most often leak through exposed environment and configuration files — .env, appsettings.json, application.properties, docker-compose.yml. The enumeration mechanics for reaching them (probing .env, backup files, debug panels, and framework endpoints such as Spring Actuator) are documented once on the Information Disclosure guide; this page keeps its focus on triaging what those files expose.

→ Debug endpoints & hidden files (canonical guide)

Git Repository Mining

An exposed .git/ directory lets you reconstruct the full source tree, and — more relevant here — secrets that were committed and later deleted still live in history. Detecting and dumping the repository (/.git/HEAD, git-dumper) is covered on the Information Disclosure guide; the secret-specific step is scanning the recovered history with trufflehog or gitleaks, then reading deleted files with git show <commit>:.env.

→ Source-code exposure (canonical guide)

Common Secret Patterns (Regex)

AWS Access Key: AKIA[0-9A-Z]{16}

AWS Secret Key: [0-9a-zA-Z/+]{40}

GitHub Token: gh[opsru]_[A-Za-z0-9_]{36,}

Slack Token: xox[baprs]-[0-9a-zA-Z-]{10,}

Google API Key: AIza[0-9A-Za-z_-]{35}

Stripe Key: sk_live_[0-9a-zA-Z]{24,}

JWT Token: eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*

Private Key: -----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----

Generic Password: (?i)(password|passwd|pwd)\s*[=:]\s*['"'][^'"']{4,}

Triaging a Candidate Without Exfiltrating It

Identify the type from the prefix — AWS AKIA, GitHub ghp_ or github_pat_, Stripe sk_live_, Slack xoxb- — because the prefix tells you what a harmless verification looks like and whether it is a credential at all. Then confirm liveness with the least invasive call the provider offers: an identity endpoint or a token-info call, never a list or a write. Record the status and the account it belongs to, redact the value, and stop.

Some candidates are not secrets. A Firebase API key identifies a project and is constrained by security rules; a Stripe publishable key is designed to ship; a Supabase anon key is public and protected by row-level security. Report those as a configuration question — are the rules restrictive? — not as a credential leak.

triage-secret.sh
# Record a fingerprint, never the value
key="$(cat candidate.txt)"
printf 'prefix=%s length=%s tail=%s\n' "${key:0:4}" "${#key}" "${key: -4}"
printf '%s' "$key" | sha256sum | cut -c1-12

# Least-invasive liveness check: identity only, no data access
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Authorization: Bearer $key" https://api.github.com/user
# 200 = live, 401 = revoked. Do not enumerate repositories with it.

Testing Checklist

  • ☐ 1. Search all JavaScript files for API keys, tokens, and credentials
  • ☐ 2. Check for source maps and download original source code
  • ☐ 3. Check HTML comments for credentials and internal URLs
  • ☐ 4. Probe for .env, config files, and backup files
  • ☐ 5. Check for exposed .git repository and mine commit history
  • ☐ 6. Check API responses for excessive data (tokens, internal IDs)
  • ☐ 7. Run truffleHog or gitleaks on any exposed source code
  • ☐ 8. Verify found secrets are active (but minimize scope — only confirm, don't exploit)
  • ☐ 9. Check framework-prefixed variables in the bundle (NEXT_PUBLIC_, VITE_, PUBLIC_) — they are inlined by design
  • ☐ 10. Grep CI logs for masked values echoed by set -x, env dumps, or tool error output
  • ☐ 11. Record a fingerprint, verify with an identity endpoint, then stop

Evidence Collection

Secret Location: URL, file, or endpoint where the secret was found

Secret Type: API key, database password, private key, etc.

Verification: Proof the secret is active (redact the actual secret in your report)

Fingerprint: Prefix, length and a truncated hash of the value — enough for the client to locate it without the report becoming another leak

Reachability: The unauthenticated request that fetched the artefact (bundle URL, map, log, repository path) and its status code

CVSS Range: Read-only API key: 5.3–6.5 | Admin credentials: 9.1–9.8 | Cloud keys: 9.1–10.0

Remediation

  • • Rotate immediately: Any exposed secret must be revoked and rotated, not just removed from code.
  • • Environment variables: Store secrets in environment variables or a secrets manager (Vault, AWS Secrets Manager).
  • • Pre-commit hooks: Use tools like git-secrets, detect-secrets, or gitleaks as pre-commit hooks.
  • • CI/CD scanning: Integrate secret scanning in the build pipeline (GitHub Secret Scanning, GitLab Secret Detection).
  • • Block .env/.git: Configure web server to deny access to sensitive files and directories.
  • • Treat build-time prefixes as public: A variable named NEXT_PUBLIC_, VITE_ or PUBLIC_ is compiled into the bundle. No privileged key belongs behind one.
  • • Rotate before rewriting history: Removing the commit does not remove the object, the clone, the fork or the CI cache. Rotation is the fix; the rewrite is tidying.
  • • Masking is not redaction: Secret masks in CI only cover literal echoes. set -x, env, and tools that print their configuration in a failure bypass them.

False Positive Identification

  • Example/placeholder keys: Strings like AKIAIOSFODNN7EXAMPLE or test-api-key-1234 are documentation examples, not real secrets — verify the key is valid by testing it (with authorization).
  • Revoked credentials: Exposed secrets in old git commits may already be rotated — note the exposure but verify current validity before rating severity.
  • Public API keys: Some API keys are designed to be public (e.g., Google Maps browser keys with domain restrictions) — check if the key has usage restrictions configured.
  • OAuth client identifiers: A client_id is not a client_secret. Finding an identifier in a bundle is expected; the finding is a secret that authenticates, or a redirect URI that accepts anything.
  • Expired or scoped to nothing: A live-looking key with no permissions or an expired credential still proves exposure but not access. Check the identity response before assigning impact.

Secrets Exposure Practice

Recover credentials and keys from exposed build artefacts and responses.

PortSwigger — Information Disclosure Labs PortSwigger easy
Source map reviewExposed .git / .envHardcoded keys