
OWASP Top 10: A Developer's Practical Guide to Critical Risks
Why the OWASP Top 10 Still Matters to Developers
I still see teams treat the OWASP Top 10 like something you hang on a wall and forget about. That misses the point. It is useful because it maps common failure modes to the parts of an app you already touch: routes, sessions, config, builds, logs, and integrations.
The list is not a threat model. It is a quick way to ask, “Where does this app trust the wrong thing?”
How to Use the List Without Turning It Into Checklist Theater
The mistake is reviewing the list as if every item applies equally to every codebase. It works better as a lens during normal work:
- new endpoint? check authorization, input handling, and logging
- new auth flow? check session state, reset paths, and recovery
- new dependency? check provenance and update path
- new integration? check outbound access and trust boundaries
That keeps the review tied to actual code instead of vague “security complete” claims.
A Practical Pass Through the Risks
Broken Access Control: Test the Backend, Not the Button
The UI is not the control. I usually test whether the server enforces ownership, role, and tenant checks even when the client changes IDs or hides controls.
A quick pattern:
// safe test idea: compare your own resource IDs with a second account in staging
await fetch("/api/orders/123", { credentials: "include" });
await fetch("/api/orders/456", { credentials: "include" });
If a free account can read, edit, or delete another user's object, the bug is on the server.
Cryptographic Failures: Check What Is Stored, Sent, and Logged
Crypto failures are often boring and expensive. Look for plaintext secrets in cookies, local storage, logs, backups, and analytics events. Also check whether sensitive fields are protected in transit and at rest.
Common mistakes:
- weak password hashing defaults
- reused encryption keys across environments
- secrets written to application logs
- tokens exposed in URL query strings
The impact is usually data exposure, not just “bad crypto.”
Injection: Follow User Input Into Queries and Commands
Injection is still about trust boundaries. Trace untrusted input into SQL, shell commands, template engines, LDAP, or unsafe deserialization.
// bad idea
db.query(`SELECT * FROM users WHERE email = '${email}'`);
Even if your ORM handles most queries, raw fragments, dynamic sorting, and admin search features are enough to bring the problem back.
Insecure Design: Look for Missing Rules, Not Just Bad Code
This category is about logic the app never enforced. The code may be clean and still wrong.
Ask simple questions:
- Can a user repeat an action that should be one-time only?
- Can they skip a paid step by calling the final endpoint directly?
- Are limits enforced per account, not just in the UI?
- Is abuse costed or rate limited?
If the business rule only lives in a product doc, it is probably missing from the implementation.
Security Misconfiguration: Default Settings and Hidden Exposure
This is where cloud and framework defaults bite. Open admin panels, debug endpoints, verbose errors, permissive CORS, and directory listings are still common.
I look for:
- staging features left enabled in production
- stack traces with secrets or internal paths
- public buckets or backups
- overly broad headers and CORS rules
A secure app can be undone by one bad environment flag.
Vulnerable and Outdated Components: Audit the Real Dependency Surface
Do not stop at package.json. The real surface includes transitive packages, build tools, browser bundles, containers, and OS layers.
What matters is not just “is this package old?” but:
- is it reachable from production?
- is the vulnerable path actually used?
- is there a patch or a safe replacement?
- can you remove it instead of waiting?
A dependency you never ship is noise. A dependency in the runtime path is risk.
Identification and Authentication Failures: Session, Reset, and Recovery Paths
Auth bugs often hide outside the login form. Check sessions, refresh tokens, password reset flows, email change flows, and MFA recovery.
I pay attention to:
- session rotation after login and privilege change
- token expiry and revocation
- reset links that work too long
- recovery flows that bypass stronger auth
If the user can take over an account through the “forgot password” path, the main login screen is irrelevant.
Software and Data Integrity Failures: Trust Boundaries in Builds and Updates
Modern attacks often ride on updates, webhooks, CI scripts, and third-party content. The question is: who can change what the app trusts?
Review:
- unsigned or unverified update feeds
- build steps that execute remote code
- webhook payloads accepted without verification
- deserialized data from external systems
This is a supply-chain problem as much as an app problem.
Security Logging and Monitoring Failures: Prove You Can Detect Abuse
If an attacker can brute force, enumerate, or abuse a flow without noise, the app is blind.
Logs should let you answer:
- who did it?
- what did they touch?
- from where?
- how often?
- did anything unusual happen before and after?
A good detection story does not need perfect security. It needs enough signal to spot misuse early.
SSRF and the Network Edge: What the App Can Reach on Your Behalf
If your app fetches URLs, previews links, imports files, or calls internal services, test where that request can go.
Look for:
- metadata endpoints
- internal admin hosts
- local services on private ports
- redirect chains that bypass host checks
The real issue is not “can the user send a URL?” It is “can the server be tricked into reaching something private?”
A Lightweight Review Workflow You Can Reuse in Pull Requests
I like a small repeatable pass:
- Identify the trust boundary.
- Trace user input into storage, queries, file paths, or outbound calls.
- Check authorization on the backend.
- Look for secrets, logs, and recovery paths.
- Confirm the failure mode with a safe test account.
This works better than random probing because it follows the code paths you actually changed.
What a Good Fix Looks Like
A good fix usually has three parts:
- the backend enforces the rule
- the test suite proves the rule
- the logs show abuse if the rule is tried again
If the fix lives only in the frontend, it is not a fix. If the fix removes the bug but leaves no regression test, it will come back.
Closing Notes for Teams Shipping Web Apps
The OWASP Top 10 is useful when it helps you ask sharper questions about your own app. I would rather see a team test one real access-control flow well than claim they reviewed ten categories on paper.
Use the list to find weak assumptions, then verify them where they actually live: in code, config, and runtime behavior.


