6 min read
On this page

Minimum Viable Security

You do not need SOC2 at seed stage. You do not need a CISO. You do not need a penetration test. You do not need a bug bounty program.

But you do need a security floor. A baseline set of practices that prevent the most common, most catastrophic vulnerabilities. The ones that end startups not because of sophisticated attacks, but because of embarrassing, preventable mistakes.

This is minimum viable security. The stuff you cannot skip, no matter how early you are.

The Non-Negotiable Security Floor

Four things. That is it. Get these right from day one and you are ahead of most startups at your stage.

Minimum Viable Security Checklist:
1. HTTPS everywhere (no exceptions)
2. Hashed passwords (bcrypt or argon2)
3. Parameterized queries (no SQL injection)
4. Secrets out of code (use environment variables)

These are not aspirational goals. These are the bare minimum. Skipping any one of these is like leaving your front door open because you have not bought furniture yet.

HTTPS Everywhere

There is no reason to serve anything over HTTP in 2024. Let's Encrypt provides free SSL certificates. Every major hosting platform handles HTTPS automatically. Cloudflare's free tier gives you SSL in front of any origin.

HTTPS setup by platform:
- Vercel, Netlify, Railway: automatic, zero config
- AWS: ACM provides free certificates
- VPS (DigitalOcean, Hetzner): Caddy auto-provisions certs
- Custom domain: Cloudflare free tier as proxy

Dropbox launched in 2008 and had HTTPS from day one. It was harder then. There is zero excuse now.

HTTP means every coffee shop wifi network can see your users' data in transit. It means form submissions, session tokens, and API keys travel in plaintext. It means your login page is trivially interceptable.

Use HTTPS. For everything. No exceptions for internal tools, staging environments, or admin panels.

Hashed Passwords

Never store plaintext passwords. Never store encrypted passwords. Store hashed passwords using bcrypt or argon2.

The difference matters. Encryption is reversible — if someone gets your encryption key, they get every password. Hashing is one-way. Even if your database leaks, attackers cannot reverse the hashes back to passwords.

Password storage hierarchy (worst to best):
- Plaintext: catastrophic. One breach exposes everything.
- MD5/SHA1: broken. Rainbow tables crack these instantly.
- SHA256 with salt: better, but too fast. GPUs crack billions per second.
- bcrypt: good. Deliberately slow. Cost factor makes brute force impractical.
- argon2: best. Memory-hard. Resists GPU and ASIC attacks.

Use bcrypt with a cost factor of at least 10. Or use argon2id if your framework supports it. Most modern frameworks default to one of these. Django uses PBKDF2 by default but supports argon2. Rails uses bcrypt. Node has the bcrypt npm package.

LinkedIn stored passwords as unsalted SHA1 hashes. In 2012, 6.5 million password hashes leaked. They were cracked almost instantly. Do not be LinkedIn.

If you are using a third-party auth provider like Auth0 or Supabase Auth, they handle this for you. One more reason to not build your own auth.

Parameterized Queries

SQL injection has been the number one web vulnerability for over two decades. It is the most well-understood, most preventable, and most devastating attack vector in web applications.

The fix is simple: never concatenate user input into SQL queries. Use parameterized queries or an ORM.

WRONG - string concatenation:
query = "SELECT * FROM users WHERE email = '" + userInput + "'"

If userInput is: ' OR '1'='1
The query becomes: SELECT * FROM users WHERE email = '' OR '1'='1'
Result: returns all users

RIGHT - parameterized query:
query = "SELECT * FROM users WHERE email = $1"
params = [userInput]

The database treats userInput as data, never as SQL code.
No injection possible.

Every modern database library supports parameterized queries. Every ORM uses them by default. There is no performance penalty. There is no complexity cost. There is no reason not to use them.

If you are using an ORM like Prisma, SQLAlchemy, ActiveRecord, or Drizzle, you are already protected for standard queries. Watch out for raw query escape hatches — those are where injection sneaks back in.

Bobby Tables is a joke from 2007. The fact that SQL injection still appears in production code in 2024 is not funny. It is negligence.

Secrets Out of Code

Never commit secrets to your code repository. Not API keys. Not database passwords. Not JWT signing keys. Not third-party service tokens.

Common secrets that end up in code:
- DATABASE_URL with password
- STRIPE_SECRET_KEY
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
- JWT_SECRET
- SENDGRID_API_KEY
- OAuth client secrets

Use environment variables. Every deployment platform supports them. Every framework has a way to read them.

Environment variable setup:
- Local development: .env file (add to .gitignore)
- Railway, Render, Fly.io: dashboard or CLI
- Vercel, Netlify: project settings
- AWS: Parameter Store or Secrets Manager
- Docker: docker-compose env_file or secrets

Uber had AWS credentials in a private GitHub repo. Attackers found them, accessed the data of 57 million users and drivers, and Uber paid $148 million in settlements. The credentials were in a private repo. Private does not mean secure.

Add a .gitignore entry for .env files before your first commit. Use a tool like git-secrets or trufflehog in your CI pipeline to catch accidental commits. Some platforms like GitHub now scan for known secret patterns and alert you automatically.

.gitignore entries (add these immediately):
.env
.env.local
.env.production
*.pem
*.key
credentials.json
service-account.json

The Things That Can Wait

Everything else is important but not urgent at seed stage. These are things you should plan for but do not need on day one.

Important but not day-one:
- Rate limiting on API endpoints
- Input validation beyond SQL injection
- CORS configuration
- Content Security Policy headers
- Dependency vulnerability scanning
- Access logging and audit trails
- Data encryption at rest
- Backup encryption
- Security headers (X-Frame-Options, etc.)

These become more important as you get users, handle more sensitive data, and start talking to enterprise customers. But they should not block your first deploy.

The exception: if you are handling health data, financial data, or data from children, the floor is higher. HIPAA, PCI-DSS, and COPPA have specific requirements that cannot wait. Know your regulatory environment.

Real-World Security Failures

Twitch (2021): Their entire source code and internal data leaked because of a server misconfiguration. Basic infrastructure security would have prevented it.

Parler (2021): Sequential API IDs with no authentication. Attackers scraped every post, including deleted ones with GPS coordinates. Parameterized queries would not have helped — this was a fundamental design flaw. But it illustrates what happens when security is an afterthought.

Heroku (2022): OAuth tokens stolen from their GitHub integration. This one is harder to prevent, but it highlights why secrets management matters — if tokens had been scoped and rotated regularly, the blast radius would have been smaller.

The Cost of Getting It Wrong

At enterprise scale, a breach means lawsuits, regulatory fines, and stock price drops. The company survives.

At startup scale, a breach can be fatal. You do not have the legal team, the PR team, or the customer base to absorb the hit. Your ten customers leave. Your investors lose confidence. Your product is dead.

Security breach impact by stage:
- Pre-launch: embarrassing, recoverable
- Post-launch, pre-PMF: potentially fatal to trust
- Post-PMF: expensive, survivable with good response
- Enterprise customers: contract violations, legal liability

The irony is that minimum viable security is cheap and easy. The four non-negotiables cost nothing and take hours to implement. The damage from skipping them can cost everything.

Common Pitfalls

Assuming private means secure. Private repos, private APIs, internal tools. If it is on the internet, assume someone will find it. Security through obscurity is not security.

Deferring all security to later. There is a difference between deferring SOC2 (fine) and deferring password hashing (not fine). Know which is which.

Copying security configs from Stack Overflow. That CORS configuration from 2018 might have Access-Control-Allow-Origin set to wildcard. That JWT example might use HS256 with a hardcoded secret. Understand what you are copying.

Using .env files in production. Environment variables should come from your platform's secrets management, not from a .env file sitting on a server. The .env file is for local development only.

Thinking "we are too small to be a target." Automated scanners do not care about your company size. Bots scan every IP range on the internet looking for exposed databases, default credentials, and known vulnerabilities. You are not too small. You are just unprotected.

Key Takeaways

  • Minimum viable security is four things: HTTPS, hashed passwords, parameterized queries, and secrets out of code.
  • These cost nothing and take hours to implement. There is no excuse to skip them.
  • Everything else can wait until you have users, revenue, or enterprise customers asking for it.
  • A security breach at startup scale can be fatal. At enterprise scale, it is expensive but survivable.
  • Private does not mean secure. Small does not mean safe. Automated scanners target everyone.
  • If you use a third-party auth provider and a modern ORM, you get two of the four for free. Do the other two yourself.