Most developers have heard of the OWASP Top 10 but couldn't name more than two or three items on it. Here's what actually matters, in plain language.
1. Broken Access Control
Users acting outside their intended permissions — like editing another user's data by changing an ID in a URL.
GET /api/orders/1234 ← your order
GET /api/orders/1235 ← someone else's order, if the server doesn't check ownership
Fix: Always verify the requesting user owns or has permission for the resource, server-side, on every request.
2. Cryptographic Failures
Sensitive data (passwords, tokens, PII) stored or transmitted without proper encryption.
Fix: Use HTTPS everywhere, hash passwords with bcrypt/argon2 (never plain hashes like MD5), and encrypt sensitive data at rest.
3. Injection
Untrusted input executed as code — SQL injection is the classic example.
// Vulnerable
db.query(`SELECT * FROM users WHERE email = '${input}'`);
// Safe — parameterized query
db.query("SELECT * FROM users WHERE email = ?", [input]);Fix: Always use parameterized queries or an ORM. Never string-concatenate user input into a query.
4. Insecure Design
Security flaws baked into the architecture itself, not fixable by patching code — e.g., a password reset flow with no rate limiting.
Fix: Threat-model sensitive flows during design, not after an incident.
5. Security Misconfiguration
Default credentials, verbose error messages leaking stack traces, unnecessary services exposed.
Fix: Disable debug mode in production, remove default accounts, and minimize the attack surface.
6. Vulnerable and Outdated Components
Using dependencies with known CVEs.
Fix: Run npm audit / pip-audit in CI, and keep dependencies patched on a schedule, not just reactively.
7. Identification and Authentication Failures
Weak session management, predictable tokens, no multi-factor authentication.
Fix: Use established auth libraries rather than rolling your own, enforce MFA for sensitive accounts, and rotate session tokens on privilege changes.
8. Software and Data Integrity Failures
Trusting unsigned updates or CI/CD pipelines without integrity checks — the root cause of many supply-chain attacks.
Fix: Verify package signatures, pin dependency versions, and restrict who can publish to your package registry.
9. Security Logging and Monitoring Failures
Breaches that go undetected for months because nothing was logged or alerted on.
Fix: Log authentication events and access-control failures, and alert on anomalies — don't just log everything and never look at it.
10. Server-Side Request Forgery (SSRF)
Tricking a server into making requests to internal resources on the attacker's behalf.
POST /fetch-preview
{ "url": "http://169.254.169.254/latest/meta-data/" }
Fix: Validate and allowlist outbound request destinations; never let user input directly control a server-side fetch target.
| # | Risk | Root cause |
|---|---|---|
| 1 | Broken Access Control | Missing server-side ownership checks |
| 2 | Cryptographic Failures | Sensitive data not properly encrypted |
| 3 | Injection | Untrusted input executed as code |
| 4 | Insecure Design | Flaws in the architecture itself |
| 5 | Security Misconfiguration | Default credentials, verbose errors |
| 6 | Vulnerable Components | Dependencies with known CVEs |
| 7 | Auth Failures | Weak sessions, no MFA |
| 8 | Integrity Failures | Unsigned updates, unverified CI/CD |
| 9 | Logging Failures | Breaches going undetected |
| 10 | SSRF | Server tricked into internal requests |
The pattern across all ten
Nearly every item traces back to one root cause: trusting input or configuration that an attacker can influence. Validate at every boundary, and you'll have already mitigated most of this list.
Broken Access Control and weak authentication are exactly what two-factor authentication is designed to raise the cost of exploiting — worth pairing with the access-control fixes above rather than treating either as sufficient alone.
A concrete SSRF walkthrough, since it's the least intuitive item
SSRF (item 10) is the one developers most often struggle to picture concretely, so it's worth one more example. Imagine a feature that generates a link preview by fetching whatever URL a user submits:
// Vulnerable
app.post("/preview", async (req, res) => {
const response = await fetch(req.body.url);
res.json({ html: await response.text() });
});This looks harmless — it's just fetching a URL to show a preview. But nothing stops a user from submitting an internal address instead of a real webpage:
POST /preview
{ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }
On many cloud providers, that address is the instance metadata service — internal-only, unreachable from the public internet directly, but reachable from inside the server making the fetch. The server, trusted to make internal-network requests, has just been tricked into fetching (and potentially returning to the attacker) cloud credentials it should never expose. The fix is the same principle as injection generally: validate what the server is allowed to fetch (an allowlist of external domains, blocking internal/private IP ranges explicitly) rather than trusting user input to specify an arbitrary destination.
Where to start if you can only fix one thing this week
Given limited time, Broken Access Control and Injection are worth prioritizing first — they're both consistently among the most exploited categories in real-world breach data, and both have a concrete, mechanical fix (server-side ownership checks; parameterized queries) rather than requiring a broader process change. Cryptographic Failures (HTTPS everywhere, proper password hashing) is the other quick, high-leverage check, since a misconfiguration there is often a single settings change away from being fixed entirely.
This list isn't static — check the revision date
The specific ordering and even category boundaries shift between OWASP Top 10 revisions as real-world vulnerability data changes (Insecure Design, for instance, is a relatively recent addition reflecting a shift toward catching architectural flaws earlier). Treat the ten items above as the current shape of the list, worth periodically re-checking against OWASP's own published version rather than treated as permanently fixed — the underlying principle (validate everything an attacker can influence) is durable even as the specific list evolves.
Common mistakes
- Treating client-side validation as a security control. Anything enforced only in the browser (a disabled button, a JS length check) can be bypassed entirely by calling the API directly — client-side validation is a UX nicety, server-side validation is the actual control.
- Rolling a custom authentication or session system instead of using an established library. Auth is deceptively easy to get mostly right and genuinely hard to get entirely right — the failure modes (session fixation, timing attacks on comparisons) are subtle and well-studied in existing libraries for a reason.
- Assuming an ORM makes SQL injection impossible by default. Most ORMs are safe when used normally, but nearly all of them offer an escape hatch for raw queries — string-concatenating user input into that raw-query path reintroduces the exact vulnerability the ORM was protecting against.
- Logging sensitive data (passwords, full card numbers, session tokens) "just in case it's useful for debugging." This turns your own logs into a target — log identifiers and context, never the secret itself.
Related reading
- Core Web Vitals Explained: What Actually Affects Your Score — shares tags: web-development, programming.
- The JavaScript Event Loop, Explained With Diagrams — shares tags: programming, web-development.
- A Practical Guide to Responsive Design in 2026 — shares tags: web-development, programming.
- Two-Factor Authentication: How It Actually Works — shares tags: cybersecurity (same category).
- Android App Permissions: A User's Guide to Staying Safe — shares tags: cybersecurity.