Security Hardening: Complete Checklist for Production Applications
•13 min read•Subid Das
securitydevopsbest-practicesinfrastructurecompliance
Security breaches cost millions. Your job is preventing them. This is a complete hardening checklist.
Layer 1: Authentication & Authorization
Passwords
# ❌ Bad: Storing plaintext
user.password = request.json['password']
# ✅ Good: Hash with bcrypt
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
user.password = hashed
# Verify
if bcrypt.checkpw(password.encode(), user.password):
print("Correct password")
Multi-Factor Authentication (MFA)
# Require TOTP (Time-based One-Time Password)
import pyotp
# Generate secret
secret = pyotp.random_base32()
user.mfa_secret = secret
# Client generates code
totp = pyotp.TOTP(secret)
code = totp.now() # "123456"
# Verify
if totp.verify(code):
user.mfa_enabled = True
Session Management
# ❌ Bad: Long-lived tokens
token_lifetime = 365 * 24 * 60 # 1 year
# ✅ Good: Short-lived tokens + refresh tokens
access_token_lifetime = 15 * 60 # 15 minutes
refresh_token_lifetime = 7 * 24 * 60 * 60 # 7 days
# Rotate tokens
@app.route('/api/refresh', methods=['POST'])
def refresh_token():
refresh = request.json['refresh_token']
user = User.verify_refresh_token(refresh)
access = create_access_token(user)
new_refresh = create_refresh_token(user)
return {
"access_token": access,
"refresh_token": new_refresh,
"expires_in": 900
}
OAuth 2.0 / OpenID Connect
# Use OAuth for third-party integrations (don't store passwords)
from authlib.integrations.flask_client import OAuth
oauth = OAuth()
oauth.register(
name='github',
client_id='...',
client_secret='...',
authorize_url='https://github.com/login/oauth/authorize',
access_token_url='https://github.com/login/oauth/access_token',
server_metadata_url='https://github.com/.well-known/openid-configuration'
)
@app.route('/login/github')
def login_github():
return oauth.github.authorize_redirect(redirect_uri=url_for('callback', _external=True))
Layer 2: Data Protection
Encryption at Rest
# AWS: Enable S3 encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "secrets" {
bucket = aws_s3_bucket.secrets.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.secrets.arn
}
}
}
# Database: Enable encryption
resource "aws_db_instance" "postgres" {
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
}
Encryption in Transit
# ✅ Always use HTTPS (TLS 1.3)
@app.route('/api/login', methods=['POST'])
def login():
# Only accept over HTTPS
if not request.is_secure:
raise APIException("HTTPS_REQUIRED", "Use HTTPS", 400)
# Harden headers
response = make_response(...)
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
Secrets Management
# ❌ Bad: Secrets in code
DB_PASSWORD = "super_secret_123"
# ✅ Good: Use secrets vault (AWS Secrets Manager, HashiCorp Vault)
import boto3
secrets_client = boto3.client('secretsmanager', region_name='us-east-1')
secret = secrets_client.get_secret_value(SecretId='prod/db-password')
DB_PASSWORD = json.loads(secret['SecretString'])['password']
# Rotate secrets regularly (90 days)
Database
-- Encrypt sensitive columns
ALTER TABLE users
ADD COLUMN ssn_encrypted BYTEA;
-- Use parameterized queries (prevent SQL injection)
-- Python:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
-- NOT: "SELECT * FROM users WHERE id = " + str(user_id)
-- Hash PII
UPDATE users SET email_hash = MD5(email);
Layer 3: Application Security
Input Validation
from marshmallow import Schema, fields, validate
class UserSchema(Schema):
email = fields.Email(required=True)
password = fields.Str(required=True, validate=validate.Length(min=12))
age = fields.Int(validate=validate.Range(min=18, max=120))
@app.route('/api/register', methods=['POST'])
def register():
schema = UserSchema()
try:
user_data = schema.load(request.json)
except ValidationError as err:
raise APIException("INVALID_INPUT", "Validation failed", 400, err.messages)
user = User.create(**user_data)
return user.to_dict(), 201
CSRF Protection
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
# Form includes CSRF token
@app.route('/api/orders', methods=['POST'])
@csrf.protect
def create_order():
# CSRF token validated automatically
order = Order.create(request.json)
return order.to_dict(), 201
XSS Protection
<!-- ❌ Bad: Unescaped user input -->
<div>Welcome {{ user.name }}</div>
<!-- If user.name = "<script>alert('xss')</script>", it executes! -->
<!-- ✅ Good: Escaped output -->
<div>Welcome {{ user.name | escape }}</div>
<!-- Rendered as: Welcome <script>...</script> -->
SQL Injection Prevention
# ❌ Bad: String concatenation
query = f"SELECT * FROM users WHERE id = {user_id}"
# ✅ Good: Parameterized queries
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
Layer 4: Infrastructure Security
Network Security
# Security Group: Principle of least privilege
resource "aws_security_group" "app" {
# Only allow traffic from ALB
ingress {
from_port = 3000
to_port = 3000
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
# Deny all by default
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# WAF: Block known attacks
resource "aws_wafv2_web_acl" "main" {
name = "rate-limit"
default_action {
allow {}
}
rule {
name = "RateLimitRule"
priority = 0
action {
block {}
}
statement {
rate_based_statement {
limit = 2000 # 2000 requests per 5 minutes
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimitMetric"
sampled_requests_enabled = true
}
}
}
DDoS Protection
# AWS Shield Standard: Automatic protection
# AWS Shield Advanced: Enhanced protection + DDoS Response Team
resource "aws_shield_protection" "alb" {
name = "ALB-Shield"
resource_arn = aws_lb.main.arn
protection_group_id = aws_shield_protection_group.main.id
}
Container Security
# ❌ Bad: Run as root
FROM ubuntu:latest
RUN apt-get install -y app
CMD ["node", "app.js"]
# ✅ Good: Non-root user, minimal image
FROM node:18-alpine
RUN addgroup -g 1001 -S app
RUN adduser -S app -u 1001
COPY --chown=app:app . .
USER app
CMD ["node", "app.js"]
# Scan image for vulnerabilities
# docker scan myapp:latest
# trivy image myapp:latest
Layer 5: Dependency Management
Vulnerability Scanning
# npm audit (JavaScript)
npm audit
# pip audit (Python)
pip install pip-audit
pip-audit
# Dependabot (GitHub)
# Automated PRs for security updates
# Snyk
npm install -g snyk
snyk test
Lock Files
# ✅ Commit lock files
git add package-lock.json
git add requirements.txt
# ❌ Don't allow automatic dependency upgrades in production
Dependency Pinning
# ✅ Pin versions
requests==2.31.0
flask==2.3.2
# ❌ Avoid floating versions (security risk)
# requests>=2.0.0
Layer 6: Logging & Monitoring
Security Logging
import logging
security_logger = logging.getLogger('security')
# Log authentication events
@app.route('/api/login', methods=['POST'])
def login():
user = User.authenticate(email, password)
if user:
security_logger.info(f"LOGIN_SUCCESS user={user.id} ip={request.remote_addr}")
else:
security_logger.warning(f"LOGIN_FAILED email={email} ip={request.remote_addr}")
Alert on Suspicious Activity
# Alert if too many failed logins
@app.route('/api/login', methods=['POST'])
def login():
failed_logins = cache.incr(f"failed_logins:{request.remote_addr}")
if failed_logins > 10:
# Alert and block
security_logger.error(f"BRUTE_FORCE_DETECTED ip={request.remote_addr}")
return {"error": "Too many attempts"}, 429
Security Checklist
Authentication (✅ 100%)
- Password hashing (bcrypt, argon2)
- Multi-factor authentication (TOTP)
- Session management (short-lived tokens)
- Rate limiting on login (prevent brute force)
- Account lockout after failures
Data Protection (✅ 100%)
- HTTPS/TLS everywhere (TLS 1.3)
- Encryption at rest (KMS, database encryption)
- Secrets management (Secrets Manager, Vault)
- PII hashing/masking
- Secure password reset flow
Application Security (✅ 100%)
- Input validation (Marshmallow, Joi, Zod)
- CSRF protection
- XSS prevention (output escaping)
- SQL injection prevention (parameterized queries)
- Command injection prevention (no shell=True)
Infrastructure (✅ 100%)
- Network segmentation (Security Groups, VPCs)
- WAF (Web Application Firewall)
- DDoS protection (AWS Shield)
- Non-root containers
- File integrity monitoring
Dependency Management (✅ 100%)
- Automated vulnerability scanning (Dependabot, Snyk)
- Lock files (package-lock.json, requirements.txt)
- Regular updates (weekly)
- Dependency pinning
Monitoring & Logging (✅ 100%)
- Security event logging
- Centralized logging (ELK, Splunk)
- Alerting on suspicious activity
- Log retention (90+ days)
- Audit trails
Compliance
SOC 2 Type II
- Audit logging
- Access controls
- Encryption
- Incident response
GDPR
- Data minimization (only collect needed data)
- User consent
- Right to deletion
- Data breach notification (72 hours)
PCI-DSS (Payment Processing)
- No plaintext card data
- Network segmentation
- Vulnerability scanning
- Encryption in transit & rest
Conclusion
Security is a journey, not a destination. Start with authentication, move to data protection, then infrastructure hardening.
Use this checklist on every project. Automate what you can (scanning, updates). Audit regularly.
A breach costs millions. Prevention costs time.

