| name | environment-configuration-guardian |
| description | Validates environment-specific configurations across development, staging, and production environments. Prevents mismatched environment variables, localhost references in production, wrong API keys, and configuration drift. Use before ANY deployment to catch environment-specific issues that cause production failures. |
Environment Configuration Guardian
Mission: Ensure environment-specific configurations are correct for target environment (dev/staging/production). Prevents localhost references in production, wrong API keys, missing environment variables, and configuration mismatches that cause deployments to fail.
Historical Context: Created after analyzing v1.1.0-v1.1.1 incidents where environment-specific configurations (CORS, trust proxy, API keys) were incorrect for production.
Activation Triggers
- MANDATORY: Before EVERY deployment to new environment
- Switching from development to production
- Setting up staging environment
- Deploying to new server/infrastructure
- Changing environment variables
- Updating API keys or credentials
- "localhost" errors in production
- API returning 401 Unauthorized in production
- Configuration not matching environment
🔴 CRITICAL: Environment Variable Validation Matrix
Standard Environment Comparison
Run this comparison BEFORE deployment:
./scripts/compare-env-variables.sh development production
| Variable | Development | Production | Status |
|---|
| NODE_ENV | development | production | ✅ Correct |
| CORS_ORIGIN | localhost:3000 | https://pdflab.pro | ✅ Correct |
| Trust Proxy | false | true | ✅ Correct |
| DB_HOST | localhost | docker-container | ✅ Correct |
| CLOUDCONVERT_SANDBOX | true | ❌ true | ❌ WRONG |
| PAYFAST_MERCHANT_ID | 10000100 | ❌ 10000100 | ❌ WRONG |
Environment-Specific Configuration Checklist
Development Environment
NODE_ENV=development
PORT=3006
DB_HOST=localhost
DB_PORT=3306
DB_USER=pdflab
DB_PASSWORD=***REMOVED***
DB_NAME=pdflab
REDIS_HOST=localhost
REDIS_PORT=6379
CORS_ORIGIN=http://localhost:3000,http://localhost:3002
CLOUDCONVERT_API_KEY=sandbox_key_here
CLOUDCONVERT_SANDBOX=true
PAYFAST_MERCHANT_ID=10000100
PAYFAST_MERCHANT_KEY=46f0cd694581a
PAYFAST_PASSPHRASE=jt7NOE43FZPn
PAYFAST_MODE=sandbox
JWT_SECRET=dev-secret-not-for-production
JWT_EXPIRATION=7d
SENTRY_DSN=
FRONTEND_URL=http://localhost:3000
Production Environment
NODE_ENV=production
PORT=3006
DB_HOST=8731b5f977d0_pdflab-mysql-prod
DB_PORT=3306
DB_USER=pdflab
DB_PASSWORD=***REMOVED***
DB_NAME=pdflab_production
REDIS_HOST=f18c830e3d31_pdflab-redis-prod
REDIS_PORT=6379
CORS_ORIGIN=https://pdflab.pro,http://pdflab.pro
CLOUDCONVERT_API_KEY=live_production_key_here
CLOUDCONVERT_SANDBOX=false
PAYFAST_MERCHANT_ID=25263515
PAYFAST_MERCHANT_KEY=***REMOVED***
PAYFAST_PASSPHRASE=
PAYFAST_MODE=production
JWT_SECRET=pdflab-production-jwt-secret-2024
JWT_EXPIRATION=7d
SENTRY_DSN=https://...@sentry.io/...
FRONTEND_URL=https://pdflab.pro
🔴 CRITICAL: Configuration Validation Rules
Rule 1: No Localhost References in Production
Scan for localhost:
grep -i "localhost" .env.production
grep -r "localhost" backend/src/ --exclude-dir=node_modules | grep -v "comment"
grep -r "127.0.0.1" backend/src/ --exclude-dir=node_modules
Violations that WILL cause production failure:
const API_URL = "http://localhost:3006"
const corsOrigins = ['http://localhost:3000']
const DB_HOST = process.env.DB_HOST || 'localhost'
const API_URL = process.env.API_URL || (
process.env.NODE_ENV === 'production'
? 'https://pdflab.pro'
: 'http://localhost:3006'
)
const corsOrigins = process.env.CORS_ORIGIN?.split(',') || []
const DB_HOST = process.env.DB_HOST
if (!DB_HOST) {
throw new Error('DB_HOST environment variable is required')
}
Rule 2: API Keys Match Environment
Validation Checklist:
□ Development: CLOUDCONVERT_SANDBOX=true
□ Production: CLOUDCONVERT_SANDBOX=false
□ API key is production key (not sandbox key)
□ Development: PAYFAST_MERCHANT_ID=10000100 (sandbox)
□ Production: PAYFAST_MERCHANT_ID=25263515 (live merchant ID)
□ Development: PAYFAST_MODE=sandbox
□ Production: PAYFAST_MODE=production
□ Development: DB_NAME=pdflab
□ Production: DB_NAME=pdflab_production
□ Production password is DIFFERENT from development
□ Development: JWT_SECRET=dev-secret
□ Production: JWT_SECRET is strong (>32 chars) and unique
□ Verify JWT_SECRET is NOT default/example value
Test API Keys:
curl -X GET https://api.cloudconvert.com/v2/users/me \
-H "Authorization: Bearer $CLOUDCONVERT_API_KEY"
./scripts/test-payfast-signature.sh
Rule 3: Required Environment Variables Present
Production Required Variables (MANDATORY):
✅ NODE_ENV=production
✅ PORT=3006
✅ DB_HOST (not localhost)
✅ DB_PORT
✅ DB_USER
✅ DB_PASSWORD (strong password)
✅ DB_NAME (production database name)
✅ REDIS_HOST (not localhost)
✅ REDIS_PORT
✅ CORS_ORIGIN (includes production domain)
✅ CLOUDCONVERT_API_KEY (production key)
✅ CLOUDCONVERT_SANDBOX=false
✅ PAYFAST_MERCHANT_ID (production merchant ID)
✅ PAYFAST_MERCHANT_KEY (production key)
✅ PAYFAST_MODE=production
✅ JWT_SECRET (strong unique secret)
✅ FRONTEND_URL (production URL)
Validation Script:
const requiredEnvVars = [
'NODE_ENV',
'DB_HOST',
'DB_PASSWORD',
'REDIS_HOST',
'CLOUDCONVERT_API_KEY',
'PAYFAST_MERCHANT_ID',
'JWT_SECRET'
]
export function validateEnvironment() {
const missing: string[] = []
for (const varName of requiredEnvVars) {
if (!process.env[varName]) {
missing.push(varName)
}
}
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(', ')}`
)
}
if (process.env.NODE_ENV === 'production') {
const corsOrigins = process.env.CORS_ORIGIN?.split(',') || []
if (!corsOrigins.some(origin => origin.includes('pdflab.pro'))) {
throw ()
}
(process.. !== ) {
()
}
(process.. !== ) {
()
}
(process... < ) {
()
}
}
.()
}
()
Rule 4: No Secrets in Code or Logs
Scan for exposed secrets:
grep -r "CLOUDCONVERT_API_KEY.*=" backend/src/ --exclude-dir=node_modules
grep -r "password.*=" backend/src/ --exclude-dir=node_modules | grep -v "req.body"
grep -r "JWT_SECRET.*=" backend/src/ --exclude-dir=node_modules
git log -p | grep -i "api_key\|password\|secret"
Red Flags:
const CLOUDCONVERT_API_KEY = "eyJ0eXAiOiJKV1Q..."
console.log('User password:', user.password)
console.log('Auth token:', req.headers.authorization)
const CLOUDCONVERT_API_KEY = process.env.CLOUDCONVERT_API_KEY
console.log('User authenticated:', user.id)
console.log('Auth header:', req.headers.authorization?.replace(/Bearer .+/, 'Bearer [REDACTED]'))
Configuration Drift Detection
Problem: Configurations Diverge Over Time
Symptoms:
- Features work in dev but fail in production
- Different behavior between environments
- Intermittent production issues
- "It works on my machine" syndrome
Solution: Regular Configuration Audits
diff .env.development .env.production
node scripts/generate-config-report.js
=== Environment Configuration Report ===
Environment: production
Generated: 2025-11-10
Configuration Differences from Development:
NODE_ENV: development → production ✅
CORS_ORIGIN: localhost → pdflab.pro ✅
DB_HOST: localhost → docker-container ✅
CLOUDCONVERT_SANDBOX: true → false ✅
Configuration Risks:
⚠️ JWT_SECRET is only 16 chars (recommend 32+)
⚠️ No SENTRY_DSN configured (monitoring disabled)
Missing Variables:
❌ SMTP_HOST (email notifications disabled)
❌ BACKUP_ENABLED (automatic backups disabled)
=== End Report ===
Pre-Deployment Environment Validation
Step-by-Step Validation Process
Step 1: Export Current Environment
cd backend
node -e "console.log(JSON.stringify(process.env, null, 2))" > /tmp/dev-env.json
ssh root@141.136.44.168
cd /path/to/app
node -e "console.log(JSON.stringify(process.env, null, 2))" > /tmp/prod-env.json
Step 2: Compare Environments
scp root@141.136.44.168:/tmp/prod-env.json /tmp/
node scripts/compare-environments.js /tmp/dev-env.json /tmp/prod-env.json
Step 3: Validate Critical Values
✅ Check: Production domain included
❌ Fail: Only localhost origins
✅ Check: Enabled for production
❌ Fail: Disabled or missing
✅ Check: Production keys configured
❌ Fail: Sandbox keys in production
✅ Check: Production database host
❌ Fail: localhost or development database
Step 4: Test Configuration
npm run test:db-connection
npm run test:redis-connection
npm run test:cloudconvert-api
npm run test:payfast-signature
Common Environment Configuration Issues
Issue 1: Localhost in Production CORS
Symptom: "Not allowed by CORS" in production
Cause: CORS_ORIGIN only has localhost
Fix: Add production domain to CORS_ORIGIN
CORS_ORIGIN=http://localhost:3000
CORS_ORIGIN=http://localhost:3000,https://pdflab.pro
Issue 2: Sandbox API Keys in Production
Symptom: API returns 401 or "Invalid credentials"
Cause: Using sandbox API keys in production
Fix: Use production API keys
CLOUDCONVERT_SANDBOX=true
PAYFAST_MERCHANT_ID=10000100
CLOUDCONVERT_SANDBOX=false
PAYFAST_MERCHANT_ID=25263515
Issue 3: Weak JWT Secret
Symptom: Security vulnerability, easy token forgery
Cause: Short or predictable JWT secret
Fix: Use strong random secret (32+ characters)
JWT_SECRET=secret
JWT_SECRET=pdflab-prod-jwt-a8f3c9e2b7d1f4a6c8e9b2d5f1a3c6e8
Issue 4: Docker Container Hostnames
Symptom: "ECONNREFUSED" errors in production
Cause: Using localhost instead of Docker container hostnames
Fix: Use Docker container hostnames or service names
DB_HOST=localhost
REDIS_HOST=localhost
DB_HOST=8731b5f977d0_pdflab-mysql-prod
REDIS_HOST=f18c830e3d31_pdflab-redis-prod
DB_HOST=mysql
REDIS_HOST=redis
Environment Variable Security Best Practices
1. Never Commit .env Files to Git
.env
.env.local
.env.development
.env.production
.env.*.local
.env.example
2. Use .env.example as Template
NODE_ENV=
PORT=
DB_HOST=
DB_USER=
DB_PASSWORD=
CLOUDCONVERT_API_KEY=
3. Rotate Secrets Regularly
- [ ] JWT_SECRET: Every 6 months
- [ ] Database passwords: Every 6 months
- [ ] API keys: When compromised or yearly
- [ ] Session secrets: Every 6 months
4. Use Secret Management Tools
Options:
- Docker Secrets (for Docker Swarm)
- AWS Secrets Manager (for AWS)
- HashiCorp Vault (enterprise)
- Environment variables (encrypted at rest)
Automated Environment Validation Script
#!/bin/bash
ENV=$1
echo "=== Validating $ENV Environment ==="
REQUIRED_VARS=("NODE_ENV" "DB_HOST" "REDIS_HOST" "CLOUDCONVERT_API_KEY")
for VAR in "${REQUIRED_VARS[@]}"; do
if [ -z "${!VAR}" ]; then
echo "❌ Missing required variable: $VAR"
exit 1
else
echo "✅ $VAR is set"
fi
done
if [ "$ENV" = "production" ]; then
if [[ "$DB_HOST" == *"localhost"* ]]; then
echo "❌ DB_HOST contains localhost in production"
exit 1
fi
if [[ ! "$CORS_ORIGIN" == *"pdflab.pro"* ]];
1
[ != ];
1
Usage:
./scripts/validate-environment.sh production
Key Principles
- Never hardcode environment-specific values - Always use environment variables
- Validate environment on startup - Fail fast if misconfigured
- No localhost in production - Ever
- Production keys are different from sandbox - Always
- Strong secrets in production - 32+ character random strings
- Regular configuration audits - Catch drift early
When to Escalate
- Moving to multi-region deployment
- Implementing CI/CD pipelines
- Setting up infrastructure as code (Terraform, etc.)
- Migrating to Kubernetes or container orchestration
- Implementing secret rotation automation
- Compliance requirements (SOC 2, PCI-DSS)
Skill Version: 1.0.0
Created: November 10, 2025
Last Updated: November 10, 2025