Developer Security Checklist: Password Hashing, Defensive Coding & Secure SDLC

50 concrete security checks organized by category. Starts with authentication and password hashing - the controls that matter most when your database gets breached.

··18 min read·By ismycodesafe.com Security Team

SSL/TLS (1-6)

  1. TLS certificate is valid and not expired. Check expiration date. Set up automated renewal with Let's Encrypt or your CA.
  2. TLS 1.2 or higher. TLS 1.0 and 1.1 are deprecated. Disable them in your server config. Use the Mozilla SSL Configuration Generator.
  3. HSTS header is set. Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  4. HTTP redirects to HTTPS. All HTTP requests should 301 redirect to HTTPS. Not 302.
  5. No mixed content. Every resource (scripts, stylesheets, images, fonts) loaded over HTTPS.
  6. Certificate chain is complete. Intermediate certificates are included. Test with openssl s_client -connect yoursite.com:443.

Security Headers (7-14)

  1. Content-Security-Policy is set. At minimum: default-src 'self'. Add exceptions as needed. See MDN CSP guide.
  2. X-Frame-Options is DENY or SAMEORIGIN. Prevents clickjacking.
  3. X-Content-Type-Options is nosniff. Prevents MIME sniffing attacks.
  4. Referrer-Policy is set. strict-origin-when-cross-origin is a good default.
  5. Permissions-Policy disables unused features. camera=(), microphone=(), geolocation=()
  6. Server header doesn't leak version info. Remove or obscure the Server and X-Powered-By headers.
  7. Cross-Origin-Opener-Policy is same-origin. Isolates browsing context.
  8. Cache-Control is set for sensitive pages. no-store for authenticated pages to prevent browser caching of sensitive data.

Password Hashing & Authentication (15-22)

Password hashing is the single authentication control that determines how bad a database breach actually gets. If an attacker dumps your users table tonight, everything else you built depends on those hashes being computationally infeasible to crack. Bcrypt, scrypt, and Argon2id are purpose-built to be slow. MD5, SHA-1, and unsalted SHA-256 are not - a modern GPU cracks unsalted SHA-256 hashes at billions of attempts per second.

Choosing a password hashing algorithm

Argon2id is the right choice for new systems. It won the Password Hashing Competition in 2015. It is memory-hard and GPU-resistant. Library support is solid: Python (argon2-cffi), Node.js (argon2), Go (golang.org/x/crypto/argon2), PHP (password_hash(PASSWORD_ARGON2ID)). Minimum parameters: memory=64MB, time=3, parallelism=4 - benchmark to ~200-500ms on your hardware.

bcrypt is the safe default for legacy codebases and languages without Argon2 support. Cost factor 12 is the 2026 baseline; factor 13-14 if your server can absorb ~500ms per login. bcrypt has a 72-byte input limit - pre-hash with SHA-256 if you allow long passwords, or switch to Argon2id.

scrypt is memory-hard like Argon2 but harder to tune correctly. Use it only if Argon2id is unavailable and you need to go beyond bcrypt. PBKDF2-SHA256 is acceptable for FIPS 140-3 compliance contexts only - it is CPU-bound, not memory-hard, and cracks faster on GPU clusters.

Never use: MD5, SHA-1, SHA-256 without a per-user random salt, or any general-purpose hash function for passwords. These were designed for speed - the exact opposite of what password hashing requires.

  1. Passwords are hashed with bcrypt (cost 12+), scrypt, or Argon2id. Never MD5, SHA-1, or SHA-256 without a random per-user salt. The algorithm is the defensive line when your database is exfiltrated.
  2. Password minimum length is 8+ characters. Support at least 64 characters. Check submitted passwords against the HaveIBeenPwned Pwned Passwords API before account creation and password change.
  3. Login endpoint is rate-limited. Maximum 10 attempts per IP per minute. Return 429 after limit. Add account-level lockout after 20 failed attempts with exponential backoff.
  4. CSRF tokens on all state-changing forms. Login, registration, password change, account settings.
  5. Session tokens are regenerated after login. Prevents session fixation attacks.
  6. Cookies have Secure, HttpOnly, and SameSite flags. Set-Cookie: session=...; Secure; HttpOnly; SameSite=Lax
  7. MFA is available for admin accounts. TOTP (authenticator app) at minimum.
  8. Password reset tokens expire. 1 hour maximum. Single-use. Invalidated after password change.

Input Validation (23-28)

Defensive programming means treating all external input as hostile by default. That is not paranoia. It is just what good code does. Validate shape, length, type, and range at the system boundary before it reaches your business logic.

  1. All user input is validated server-side. Client-side validation is for UX only. An attacker bypasses JavaScript in seconds.
  2. Input length limits are enforced. Names: 100 chars. Emails: 254 chars. URLs: 2048 chars. Prevent memory and storage abuse.
  3. File uploads are validated. Check MIME type, file extension, and file size. Store outside the webroot. Generate random filenames.
  4. HTML output is encoded. Use your framework's built-in escaping (React JSX, Django templates, Jinja2 autoescaping). Never use innerHTML with user data.
  5. URLs in redirects are validated. Open redirect vulnerabilities let attackers redirect users to phishing sites using your domain.
  6. JSON and XML parsers reject external entities. Disable XXE processing. Most modern parsers do this by default.

Database (29-34)

  1. All queries use parameterized statements. No string concatenation in SQL. Ever. Use your ORM or prepared statements. See bobby-tables.com.
  2. Database user has minimum privileges. The application's database account should not have DROP, CREATE, or GRANT permissions.
  3. Database is not exposed to the internet. Bind to localhost or a private network. No public port 3306/5432/27017.
  4. Default database credentials are changed. No root with no password. No postgres/postgres.
  5. Sensitive data is encrypted at rest. Use database-level encryption (TDE) or application-level encryption for PII fields.
  6. Database backups are encrypted and tested. An unencrypted backup is a data breach waiting to happen. Test restore procedures monthly.

API Security (35-40)

  1. CORS is configured with specific origins. Never Access-Control-Allow-Origin: * with credentials. Specify allowed domains.
  2. API authentication on every endpoint. No endpoints that assume "nobody will find this URL."
  3. Rate limiting on all public endpoints. Prevent abuse, scraping, and brute force attacks.
  4. Response bodies don't leak internal data. No stack traces, database column names, or file paths in error responses.
  5. API versioning is in place. Breaking changes don't affect existing clients without notice.
  6. Request size limits are enforced. Prevent request body abuse. Typically 1-10 MB depending on use case.

Deployment (41-46)

In a secure SDLC, deployment is where you verify nothing slipped through. Run your dependency audit and secrets check before the release lands in production. Do it in CI so it is automatic, not optional.

  1. Debug mode is off in production. No DEBUG=True (Django), no app.debug = True (Flask), no verbose error pages.
  2. Sensitive files are not accessible. .env, .git/, docker-compose.yml, package.json should return 404.
  3. Admin panels are protected. Not accessible on the public URL. Use IP allowlisting, VPN, or a separate domain.
  4. Dependencies are updated and audited. Run npm audit, pip audit, or your package manager's security check.
  5. Secrets are in environment variables. Not in source code, not in config files committed to git. Use a secret manager.
  6. Docker images use non-root users. USER node or USER appuser in your Dockerfile. Never run as root.

Monitoring (47-50)

  1. Security events are logged. Login attempts, access denied, input validation failures, errors. Include timestamp, user, IP, action.
  2. Logs are stored securely. Centralized logging (ELK, CloudWatch, Datadog). An attacker who compromises the app should not be able to delete logs.
  3. Alerts are configured for anomalies. Spike in 403/401 errors, login failures from single IP, unusual data export patterns.
  4. Regular security scans. Automated vulnerability scanning after every deployment. ismycodesafe.com covers 110 checks across SSL, headers, ports, OWASP paths, CVEs, SEO, and AI content detection. Free.

Check your website right now

110 security checks in 60 seconds. Free, no signup required.

Scan My Website (Free)

ismycodesafe.com Security Team

We run automated security scans on thousands of websites daily, combining static analysis, SSL/TLS inspection, header auditing, and CVE lookups. Our team tracks OWASP, NIST, and evolving compliance requirements (GDPR, NIS2, PCI DSS) to keep these guides accurate and practical.