Secrets in Plain Sight: How One Line of Code Can Sink a Project
Hardcoded credentials are a footgun with a long fuse. Here is why β and the dead-simple env-var discipline that keeps your keys, your budget, and your sanity intact.
Friday, 6 PM. The deploy is due, the coffee has gone cold. A dev wires up a new integration, drops the API key straight into a config file "just to see if it works", commits, and clocks out. Monday morning: revoked credentials, a data leak, and a cloud bill with more digits than a phone number.
The cause is almost boringly simple β the secret went into the code, and from there into Git history forever.
At PolyCode Systems we live in complex infrastructure, marketplace integrations, and multi-agent systems all day. The blast radius of a mistake is huge. So rule number one, no exceptions: secrets never go in the code. Not "just for five minutes". Not "only for local testing". Here is why hardcoding credentials is always a slow-motion disaster β and how we avoid it.
Why "It Is Fine" Is the Most Expensive Phrase in Engineering
"It is a private repo." "I will comment it out before I push." Famous last words. Three traps catch teams again and again:
- Git has a perfect memory. Commit a database password or a payment key by accident, and deleting the file in your next commit fixes nothing β the secret lives in history forever. An attacker (or a bored intern with git log) just has to scroll back.
- The copy-paste trap. Hardcoded config turns every promotion from dev to staging to prod into a minefield. You are swapping keys by hand on each deploy, and one fat-fingered value means your test server is suddenly billing the production account real money.
- Death by screenshot and log. Hardcoded values leak into error logs, stack traces, and the projector during a pairing session. Environment variables never show up in the code β they live at the server level, out of sight.
The PolyCode Playbook for Secrets
Our process is simple and non-negotiable. No pricey enterprise vault required β just discipline you actually stick to.
- A .env file exists; it is never committed. Every repo ships a .env.example β a template listing every variable you need (DB_HOST=, STRIPE_API_KEY=) with the values left blank. You copy it to .env, fill in your own keys, and .env itself stays in .gitignore forever. No exceptions.
- The code does not know where it lives. It just asks the platform: "give me the database token." Locally that comes from your .env; in production from the host environment settings (Coolify, your orchestrator, plain Linux env β whatever you run).
- Secrets ride the pipeline, not the repo. CI/CD injects them from a secure store (GitHub Secrets, your vault) straight into the build. Config never travels in a zip file or a Slack DM.
What This Looks Like in Code
Here is the anti-pattern almost everyone writes first:
The footgun
// β How NOT to do it (a straight path to grey hair)
export const dbConfig = {
host: "192.168.1.100",
user: "admin",
password: "SuperSecretPassword123" // Hello, public repository!
};The obvious fix is swapping the string for process.env.PASSWORD β but that is only half the job. Reading env vars is easy; trusting them blindly is the new bug. The other half is validating them at startup and failing fast. If the database URL is missing, the app should refuse to boot β a loud crash at deploy time beats a quiet one throwing 500s at real users. This zod pattern is our standard:
The standard: validate, then trust
// β
Gold standard: validate the environment
import { z } from 'zod';
// 1. Declare the variables we truly need
const envSchema = z.object({
DATABASE_URL: z.string().url(),
API_SECRET_KEY: z.string().min(10),
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
});
// 2. Validate whatever the system gave us
const env = envSchema.safeParse(process.env);
if (!env.success) {
console.error('β Critical error: missing environment variables', env.error.format());
process.exit(1); // Hard-stop the application
}
// 3. Export a typed config for the whole project
export const config = env.data;Why This Just Works
The payoff: your code is environment-agnostic. Local .env while you build, the same keys in your host dashboard on deploy. Business logic does not change by a single character.
And you get a guarantee worth its weight in on-call sleep: if the app booted, every credential it needs is present and valid. No 2 AM "why is prod down" surprises.
The 30-Second Audit
Before you close this tab, run your current project past three questions:
- Is .env in .gitignore right now β not "probably", actually?
- Is there a .env.example (no real secrets) so a new teammate knows exactly what to set?
- Does the app crash on boot if the production config is missing or renamed?
Three yeses? Your secrets are safe β nice work. Even one no? You just found your next sprint ticket.
Over to You
How does your team hand secrets from devs to ops β a vault, encrypted env, a shared password manager, or vibes? Ever shipped a key straight to prod? Drop your best (or worst) story in the comments.
We build our tools on infrastructure that treats secrets like they matter β because they do.
Read article