SUBID DASPORTFOLIO / 2026
000

SUBID

PORTRAIT // ORIGINAL[ 1 / 7 ]
WORK // RIDENOWW[ 2 / 7 ]
WORK // DEED POLL[ 3 / 7 ]
PORTRAIT // CYBERNETIC[ 4 / 7 ]
PORTRAIT // HOLOGRAM[ 5 / 7 ]
PORTRAIT // SKETCH[ 6 / 7 ]
PORTRAIT // GLITCH[ 7 / 7 ]
SUBID DAS
Back to Blog

Security Hardening: Complete Checklist for Production Applications

13 min readSubid 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 &lt;script&gt;...&lt;/script&gt; -->

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.

About the author

Subid Das is a cloud native engineer and open source contributor. Find more articles onthe blog.

Open to freelance, full-time, and interesting problems.

LET'S CONNECT