Skip to main content
Technology & EngineeringVibe Coding Security385 lines

Production Hardening Checklist

Quick Summary24 lines
AI gets your application running. This checklist gets it running safely. Every item here has been found missing in production AI-generated codebases. Work through each section before launch — every unchecked item is an open vulnerability.

## Key Points

- [ ] All HTTP traffic redirects to HTTPS (301)
- [ ] TLS 1.2+ only (no TLS 1.0/1.1)
- [ ] Strong cipher suites configured
- [ ] Certificate auto-renewal (Let's Encrypt / ACM)
- [ ] SSL Labs test: A+ rating (`ssllabs.com/ssltest/`)
- [ ] HSTS header present on all HTTPS responses
- [ ] `includeSubDomains` enabled
- [ ] Submitted to HSTS preload list (hstspreload.org)
- [ ] CSP header configured
- [ ] No `unsafe-inline` for scripts (use nonces if needed)
- [ ] No `unsafe-eval`
- [ ] `frame-ancestors 'none'` (prevents clickjacking)

## Quick Example

```
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
```
skilldb get vibe-coding-security-skills/production-hardening-checklistFull skill: 385 lines
Paste into your CLAUDE.md or agent config

Production Hardening Checklist

AI gets your application running. This checklist gets it running safely. Every item here has been found missing in production AI-generated codebases. Work through each section before launch — every unchecked item is an open vulnerability.

TLS / HTTPS

# Nginx — force HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Modern TLS configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # Session settings
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
}

Checklist:

  • All HTTP traffic redirects to HTTPS (301)
  • TLS 1.2+ only (no TLS 1.0/1.1)
  • Strong cipher suites configured
  • Certificate auto-renewal (Let's Encrypt / ACM)
  • SSL Labs test: A+ rating (ssllabs.com/ssltest/)

HSTS (HTTP Strict Transport Security)

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
// Express with helmet
app.use(helmet.hsts({
  maxAge: 31536000,       // 1 year
  includeSubDomains: true,
  preload: true,
}));
  • HSTS header present on all HTTPS responses
  • includeSubDomains enabled
  • Submitted to HSTS preload list (hstspreload.org)

Content Security Policy (CSP)

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],  // No 'unsafe-inline' or 'unsafe-eval'
    styleSrc: ["'self'", "'unsafe-inline'"],  // Inline styles needed for some frameworks
    imgSrc: ["'self'", "data:", "https://cdn.example.com"],
    connectSrc: ["'self'", "https://api.example.com"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    objectSrc: ["'none'"],
    mediaSrc: ["'self'"],
    frameSrc: ["'none'"],
    baseUri: ["'self'"],
    formAction: ["'self'"],
    frameAncestors: ["'none'"],
    upgradeInsecureRequests: [],
  },
}));
  • CSP header configured
  • No unsafe-inline for scripts (use nonces if needed)
  • No unsafe-eval
  • frame-ancestors 'none' (prevents clickjacking)
  • Report-URI configured for monitoring violations

Security Headers

import helmet from 'helmet';

app.use(helmet());

// Additional headers
app.use((req, res, next) => {
  // Prevent MIME sniffing
  res.setHeader('X-Content-Type-Options', 'nosniff');

  // Prevent clickjacking
  res.setHeader('X-Frame-Options', 'DENY');

  // XSS protection (legacy browsers)
  res.setHeader('X-XSS-Protection', '0'); // Disabled — CSP is the modern approach

  // Referrer policy
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Permissions policy — disable unnecessary browser features
  res.setHeader('Permissions-Policy',
    'camera=(), microphone=(), geolocation=(), payment=(self)');

  // Cross-origin policies
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
  res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
  res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');

  next();
});

// Remove identifying headers
app.disable('x-powered-by');

Checklist:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy set
  • Permissions-Policy configured
  • Server header removed
  • X-Powered-By removed

Cookie Flags

// Every cookie must have these flags
res.cookie('session', token, {
  httpOnly: true,    // Not accessible via JavaScript
  secure: true,      // HTTPS only
  sameSite: 'lax',   // CSRF protection ('strict' for sensitive actions)
  maxAge: 86400000,  // 24 hours — not indefinite
  path: '/',
  domain: undefined, // Current domain only — no subdomain sharing unless needed
});

// Use __Host- prefix for maximum security
res.cookie('__Host-session', token, {
  httpOnly: true,
  secure: true,      // Required by __Host- prefix
  sameSite: 'lax',
  path: '/',         // Required by __Host- prefix
  // domain is intentionally omitted — required by __Host- prefix
});
  • httpOnly on all auth cookies
  • secure flag on all cookies
  • sameSite set (lax or strict)
  • Reasonable maxAge (not indefinite)
  • __Host- prefix where possible

DNS Configuration

# CAA record — only allow specific CAs to issue certificates
example.com. IN CAA 0 issue "letsencrypt.org"
example.com. IN CAA 0 issuewild ";"  # No wildcard certs

# DMARC — prevent email spoofing
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; ruf=mailto:dmarc@example.com"

# SPF — specify allowed email senders
example.com. IN TXT "v=spf1 include:_spf.google.com -all"

# DKIM — email signing (configured via your email provider)
  • CAA records restrict certificate issuance
  • DMARC policy set to reject
  • SPF record configured
  • DKIM configured
  • DNSSEC enabled (if registrar supports)

DDoS Protection

Cloudflare

# Page rules for API protection
api.example.com/*
  - Security Level: I'm Under Attack (during incidents)
  - Rate Limiting: 100 requests per minute per IP
  - Browser Integrity Check: On

# WAF rules
  - Block known bot user agents
  - Challenge suspicious IPs
  - Block requests from TOR exit nodes (if appropriate)

AWS Shield + WAF

{
  "Name": "RateLimitRule",
  "Priority": 1,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 2000,
      "AggregateKeyType": "IP"
    }
  },
  "Action": { "Block": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "RateLimitRule"
  }
}
  • DDoS protection enabled (Cloudflare, AWS Shield, Cloud Armor)
  • Rate limiting on all endpoints
  • Stricter rate limiting on auth endpoints
  • Geographic restrictions if applicable
  • Bot detection enabled

WAF Rules

# AWS WAF managed rule groups
- AWSManagedRulesCommonRuleSet        # OWASP Top 10
- AWSManagedRulesSQLiRuleSet          # SQL injection
- AWSManagedRulesKnownBadInputsRuleSet  # Log4j, etc.
- AWSManagedRulesBotControlRuleSet    # Bot management

# Custom rules
- Block requests with bodies > 10MB
- Block requests with more than 50 query parameters
- Block requests with SQL keywords in parameters
- Rate limit by IP: 100 requests per 5 minutes

Backup Verification

#!/bin/bash
# backup-verify.sh — run weekly

# 1. Create backup
pg_dump -h $DB_HOST -U $DB_USER $DB_NAME | gzip > /tmp/backup-test.sql.gz

# 2. Restore to test database
createdb backup_test
gunzip -c /tmp/backup-test.sql.gz | psql backup_test

# 3. Verify data integrity
EXPECTED_USERS=$(psql $DB_NAME -t -c "SELECT count(*) FROM users")
RESTORED_USERS=$(psql backup_test -t -c "SELECT count(*) FROM users")

if [ "$EXPECTED_USERS" != "$RESTORED_USERS" ]; then
  echo "BACKUP VERIFICATION FAILED: user count mismatch"
  # Alert team
  exit 1
fi

# 4. Cleanup
dropdb backup_test
rm /tmp/backup-test.sql.gz

echo "Backup verification passed"
  • Automated daily backups
  • Backups are encrypted at rest
  • Backup restore tested monthly
  • Backups stored in different region/provider
  • Backup retention policy defined (e.g., 30 days)
  • Point-in-time recovery enabled (RDS/Cloud SQL)

Incident Response Plan

## Incident Runbook

### Severity Levels
- P1 (Critical): Data breach, system compromise, service down
- P2 (High): Vulnerability actively exploited, partial outage
- P3 (Medium): Vulnerability discovered, no exploitation
- P4 (Low): Security improvement needed

### P1 Response (within 15 minutes)
1. Acknowledge in #incidents channel
2. Assemble response team
3. Contain the incident (revoke compromised credentials, block attack source)
4. Preserve evidence (logs, snapshots)
5. Assess impact (what data was accessed?)
6. Notify affected users (within 72 hours per GDPR)
7. Post-mortem within 48 hours

### Key Contacts
- On-call engineer: PagerDuty rotation
- Security lead: [contact]
- Legal/compliance: [contact]
- PR/communications: [contact]

### Emergency Actions
- Revoke all API keys: `./scripts/revoke-all-keys.sh`
- Enable maintenance mode: `./scripts/maintenance-on.sh`
- Block IP range: Cloudflare dashboard or `./scripts/block-ip.sh`
- Force logout all sessions: `redis-cli FLUSHDB` (session store)
  • Incident response plan documented
  • On-call rotation established
  • Runbooks for common scenarios
  • Communication templates ready
  • Legal/compliance contacts identified
  • Post-mortem process defined

Monitoring Setup

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// Detailed health check (internal only)
app.get('/health/detailed', requireInternalNetwork, async (req, res) => {
  const checks = {
    database: await checkDatabase(),
    redis: await checkRedis(),
    storage: await checkStorage(),
    memory: process.memoryUsage(),
    uptime: process.uptime(),
  };
  const healthy = Object.values(checks).every(c => c.status !== 'error');
  res.status(healthy ? 200 : 503).json(checks);
});
  • Uptime monitoring (external: Pingdom, UptimeRobot)
  • Application performance monitoring (Datadog, New Relic)
  • Error tracking (Sentry)
  • Log aggregation (CloudWatch, Datadog, ELK)
  • Security event alerting
  • SSL certificate expiry monitoring
  • Disk space and resource alerts
  • Database connection pool monitoring

The Full Pre-Launch Checklist

CategoryItemStatus
TLSHTTPS enforced, TLS 1.2+[ ]
HeadersHSTS, CSP, X-Content-Type-Options[ ]
CookieshttpOnly, secure, sameSite[ ]
AuthRate limiting on login, MFA available[ ]
APIRate limiting, input validation[ ]
DatabaseParameterized queries, encrypted connections[ ]
SecretsNo hardcoded credentials, rotation schedule[ ]
DependenciesAudited, pinned, monitored[ ]
ContainersNon-root, scanned, minimal base[ ]
DNSCAA, DMARC, SPF configured[ ]
DDoSProtection enabled, WAF rules active[ ]
BackupsAutomated, encrypted, tested[ ]
MonitoringUptime, errors, security events[ ]
IncidentResponse plan documented, contacts ready[ ]
LoggingStructured, no PII, centralized[ ]
Error handlingGeneric responses, no stack traces[ ]

No application is ready for production until every item on this list is addressed. AI will generate zero of these by default. This is the gap between "it works" and "it's safe to deploy."

Install this skill directly: skilldb add vibe-coding-security-skills

Get CLI access →