OWASP Top 10 for Developers — What Actually Matters in 2026
A practitioner's take on the OWASP Top 10 — skipping the theory and focusing on what developers actually need to know to write secure code in 2026.
The Problem With How OWASP Top 10 Is Usually Taught
Every AppSec course covers OWASP Top 10. Most of them cover it badly — 10 slides, one example per category, no context on which ones actually show up in real codebases versus which ones are mostly theoretical.
After 7+ years doing code review and penetration testing across dozens of applications, I have opinions on which of these categories you should be spending your time on. This post is that opinion, structured around what I actually find in real applications in 2026.
A01: Broken Access Control — #1 for a Reason
This has been the top finding for three years running, and from my experience, that's accurate. Not SQL injection, not XSS — missing or broken authorization checks.
The pattern I find most often:
# Authentication check present — user must be logged in
@login_required
def update_invoice(request, invoice_id):
invoice = Invoice.objects.get(id=invoice_id)
# Missing: does this user OWN this invoice?
invoice.amount = request.POST.get("amount")
invoice.save()
This is an IDOR (Insecure Direct Object Reference). Any authenticated user can modify any invoice by changing the invoice_id parameter.
The fix is one line:
invoice = Invoice.objects.get(id=invoice_id, owner=request.user)
But the real fix is architectural: enforce object-level authorization at the data layer, not scattered across views. If you're using Django, use a custom manager. If you're using Rails, use a policy object. Make it impossible to accidentally fetch data without the ownership check.
What to actually do: Audit every endpoint that takes an ID parameter. Verify that ownership/role is checked, not just authentication.
A02: Cryptographic Failures — Focus on Data at Rest
This category used to be called "Sensitive Data Exposure" and the rename reflects where the actual risk is: cryptography being used wrong, not absent.
What I find in real codebases:
-
Passwords hashed with MD5 or SHA-256 — still common in legacy code. Use bcrypt, Argon2, or scrypt. The reason SHA-256 is wrong for passwords isn't that it's broken — it's that it's too fast. A GPU can compute billions of SHA-256 hashes per second.
-
JWT secrets that are weak or reused across environments —
secret,changeme, the app name. Rotate secrets, use long random values, keep prod separate from dev. -
PII stored in logs — email addresses, phone numbers, partial card numbers ending up in application logs shipped to a third-party aggregator. Audit your log output.
The encryption-in-transit problem (HTTP vs HTTPS) is largely solved by 2026 — Let's Encrypt and cloud platforms make TLS nearly free. If you're still shipping HTTP, that's the first thing to fix.
A03: Injection — SQL Still Happens
SQL injection should be solved by parameterized queries, and in most modern frameworks it is — until someone reaches for raw SQL.
# This is found in real codebases, usually in "optimized" queries
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")
# This is safe
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
The same pattern applies to:
- LDAP injection — common in enterprise SSO integrations
- NoSQL injection — MongoDB queries built from unsanitized JSON input
- OS command injection — shell=True in Python subprocess calls with user input
In 2026, the new injection frontier is prompt injection in LLM-backed features. If your application passes user input into an LLM prompt and acts on the output, you have an injection surface. The mitigations are still maturing, but the risk is real.
A04: Insecure Design — This One Is Different
A04 is the only category that can't be fixed with a code change. Insecure design means the threat model was wrong, or nonexistent.
The canonical example: a password reset flow that sends a reset link to the email on file. Secure design. Now consider a password reset flow that asks your "security question" (mother's maiden name, first pet). Insecure design — no amount of correct implementation fixes the underlying flaw.
In practice, A04 findings look like:
- Multi-tenant apps with no data isolation at the query layer — isolation is enforced by application code that can be bypassed
- Admin features protected only by UI hiding, with no server-side role check
- Workflows where a user can skip steps (e.g., pay → confirm → skip confirm step)
What to actually do: Threat model before building features that handle sensitive data or money. Even a 2-hour exercise catches most of these.
A05: Security Misconfiguration — The One That Actually Gets You Breached
This category is responsible for more real breaches than A03 and A04 combined, because it includes:
- S3 buckets left public
- Default credentials on admin panels
- Debug mode enabled in production (
DEBUG=Truein Django, stack traces returned in Express) - Unnecessary ports open in security groups
- CORS configured as
*on an API that uses cookie authentication
My checklist for every deployment:
- Remove or password-protect all default admin interfaces
- Disable debug mode and stack trace responses
- Audit CORS policy — is
*intentional? - Verify S3/blob storage bucket policies
- Check that error responses don't leak version numbers or stack traces
Automated misconfiguration scanning with Trivy or Checkov catches most of these in CI before they reach production.
A06: Vulnerable Components — Keep Dependencies Updated
A06 is straightforward: your application is only as secure as its dependencies. Log4Shell was a vulnerable component. The XZ Utils backdoor was a vulnerable component.
In 2026 the tooling for this is mature:
- GitHub Dependabot — automatic PRs for vulnerable dependencies
- Trivy — scans container images and lock files in CI
- OWASP Dependency-Check — for Java/Maven ecosystems
The practice to build: review your npm audit / pip-audit / bundle audit output weekly. Treat HIGH and CRITICAL findings like P1 bugs.
A07 through A10 — The Ones You Can Address With Headers
A07 (Authentication failures), A08 (Software integrity failures), A09 (Logging failures), A10 (SSRF) are real risks but more situational.
For most applications, the baseline security headers address a significant chunk of A07-level risk:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=()
SSRF (A10) deserves special attention if your application fetches URLs on behalf of users (webhooks, URL previews, import-from-URL features). Validate that the resolved IP is not in RFC 1918 space before making the request.
My Actual Priority Order for a New Codebase
If I'm doing a first-pass security review of a new application, this is the order I look:
- A01 — Find every endpoint, check authorization on each one
- A05 — Check deployment configuration, CORS, headers
- A02 — Check how passwords and PII are stored
- A06 — Run
npm auditor equivalent, triage CRITs - A03 — Grep for raw SQL, shell=True, eval()
- Everything else
A01 and A05 together cover the majority of what I find in real applications. Start there.