Free Resource
The Startup Security Checklist
Everything you need to secure your application before launch. Each item includes an explanation of why it matters and how to implement it.
Authentication & Session Security
Passwords require 12+ characters with complexity rules
Short passwords are cracked in seconds with modern GPUs. Require a minimum of 12 characters and encourage passphrases. Use a library like zxcvbn for strength estimation rather than rigid rules.
Password hashing uses bcrypt or argon2 (not MD5/SHA1)
MD5 and SHA1 are fast hashing algorithms, which is bad for passwords. Attackers can try billions of guesses per second. Use bcrypt (cost factor 12+) or argon2id, which are deliberately slow.
Account lockout after 5 failed login attempts
Without lockout, attackers can try unlimited password combinations. Lock accounts after 5 failures for 15 minutes. Also implement progressive delays (1s, 2s, 4s) to slow automated attacks.
Session tokens stored in httpOnly, Secure cookies
Storing tokens in localStorage or sessionStorage exposes them to XSS attacks. HttpOnly cookies prevent JavaScript access. The Secure flag ensures cookies are only sent over HTTPS.
Sessions expire after 30 minutes of inactivity
Abandoned sessions are a common attack vector. Set idle timeouts to 30 minutes for regular users, 15 minutes for admin sessions. Implement server-side session invalidation.
MFA available for admin accounts
Passwords alone aren't enough for privileged accounts. Implement TOTP-based MFA (Google Authenticator, Authy) for all admin and owner roles. WebAuthn/passkeys are even more secure.
Password reset tokens expire after 1 hour
Reset tokens sent via email should be single-use and time-limited. One hour is generous. Also invalidate the token if the user logs in before using it.
Logout invalidates sessions server-side
Client-side token deletion isn't enough. The token might still be valid if intercepted. Add a server-side deny list for logged-out tokens, or use short-lived JWTs with refresh token rotation.
API Security
Rate limiting on all endpoints
Without rate limits, attackers can brute-force logins, enumerate users, or run up your cloud bill. Implement per-IP and per-user rate limits. Use tiered limits: stricter on auth endpoints, more relaxed on reads.
CORS restricted to specific origins (not wildcard)
A wildcard CORS policy (Access-Control-Allow-Origin: *) lets any domain make requests to your API. Restrict to your actual frontend origins. Never reflect the Origin header without validation.
Input validation on every endpoint (use Zod/Joi)
Never trust user input. Validate types, lengths, formats, and ranges on every API endpoint. Libraries like Zod (TypeScript) or Joi (Node.js) make this easy and catch issues early.
No sensitive data in URL parameters
URLs appear in server logs, browser history, referrer headers, and analytics tools. Never put tokens, passwords, or PII in query parameters. Use request bodies or headers instead.
API versioning in place
Version your API from day one (v1). This lets you make breaking changes without disrupting existing clients. Use URL prefixing (/api/v1/) or header-based versioning.
Request size limits configured
Without size limits, attackers can send massive payloads to exhaust server memory. Set reasonable limits: 1MB for JSON bodies, smaller for specific endpoints. Use streaming for file uploads.
Security Headers
Strict-Transport-Security (HSTS)
HSTS tells browsers to always use HTTPS, preventing SSL stripping attacks. Set max-age to at least 31536000 (1 year). Include includeSubDomains and consider preload once stable.
Content-Security-Policy (CSP)
CSP prevents XSS by restricting which scripts can execute. Start with a restrictive policy and loosen as needed. At minimum: default-src 'self'; script-src 'self'.
X-Frame-Options: DENY
Prevents your application from being embedded in iframes, blocking clickjacking attacks. Set to DENY unless you specifically need iframe embedding, then use SAMEORIGIN.
X-Content-Type-Options: nosniff
Prevents browsers from MIME-sniffing responses, which can lead to XSS via uploaded files. Always set this header. There's no reason not to.
Referrer-Policy
Controls how much URL information is sent in the Referer header. Set to strict-origin-when-cross-origin to prevent leaking internal URLs to third parties.
Permissions-Policy
Restricts which browser features your application can use (camera, microphone, geolocation). Disable everything you don't need to reduce your attack surface.
X-Powered-By header removed
This header reveals your server technology (Express, PHP, etc.), helping attackers find version-specific exploits. Remove it. It provides no value to legitimate users.
Data Protection
HTTPS everywhere (no HTTP)
All traffic must be encrypted. Redirect HTTP to HTTPS. Use HSTS to ensure browsers never attempt unencrypted connections. Get certificates from Let's Encrypt. They're free.
Secrets in environment variables (not code)
Never hardcode API keys, database passwords, or tokens in source code. Use environment variables and a secrets manager (AWS Secrets Manager, Doppler, or .env files locally).
No API keys in frontend JavaScript
Any key in your frontend bundle is public. Use server-side API routes to proxy requests that need secret keys. Audit your bundles regularly for leaked credentials.
Database backups automated and tested
Automated daily backups aren't enough. You must test restores. An untested backup is not a backup. Schedule quarterly restore tests and document the procedure.
PII encrypted at rest
Encrypt personally identifiable information in your database using AES-256. Use column-level encryption for sensitive fields. Ensure encryption keys are stored separately from data.
Logs don't contain passwords or tokens
Audit your logging to ensure sensitive data is never written to logs. Redact passwords, tokens, and PII before logging. Use structured logging with explicit field selection.
Code Security
Dependencies scanned for vulnerabilities
Run npm audit, pip audit, or equivalent regularly. Better yet, automate it with Dependabot, Snyk, or Cybrove's GitHub Scanning. One vulnerable dependency can compromise everything.
No hardcoded credentials in source code
Search your codebase for strings like "password", "secret", "api_key". Check git history too. Deleted secrets are still in old commits. Use Cybrove's GitHub Scanner to automate this.
Source maps disabled in production
Source maps expose your original source code to anyone who opens browser devtools. Disable them in production builds. If you need them for error tracking, use private source maps.
.env files in .gitignore
Environment files contain secrets and should never be committed. Add .env* to .gitignore before your first commit. If already committed, rotate all secrets immediately.
Dependabot or similar enabled
Automated dependency updates catch vulnerabilities before you notice them. Enable Dependabot, Renovate, or a similar tool to submit PRs when new versions fix security issues.
Infrastructure
Database not publicly accessible
Your database should only accept connections from your application servers, never from the public internet. Use VPC/private networking and firewall rules to restrict access.
SSH keys used (not passwords)
Password-based SSH is vulnerable to brute force. Use SSH key pairs, disable password authentication, and disable root login. Consider using a bastion host for production access.
Firewall configured with minimal open ports
Only expose the ports you need (typically 80, 443). Close everything else. Use security groups or iptables to restrict traffic. Audit open ports regularly.
Monitoring and alerting set up
You need to know when things break. Set up uptime monitoring, error tracking (Sentry), and resource alerts (CPU, memory, disk). Include security event alerts for failed logins and suspicious activity.
Incident response plan documented
When a security incident happens (not if), you need a plan. Document: who to contact, how to contain, how to investigate, and how to communicate. Practice it before you need it.
Want to automate this checklist? Cybrove checks most of these automatically.
Try Cybrove