Most web app breaches don't come from some exotic zero-day exploit. They come from basic mistakes that slip through because "it works" got prioritized over "it's secure." Here are the ones I see most often, along with the actual fix — not just the theory.
- Storing Passwords the Wrong Way
Still seeing this in 2026: plaintext passwords, or worse, unsalted MD5/SHA1 hashes. Both are trivially crackable with rainbow tables.
Fix: Use a purpose-built password hashing algorithm — bcrypt, scrypt, or Argon2. They're slow by design, which is exactly what you want against brute-force attempts.
// Bad
const hashedPassword = crypto.createHash('md5').update(password).digest('hex');
// Good
const bcrypt = require('bcrypt');
const hashedPassword = await bcrypt.hash(password, 12);
- Trusting Client-Side Validation Alone
Frontend validation is a UX nice-to-have, not a security control. Anyone can bypass it with dev tools or a direct API call.
Fix: Validate and sanitize everything server-side too, no exceptions. Treat every incoming request as hostile until proven otherwise.
- SQL Injection via String Concatenation
This one's ancient, but it's still everywhere — especially in codebases with a "we'll fix it later" mentality.
// Bad — vulnerable to injection
const query = `SELECT * FROM users WHERE email = '${userInput}'`;
// Good — parameterized query
const query = 'SELECT * FROM users WHERE email = ?';
db.execute(query, [userInput]);
Use an ORM or parameterized queries. Always.
- Missing Rate Limiting on Auth Endpoints
Login and password-reset endpoints without rate limiting are an open invitation for credential stuffing and brute-force attacks.
Fix: Add rate limiting at the endpoint level, and consider account lockouts or CAPTCHA after repeated failures.
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts, try again later.'
});
app.post('/login', loginLimiter, loginHandler);
- Exposing Sensitive Data in API Responses
A shockingly common one: returning full user objects from an API, including password hashes, internal IDs, or fields never meant to leave the server.
Fix: Explicitly whitelist what gets serialized in a response. Never rely on "the frontend just won't display it" as your security boundary.
- Hardcoded Secrets in the Codebase
API keys and DB credentials committed directly into source, sometimes even pushed to a public repo. This happens more than anyone wants to admit.
Fix: Use environment variables and a .gitignore'd .env file, or better, a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) for production.
- Skipping CSRF Protection on State-Changing Requests
If your app uses cookie-based sessions and doesn't validate CSRF tokens on POST/PUT/DELETE requests, you're exposed to cross-site request forgery.
Fix: Use CSRF tokens (most frameworks have built-in middleware for this) or, if feasible, move to a SameSite cookie policy combined with proper CORS configuration.
- Ignoring Dependency Vulnerabilities
node_modules accumulates dozens of transitive dependencies, and outdated packages are one of the most common attack vectors in real breaches.
Fix: Run npm audit regularly, and wire up Dependabot or Snyk to catch vulnerable dependencies before they ship.
The Underlying Pattern
Almost every item on this list comes down to the same root cause: trusting input, trusting the client, or trusting that "no one will find this." Security isn't a checklist you run once before launch — it's a habit baked into how you write and review code.
If security auditing isn't something your team has bandwidth for internally, it's worth having someone specialized take a pass at it periodically. Agencies like I NEXTETS offer dedicated cyber security assessments alongside their development work, which can catch exactly this kind of issue before it ships to production rather than after an incident forces the conversation.
Security debt compounds just like technical debt — the earlier you catch these, the cheaper they are to fix.
What's the worst security mistake you've caught in a codebase? Drop it in the comments — always good to learn from real war stories.
Top comments (0)