Hard-coded secrets are the most dangerous vulnerability. They're committed to version control, leaked in logs, exposed in error messages, and stolen in supply chain attacks. Proper secrets management means treating credentials as infrastructure, not code.
1. Use Environment Variables, Not Hardcoded Values
Environment variables are the bare minimum, but they're not foolproof.
// Vulnerable
const apiKey = 'sk_live_abc123xyz789';
const db = postgres(`postgresql://${apiKey}@host`);
// Better
const apiKey = process.env.API_KEY;
const db = postgres(`postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}`);
// Validate on startup
if (!process.env.API_KEY) {
throw new Error('API_KEY environment variable is required');
}
2. Never Commit .env
Files
Use .gitignore
to exclude environment files, and provide .env.example
templates.
# .gitignore
.env
.env.local
.env.*.local
*.log
# .env.example (commit this)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
API_KEY=change-me
JWT_SECRET=change-me
3. Use Dedicated Secrets Management Services
Tools like AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault provide encryption, rotation, and audit logging.
import AWS from 'aws-sdk';
const secretsManager = new AWS.SecretsManager({ region: 'us-east-1' });
async function getSecret(secretName) {
try {
const data = await secretsManager.getSecretValue({ SecretId: secretName }).promise();
return JSON.parse(data.SecretString);
} catch (err) {
console.error(`Failed to retrieve secret: ${secretName}`, err);
throw err;
}
}
const dbPassword = await getSecret('prod/db-password');
4. Implement Secret Rotation
Rotate secrets regularly to limit the damage from compromised credentials. Automate this with your secrets manager.
// AWS Lambda function to rotate RDS password
exports.handler = async (event) => {
const sm = new AWS.SecretsManager();
const rds = new AWS.RDS();
const secret = JSON.parse(event.SecretString);
const newPassword = generateSecurePassword();
// Update RDS password
await rds.modifyDBInstance({
DBInstanceIdentifier: 'prod-db',
MasterUserPassword: newPassword,
ApplyImmediately: true
}).promise();
// Update secret
await sm.updateSecret({
SecretId: event.ClientRequestToken,
SecretString: JSON.stringify({ ...secret, password: newPassword })
}).promise();
};
5. Monitor Secret Access
Log every access to secrets. Unusual access patterns (off-hours, multiple services, rapid requests) are red flags.
async function getSecret(secretName, context) {
const auditLog = {
action: 'SECRET_ACCESS',
secretName,
service: process.env.SERVICE_NAME,
timestamp: new Date(),
requestId: context.requestId,
ipAddress: context.ipAddress
};
await auditLogger.log(auditLog);
return secretsManager.getSecretValue({ SecretId: secretName }).promise();
}
6. Separate Secrets by Environment
Production secrets should never be accessible to development environments, and vice versa.
function getSecretPath(env) {
const validEnvs = ['development', 'staging', 'production'];
if (!validEnvs.includes(env)) {
throw new Error(`Invalid environment: ${env}`);
}
return `${env}/secrets`;
}
const secrets = await secretsManager.getSecretValue({
SecretId: getSecretPath(process.env.NODE_ENV)
}).promise();
Thanks for reading! If this post was insightful for you, please share it with your team or leave a comment with your own security wins.
Secrets in code are like leaving your keys in the door. The attack isn't a matter of if, but when.
With proper secrets management, rotation, and monitoring, credentials become a controlled asset rather than a liability.
I help teams build zero-trust secrets infrastructure that scales from startup to enterprise.
Secure your credentials today: kodex.studio
Top comments (0)