Security

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.

5 min readJune 2026

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:

  1. 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.
  2. 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.
  3. 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:

  1. Is .env in .gitignore right now β€” not "probably", actually?
  2. Is there a .env.example (no real secrets) so a new teammate knows exactly what to set?
  3. 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