---
name: Hardcoded Secrets Detection
slug: hardcoded-secrets-detection
category: Quality
description: "Detects hardcoded secrets like API keys, tokens, and passwords in publicly accessible code (frontend JS, mobile apps, HTML). Employs a three-phase process: recon, batch verification, and merge reporting. Ideal for security audits to prevent credential leaks."
github: "https://github.com/utkusen/sast-skills/tree/main/sast-files/.agents/skills/sast-hardcodedsecrets"
stars: 1225
forks: 60
install: "git clone https://github.com/utkusen/sast-skills"
added: 2026-07-20T06:49:09.766Z
last_synced: 2026-07-28T07:01:08.522Z
canonical_url: "https://dirskills.com/skills/hardcoded-secrets-detection"
---

# Hardcoded Secrets Detection

Detects hardcoded secrets like API keys, tokens, and passwords in publicly accessible code (frontend JS, mobile apps, HTML). Employs a three-phase process: recon, batch verification, and merge reporting. Ideal for security audits to prevent credential leaks.

**Install:** `git clone https://github.com/utkusen/sast-skills`

## README

# Hardcoded Secrets in Public Code Detection

You are performing a focused security assessment to find hardcoded sensitive data that is exposed in publicly accessible code. This skill uses a three-phase approach with subagents: **recon** (find all potential secret candidates), **batched verify** (confirm each is a real secret in publicly reachable code, in parallel batches of 3), and **merge** (consolidate batch reports into one file).

**Prerequisites**: `sast/architecture.md` must exist. Run the analysis skill first if it doesn't.

---

## What Are Hardcoded Secrets in Public Code

Hardcoded secrets are sensitive credentials — API keys, access tokens, private keys, passwords, signing secrets, database connection strings — embedded directly in source code as string literals.

This skill focuses specifically on secrets that end up in **publicly accessible code**, meaning an attacker can extract them **without any server-side access**. A secret hardcoded in backend server code is bad practice but not directly exploitable by an external attacker inspecting the deployed application. A secret hardcoded in frontend JavaScript or a mobile app binary **is** directly extractable.

The core question: *Can an external attacker obtain this secret from the deployed application without server access?*

### What to Report (Publicly Accessible Code)

These code paths are accessible to attackers after deployment:

- **Frontend JavaScript/TypeScript** — any `.js`, `.ts`, `.jsx`, `.tsx` file that runs in the browser. This includes:
  - React, Angular, Vue, Svelte components and pages
  - Next.js client components (files with `"use client"` or files under `app/` without `"use server"`)
  - Nuxt.js pages and client plugins
  - Vanilla JS in `public/`, `static/`, or `assets/` directories
  - Webpack/Vite/Rollup entry points and their imported modules
  - Any file imported by a client-side entry point (even if it lives in a `utils/` or `lib/` folder)
- **Mobile application code** — extractable via reverse engineering (decompiling APK, inspecting IPA):
  - Android: Java/Kotlin source files
  - iOS: Swift/Objective-C source files
  - React Native: JavaScript bundles
  - Flutter: Dart source files
  - Xamarin: C# source files
- **HTML files and templates served to clients** — inline `<script>` blocks, `data-` attributes, meta tags
- **Client-side configuration files** — files in `public/`, `static/`, `assets/`, `www/` directories
- **Electron/desktop app source** — extractable from ASAR archives
- **WebAssembly source/companion JS** — secrets in JS glue code or extractable from WASM

### What NOT to Report (Backend-Only Code)

Do not flag secrets in these locations — they are not publicly accessible:

- **Server-side application code** — Express route handlers (server-only), Django views, Flask routes, Spring controllers, Rails controllers, Go HTTP handlers, PHP controllers — code that runs exclusively on the server
- **Server-side API route files** — Next.js `app/api/` routes, Nuxt server routes, SvelteKit `+server.ts` files
- **Environment files** — `.env`, `.env.local`, `.env.production` (unless served statically)
- **Server-side configuration** — `config/database.yml`, `settings.py`, `application.properties`, `appsettings.json`
- **CI/CD pipeline files** — `.github/workflows/`, `Jenkinsfile`, `.gitlab-ci.yml`
- **Docker/infrastructure files** — `Dockerfile`, `docker-compose.yml`, Kubernetes manifests
- **Backend utility/service files** — files that are only imported by server-side code
- **Test files** — test fixtures and test configuration (unless the test files are shipped to the client)
- **Migration files** — database migrations

### Distinguishing Frontend from Backend

This is critical and requires understanding the project architecture:

**Next.js**: Files under `app/` with `"use client"` directive or without `"use server"` are client components. Files under `app/api/` are server-only. Files under `pages/api/` are server-only. Files under `pages/` (non-api) render on both server and client — secrets here ARE exposed. `next.config.js` runs server-side only but `NEXT_PUBLIC_*` env vars are embedded in client bundles.

**Nuxt.js**: Files under `pages/`, `components/`, `composables/` are client-accessible. Files under `server/` are server-only.

**React (CRA/Vite)**: Everything in `src/` is bundled for the client. `REACT_APP_*` and `VITE_*` env vars are embedded in client builds.

**Angular**: Everything in `src/` is bundled for the client.

**Vue (Vite)**: Everything in `src/` is bundled for the client. `VITE_*` env vars are embedded.

**Express/Fastify/Koa**: All server-side unless serving static files from a `public/` or `static/` directory.

**Django/Flask**: Python code is server-side. Templates are rendered server-side (secrets in template context don't reach the client unless explicitly rendered into JS). Static files in `static/` are client-accessible.

**Rails**: Ruby code is server-side. Assets in `app/assets/javascripts/` or `app/javascript/` are client-accessible.

**Mobile apps**: ALL source code is considered publicly accessible via reverse engineering.

---

## Types of Secrets to Look For

### High-Confidence Patterns (Regex-Identifiable)

These have distinctive formats that make them identifiable with high confidence:

| Secret Type | Pattern |
|---|---|
| AWS Access Key ID | `AKIA[0-9A-Z]{16}` |
| AWS Secret Access Key | 40-character base64 string near an `AKIA` key |
| Google API Key | `AIza[0-9A-Za-z\\-_]{35}` |
| Google OAuth Client Secret | `GOCSPX-[0-9A-Za-z\\-_]{28}` |
| GitHub Personal Access Token | `ghp_[0-9A-Za-z]{36}`, `github_pat_[0-9A-Za-z_]{82}` |
| GitHub OAuth App Secret | `gho_[0-9A-Za-z]{36}` |
| GitLab Personal Access Token | `glpat-[0-9A-Za-z\\-_]{20}` |
| Slack Bot/User Token | `xoxb-[0-9A-Za-z\\-]+`, `xoxp-[0-9A-Za-z\\-]+` |
| Slack Webhook URL | `hooks.slack.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+` |
| Stripe Secret Key | `sk_live_[0-9A-Za-z]{24,}` |
| Stripe Publishable Key | `pk_live_[0-9A-Za-z]{24,}` (publishable keys are designed for client-side — skip unless paired with a secret key) |
| Twilio Account SID + Auth Token | `AC[0-9a-f]{32}` (SID), 32-hex auth token nearby |
| SendGrid API Key | `SG\.[0-9A-Za-z\\-_]{22}\.[0-9A-Za-z\\-_]{43}` |
| Mailgun API Key | `key-[0-9a-zA-Z]{32}` |
| Firebase Config | `apiKey`, `authDomain`, `projectId` together in a config object — only flag if it includes a server/admin key, not the standard client config |
| Private RSA/EC/SSH Key | `-----BEGIN (RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----` |
| JWT Secret / Signing Key | String assigned to variables like `JWT_SECRET`, `jwt_secret`, `signingKey`, `HMAC_KEY` |
| Database Connection String with Password | `postgresql://user:pass@`, `mysql://user:pass@`, `mongodb://user:pass@`, `redis://:pass@` |
| Generic API Key Assignment | Variable named `*api_key*`, `*apiKey*`, `*API_KEY*`, `*secret*`, `*SECRET*`, `*token*`, `*TOKEN*`, `*password*`, `*PASSWORD*` assigned a string literal that looks like a real credential |
| Heroku API Key | `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}` in a Heroku context |
| Azure Storage Key | Base64 string ~88 chars assigned to storage account key variables |
| OpenAI API Key | `sk-[A-Za-z0-9]{48}` or `sk-proj-[A-Za-z0-9\\-_]{100,}` |
| Anthropic API Key | `sk-ant-[A-Za-z0-9\\-_]{90,}` |

### Variable Name Patterns (Require Value Inspection)

Search for variables/constants with these name patterns and check if the assigned value looks like a real credential:

- `api_key`, `apiKey`, `API_KEY`, `ApiKey`
- `secret`, `SECRET`, `secret_key`, `secretKey`, `SECRET_KEY`
- `access_token`, `accessToken`, `ACCESS_TOKEN`
- `auth_token`, `authToken`, `AUTH_TOKEN`
- `private_key`, `privateKey`, `PRIVATE_KEY`
- `password`, `PASSWORD`, `passwd`, `PASSWD`
- `client_secret`, `clientSecret`, `CLIENT_SECRET`
- `signing_key`, `signingKey`, `SIGNING_KEY`
- `encryption_key`, `encryptionKey`, `ENCRYPTION_KEY`
- `bearer_token`, `BEARER_TOKEN`
- `credentials`, `CREDENTIALS`
- `connection_string`, `connectionString`, `DATABASE_URL`

### What is NOT a Real Secret (False Positives to Ignore)

- **Placeholder values**: `"your-api-key-here"`, `"TODO"`, `"xxx"`, `"changeme"`, `"REPLACE_ME"`, `"INSERT_KEY"`, `"<api_key>"`, `"dummy"`, `"test"`, `"example"`, `"sample"`, `"placeholder"`
- **Empty strings**: `""`, `''`
- **Environment variable references**: `process.env.API_KEY`, `os.environ["SECRET"]`, `ENV["KEY"]` — these read from the environment at runtime, not hardcoded
- **Public keys**: Public keys (not private) are designed to be shared — not a secret
- **Publishable/public API keys**: Stripe `pk_test_*`, `pk_live_*`; Firebase client config `apiKey` (designed for client-side use); Google Maps client key (restricted by HTTP referrer)
- **Test/development keys**: `sk_test_*` (Stripe test), keys in files clearly named as test fixtures
- **Type definitions / interfaces**: TypeScript `interface Config { apiKey: string }` — no actual value
- **Documentation strings**: Comments explaining what a key looks like
- **Hash values**: SHA256/MD5 hashes that are not secrets (e.g., content hashes, checksums)
- **Build-time constants**: Version strings, build IDs, commit hashes

---

## Execution

This skill runs in three phases using subagents. Pass the contents of `sast/architecture.md` to all subagents as context.

### Phase 1: Recon — Find Secret Candidates

Launch a subagent with the following instructions:

> **Goal**: Find every location in the codebase where a hardcoded secret (API key, access token, private key, password, signing secret, connection string) appears as a string literal. Write results to `sast/hardcodedsecrets-recon.md`.
>
> **Context**: You will be given the project's architecture summary. Use it to understand the tech stack, project structure, and which files are frontend vs. backend.
>
> **What to search for**:
>
> Scan the entire codebase. At this stage, flag ALL potential secrets regardless of whether they are in frontend or backend code — the filtering happens in Phase 2.
>
> 1. **High-confidence regex patterns** — search for these distinctive formats:
>    - AWS keys: `AKIA[0-9A-Z]{16}`
>    - Google API keys: `AIza[0-9A-Za-z\-_]{35}`
>    - GitHub tokens: `ghp_`, `github_pat_`, `gho_`, `ghs_`
>    - Slack tokens: `xoxb-`, `xoxp-`, `xoxa-`, `xoxr-`
>    - Stripe secret keys: `sk_live_`, `sk_test_`
>    - SendGrid keys: `SG\.`
>    - OpenAI keys: `sk-` followed by 48+ alphanumeric characters
>    - Anthropic keys: `sk-ant-`
>    - Private key headers: `-----BEGIN.*PRIVATE KEY-----`
>    - Connection strings with embedded passwords: `://[^:]+:[^@]+@`
>
> 2. **Variable assignment patterns** — search for variables with secret-related names assigned string literal values:
>    - Search for patterns like: `apiKey = "..."`, `api_key = '...'`, `API_KEY: "..."`, `secret: "..."`, `token = "..."`, `password = "..."`, `client_secret = "..."`
>    - Include all casing conventions: camelCase, snake_case, SCREAMING_SNAKE_CASE, PascalCase
>    - Look in JS/TS objects, JSON files, YAML/TOML config, Python dicts, environment-like configs
>
> 3. **Inline string literals** that match known key formats:
>    - Long random alphanumeric strings (32+ characters) assigned to auth-related variables
>    - Base64-encoded strings in authentication contexts
>    - Hex strings (64+ characters) used as keys or secrets
>    - UUIDs used as API keys or secrets
>
> **What to skip during recon**:
> - Environment variable reads: `process.env.*`, `os.environ[*]`, `ENV[*]`, `System.getenv(*)` — these are not hardcoded
> - Type definitions with no values: `apiKey: string`, `type Config = { secret: string }`
> - Obvious placeholders: `"your-key-here"`, `"TODO"`, `"xxx"`, `"changeme"`, `"REPLACE_ME"`, `"<api_key>"`, `"dummy"`, `"test-key"`, `"example"`, `"sample"`, empty strings
> - Comments that merely describe or document secrets
> - Public keys (non-private cryptographic keys)
> - Hash values used as checksums or content identifiers
> - Files in `.git/`, `node_modules/`, `vendor/`, `venv/`, `__pycache__/`, `dist/`, `build/` directories
>
> **Output format** — write to `sast/hardcodedsecrets-recon.md`:
>
> ```markdown
> # Hardcoded Secrets Recon: [Project Name]
>
> ## Summary
> Found [N] potential hardcoded secret candidates.
>
> ## Candidates
>
> ### 1. [Descriptive name — e.g., "AWS Access Key in API config"]
> - **File**: `path/to/file.ext` (lines X-Y)
> - **Secret type**: [AWS key / Google API key / Generic API key / Private key / Password / JWT secret / Connection string / etc.]
> - **Variable/context**: [variable name or context where the secret appears]
> - **Detection method**: [regex match / variable name pattern / inline literal]
> - **Code snippet**:
>   ```
>   [Show the line(s) containing the secret — REDACT the middle portion of the actual value, e.g., "AKIA****EXAMPLE" or "sk_live_****abcd"]
>   ```
>
> [Repeat for each candidate]
> ```

### After Phase 1: Check for Candidates Before Proceeding

After Phase 1 completes, read `sast/hardcodedsecrets-recon.md`. If the recon found **zero candidates** (the summary reports "Found 0" or the "Candidates" section is empty or absent), **skip Phase 2 and Phase 3 entirely**. Instead, write the following content to `sast/hardcodedsecrets-results.md` and stop:

```markdown
# Hardcoded Secrets Analysis Results

No vulnerabilities found.
```

Only proceed to Phase 2 if Phase 1 found at least one candidate.

### Phase 2: Verify — Confirm Real Secrets in Public Code (Batched)

After Phase 1 completes, read `sast/hardcodedsecrets-recon.md` and split the candidates into **batches of up to 3 candidates each**. Launch **one subagent per batch in parallel**. Each subagent verifies only its assigned candidates and writes results to its own batch file.

**Batching procedure** (you, the orchestrator, do this — not a subagent):

1. Read `sast/hardcodedsecrets-recon.md` and count the numbered candidate sections (### 1., ### 2., etc.).
2. Divide them into batches of up to 3. For example, 8 candidates -> 3 batches (1-3, 4-6, 7-8).
3. For each batch, extract the full text of those candidate sections from the recon file.
4. Launch all batch subagents **in parallel**, passing each one only its assigned candidates.
5. Each subagent writes to `sast/hardcodedsecrets-batch-N.md` where N is the 1-based batch number.

Give each batch subagent the following instructions (substitute the batch-specific values):

> **Goal**: Verify the following hardcoded secret candidates. For each one, determine (1) whether it is a real secret and (2) whether it is in publicly accessible code. Write results to `sast/hardcodedsecrets-batch-[N].md`.
>
> **Your assigned candidates** (from the recon phase):
>
> [Paste the full text of the assigned candidate sections here, preserving the original numbering]
>
> **Context**: You will be given the project's architecture summary. Use it to understand the tech stack, frontend/backend separation, build pipeline, and which directories contain client-side vs. server-side code.
>
> **For each candidate, answer TWO questions:**
>
> **Question 1: Is this a real secret?**
>
> Check whether the value is an actual credential vs. a false positive:
> - Does the string have the entropy and format of a real key/token? (Real API keys are typically 20+ random characters)
> - Is it a known placeholder or example value? ("your-key-here", "changeme", "test", "example", "TODO", "xxx", "REPLACE_ME", etc.)
> - Is it a test/development key? (Stripe `sk_test_*`, sandbox credentials, keys in test fixtures)
> - Is it a public/publishable key by design? (Stripe `pk_live_*`, Firebase client `apiKey`, Google Maps browser key)
> - Is it actually an environment variable reference that got picked up by mistake?
> - Is it a hash, checksum, or non-secret identifier?
>
> If the value is NOT a real secret, classify as **Not Vulnerable** and explain why.
>
> **Question 2: Is this in publicly accessible code?**
>
> Determine whether an external attacker can extract this secret from the deployed application:
>
> **PUBLICLY ACCESSIBLE (report these):**
> - Frontend JavaScript/TypeScript that runs in the browser (React, Angular, Vue, Svelte components/pages)
> - Next.js client components (files with `"use client"` or client-rendered pages)
> - Nuxt.js `pages/`, `components/`, client-side `plugins/`
> - Any `.js`/`.ts` file that is imported by a client-side entry point (trace the import chain)
> - Files in `public/`, `static/`, `assets/`, `www/` directories that are served directly
> - HTML files with inline `<script>` blocks
> - Mobile app source code — Android (Java/Kotlin), iOS (Swift/Objective-C), React Native JS, Flutter Dart, Xamarin C# — ALL mobile code is extractable via reverse engineering
> - Electron app source (extractable from ASAR)
> - Client-side configuration objects embedded in JavaScript (e.g., Firebase config, analytics init)
>
> **NOT PUBLICLY ACCESSIBLE (do not report):**
> - Server-side route handlers (Express, Django, Flask, Rails, Spring, Go, PHP controllers)
> - Server-side API routes (Next.js `app/api/`, Nuxt `server/`, SvelteKit `+server.ts`)
> - Backend services, middleware, utilities only imported by server code
> - `.env` files, server config files, Docker/CI files
> - Test files and fixtures not shipped to clients
> - Database migrations
> - Build scripts and tooling
>
> **How to determine if a file is client-side:**
> 1. Check the file path — is it under a client-side directory? (`src/` in CRA/Vite React, `pages/` in Next.js, `app/` in Angular, `src/` in Vue)
> 2. Trace the import chain — is this file imported (directly or transitively) by a client-side entry point?
> 3. Check for server-only markers — `"use server"` directive, file under `api/` or `server/` directories
> 4. Check `sast/architecture.md` for the project's frontend/backend separation pattern
> 5. For ambiguous cases (e.g., shared utility files), err on the side of caution — if it COULD be bundled for the client, treat it as publicly accessible
>
> If the secret is NOT in publicly accessible code, classify as **Not Vulnerable** and explain why (e.g., "Server-side only — Express route handler").
>
> **Classification**:
> - **Vulnerable**: Confirmed real secret in confirmed publicly accessible code. An attacker can extract this from the deployed application.
> - **Likely Vulnerable**: Appears to be a real secret and the file is likely client-accessible, but cannot fully confirm one or both conditions (e.g., ambiguous import chain, uncertain if the value is a real production key).
> - **Not Vulnerable**: Either not a real secret (placeholder, test key, public key) OR not in publicly accessible code (backend-only).
> - **Needs Manual Review**: Cannot determine if the value is a real secret or if the file reaches the client — requires human judgment.
>
> **Output format** — write to `sast/hardcodedsecrets-batch-[N].md`:
>
> ```markdown
> # Hardcoded Secrets Batch [N] Results
>
> ## Findings
>
> ### [VULNERABLE] Descriptive name
> - **File**: `path/to/file.ext` (lines X-Y)
> - **Secret type**: [AWS key / Google API key / etc.]
> - **Exposure path**: [How an attacker extracts it — e.g., "Bundled into client JS via Webpack, visible in browser DevTools Sources tab" or "Embedded in Android APK, extractable via `apktool d app.apk`"]
> - **Issue**: [Clear description — e.g., "AWS access key hardcoded in React component that is bundled for the browser"]
> - **Impact**: [What an attacker can do with this secret — e.g., "Full access to AWS S3 buckets, potential data exfiltration", "Send emails via SendGrid on behalf of the organization", "Access user data via the API"]
> - **Evidence**:
>   ```
>   [Code snippet with the secret value partially redacted]
>   ```
> - **Remediation**: [Move the secret to a server-side environment variable. If the client needs to call this API, proxy through your backend
