TL;DR
Development features in production cause breaches. Debug modes, test credentials, and development APIs should never reach production. Use environment variables, separate databases, and build-time checks to ensure clean separation. These prompts help you audit and fix environment issues.
Environment Audit
Audit my application for dev/production separation issues.
Check for:
- Debug modes in production:
- DEBUG=true, NODE_ENV !== 'production'
- Verbose error messages
- Stack traces in responses
- Development middleware (hot reload, etc.)
- Test credentials in production:
- Test API keys
- Default passwords
- Hardcoded tokens
- Development endpoints:
- /debug, /test, /dev routes
- GraphQL introspection enabled
- API documentation exposed
- phpinfo(), /env, /.env accessible
- Database concerns:
- Dev database connected in production
- Test data in production database
- Seed data or admin test accounts
- Logging:
- Verbose logging in production
- Sensitive data in logs
- Log files publicly accessible
- Source exposure:
- Source maps in production
- .git directory accessible
- Environment files exposed
Environment Variables
Set up proper environment variable handling.
File structure: .env.example # Template with dummy values (committed) .env.local # Local development (gitignored) .env.development # Development defaults (optional) .env.production # Production (set in deployment platform, NOT committed)
.gitignore: .env .env.local .env.production .env*.local
Validation at startup: const requiredEnvVars = 'DATABASE_URL', 'API_KEY', 'JWT_SECRET';
for (const envVar of requiredEnvVars) {
if (!process.envenvVar) {
console.error(Missing required environment variable: ${envVar});
process.exit(1);
}
}
// Validate NODE_ENV const validEnvs = 'development', 'production', 'test'; if (!validEnvs.includes(process.env.NODE_ENV)) { console.error('Invalid NODE_ENV'); process.exit(1); }
Use a library like envalid or zod for typed validation: import { z } from 'zod';
const envSchema = z.object({ NODE_ENV: z.enum('development', 'production', 'test'), DATABASE_URL: z.string().url(), API_KEY: z.string().min(1), });
Never commit .env files with real secrets: Even if you later remove them, they remain in git history. Use git-secrets or similar tools to prevent accidental commits. Rotate any secrets that were ever committed.
Conditional Features
Implement environment-specific features safely.
Pattern for dev-only features:
const isDev = process.env.NODE_ENV === 'development'; const isProd = process.env.NODE_ENV === 'production';
// Dev-only middleware if (isDev) { app.use(morgan('dev')); app.use('/debug', debugRoutes); }
// Production security if (isProd) { app.use(helmet()); app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
// Disable features app.disable('x-powered-by'); }
// Error handling differs app.use((err, req, res, next) => { console.error(err);
if (isProd) { res.status(500).json({ error: 'Internal server error' }); } else { res.status(500).json({ error: err.message, stack: err.stack }); } });
// Build-time dead code elimination (webpack/rollup) if (process.env.NODE_ENV !== 'production') { // This code is removed from production build require('./devTools'); }
Never use environment checks for security-critical features - use proper access control.
Separate Databases
Ensure development and production databases are completely separate.
Requirements:
- Separate database instances:
- Development: Local or dev server
- Staging: Copy of production structure, no real data
- Production: Production server with real data
- Environment-specific connection strings:
.env.development
DATABASE_URL=postgresql://localhost:5432/myapp_dev.env.production (set in platform)
DATABASE_URL=postgresql://prod-server:5432/myapp_prod - Prevent accidental production connection:
const dbUrl = process.env.DATABASE_URL;
if (process.env.NODE_ENV === 'development' && dbUrl.includes('prod')) { throw new Error('Refusing to connect to production database in development'); } - Seed data safety: // Only run seeds in development if (process.env.NODE_ENV === 'production') { throw new Error('Cannot run seeds in production'); }
- Migration safety:
- Require confirmation for production migrations
- Use separate migration tracking per environment
- Never run destructive migrations in production without backup
Pro tip: Use different credentials, different cloud accounts, or at minimum different database names for each environment. Make it physically impossible to accidentally connect dev tools to production data.
Should staging use production data?
Ideally no - use anonymized or synthetic data. If you must use production data, ensure it's properly anonymized (PII removed), and staging has the same security controls as production.
How do I test production-like conditions safely?
Use staging environments that mirror production infrastructure but with test data. Load test against staging, not production. Use feature flags to gradually roll out to production users.
Check Your Environment Config
Scan for development features accidentally exposed in production.
Start Free Scan