Build the mental model
API Tutorial taught you to attach an API key to a request. It did not tell you where that key should physically live, and getting this wrong is one of the most common real-world security failures.
Writing `const apiKey = "sk_live_abc123"` directly in frontend or mobile app code means the key ships inside the JavaScript bundle or app binary that runs on every user's device. Anyone can open browser devtools and copy the secret key out in seconds.
Minification is not encryption
Minifying code makes it harder to read, but any secret string inside is still plain text in the bundle -- searchable and copyable.
The correct architecture adds a layer: the browser talks to your own backend server, and only your backend holds the real secret key and talks to the external API. Your backend becomes the trusted boundary.
Environment variables are the standard way to hand that secret to your backend without hardcoding it into source files. That `.env` file must be listed in `.gitignore` -- a secret committed to Git stays recoverable from history forever, even after you delete the line.
- Secret
- A private value, like an API key, password, or token, that grants access -- if it becomes public, it can be misused.
- Environment Variable
- A configuration value stored outside the codebase and handed to a process at runtime -- the standard way to supply secrets without hardcoding them.
SECRET KEY: GOOD PATH VS BAD PATH
---------------------------------
GOOD:
Browser --> Your Backend --> [ Secret Key (env var) ] --> External API
BAD (do not do this):
Browser --> [ Secret Key hardcoded in JS ] --> External API
X-- anyone can read this in devtools --XConnect it to a real scenario
The scanner below looks for a common, careless pattern: a `const`, `let`, or `var` declaration whose variable name contains "key", "secret", or "token", assigned directly to a quoted string literal of at least twelve characters.
Run it against two snippets. The risky one hardcodes an API key directly above a `fetch` call, and the scanner flags exactly that line. The clean version reads the key from `process.env.API_KEY` instead, and the scanner correctly returns an empty array.
A tool like this is exactly what real teams wire into a pre-commit hook or CI pipeline, catching an accidentally hardcoded key before it ever reaches shared Git history, where removing it later is far harder than never committing it.
Before Committing Code
Try the working example
function scanForHardcodedSecrets(sourceCode) {
const pattern = /(const|let|var)\s+(\w*(?:key|secret|token)\w*)\s*=\s*["']([A-Za-z0-9_\-]{12,})["']/gi;
const findings = [];
let match;
while ((match = pattern.exec(sourceCode)) !== null) {
findings.push({ variable: match[2], flagged: true, snippet: match[0] });
}
return findings;
}
const risky = `
const apiKey = "sk_live_51H8x9aBcDeFgHiJkLmNoP";
const userName = "guest";
fetch("https://api.example.com/charge", { headers: { Authorization: apiKey } });
`;
const clean = `
const apiKey = process.env.API_KEY;
const userName = "guest";
fetch("https://api.example.com/charge", { headers: { Authorization: apiKey } });
`;
console.log("Risky file findings:", scanForHardcodedSecrets(risky));
console.log("Clean file findings:", scanForHardcodedSecrets(clean));Risky file findings: [{ variable: 'apiKey', flagged: true, snippet: 'const apiKey = "sk_live_51H8x9aBcDeFgHiJkLmNoP"' }]
Clean file findings: []5-minute try-it
Run `scanForHardcodedSecrets` against a new snippet containing `const dbPassword = "SuperSecret2024!"` and explain why it does or does not get flagged.
One important caution
Forgetting to add .env to .gitignore -- once committed, cleaning it from history is hard
Using a secret as a public-prefixed frontend env variable (like NEXT_PUBLIC_) -- it still ends up baked into the bundle
About secret scanning - GitHub Docs — API Integration & Webhooks