The Top 10 Security Vulnerabilities a Code Audit Catches
The bugs that actually show up
Every security vendor has a top-ten list. Most of them are marketing wrapped around the OWASP Top 10, which is a fine document but describes categories, not the specific mistakes sitting in your repo right now.
This list is different in one way: it’s ordered by what we actually find when we read real production codebases at startups and scale-ups, not by theoretical severity. Some famous vulnerabilities (buffer overflows, say) barely appear in modern SaaS stacks. Others that get less press (broken object-level authorization) appear in nearly every codebase we open.
Here’s what a manual audit catches, and why half of it sails past automated scanners.
1. Broken object-level authorization (IDOR)
The single most common serious finding. The code checks that a user is logged in, but not that the resource they’re requesting is theirs:
// Vulnerable: any authenticated user can read any invoice
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
const invoice = await db.invoices.findById(req.params.id);
res.json(invoice);
});The fix is boring and must be everywhere:
const invoice = await db.invoices.findOne({
_id: req.params.id,
accountId: req.user.accountId,
});
if (!invoice) return res.sendStatus(404);Scanners almost never catch this, because the code is syntactically fine: the bug is a missing line, and only a human tracing the data model knows it should be there. This is the heart of auditing authentication and authorization.
2. Secrets committed to the repository
API keys, database URLs, JWT signing secrets: in .env files that got committed, in config files, in test fixtures, or in a commit from 2023 that’s still in history even though the file was “deleted.” Git never forgets, and neither do the bots scraping public repos. We cover the mechanics (and the cleanup) in how exposed secrets sneak into your repo.
3. Injection: SQL and its cousins
Still alive in 2026, mostly at the edges: the one hand-built report query, the search endpoint with dynamic ORDER BY, the raw query someone wrote because the ORM was slow. String-concatenated SQL is the classic, but the same pattern shows up as NoSQL operator injection ({"$gt": ""} as a password), command injection in shell-outs, and template injection. There’s a full checklist in SQL injection and input validation.
4. Missing function-level authorization
The admin routes check for admins in the UI, by hiding the button. The API endpoint behind the button checks nothing. Any user who opens dev tools can call POST /api/admin/users/:id/impersonate directly. UI-enforced permissions are not permissions; they’re suggestions.
5. Vulnerable and abandoned dependencies
The average modern app is mostly other people’s code, and some of it is carrying known CVEs, some is unmaintained, and occasionally one package is doing something it shouldn’t. The audit question isn’t “are there CVEs?” (there always are) but “which ones are reachable in this codebase?” That triage is the entire subject of auditing your third-party packages.
6. Broken session and token handling
A greatest-hits collection: JWTs signed with a secret like "secret", tokens with no expiry, alg: none accepted by an old library version, session cookies missing HttpOnly/Secure/SameSite, and password-reset tokens that never get invalidated after use. Each one is a one-line fix; each one is invisible until someone looks.
7. Server-side request forgery (SSRF)
Any feature that fetches a user-supplied URL (webhooks, “import from URL”, link previews, PDF renderers) can be pointed at things the user shouldn’t reach: http://169.254.169.254/ (cloud metadata endpoints, which hand out credentials), internal admin services, or localhost. The fix is an allowlist of destinations plus blocking private IP ranges after DNS resolution, not naive hostname string checks.
8. Mass assignment
The handler that does User.update(req.body) for convenience, letting a request body quietly include "role": "admin" or "credits": 999999. Modern frameworks give you allowlisting (params.require(:user).permit(:name, :email) in Rails, explicit DTOs elsewhere); the vulnerability is not using it.
9. Sensitive data in logs and error responses
Stack traces returned to the client in production. Full request bodies, passwords included, shipped to a third-party logging SaaS. Database errors that echo the query back, giving an attacker a free schema tour. Individually these are “informational” findings; combined with anything else on this list, they’re the reconnaissance phase of a breach.
10. Missing rate limiting and abuse controls
No throttle on login (credential stuffing), on password reset (email bombing and token brute-force), on signup (spam), or on the expensive export endpoint (a self-inflicted denial of service). Rate limiting is unglamorous, which is exactly why it’s missing.
Why scanners miss half of this list
Look back at the list. Items 3, 5, and parts of 2 and 6 are pattern-shaped: a scanner can find string-concatenated SQL, known CVEs, and high-entropy strings. That’s why any competent audit runs those tools first.
But items 1, 4, 7, 8, and most of 9 and 10 are semantic. They’re not bad code; they’re missing code, or correct code in the wrong place. db.invoices.findById(id) is a perfectly valid line; it’s only a vulnerability because of what your data model means. No tool without a model of your business can flag it. That’s the structural reason manual review exists, and it’s why the review takes days rather than minutes, a tradeoff we’ve quantified in how long a code audit takes.
A quick self-assessment
Before any audit, you can grep your own repo for the cheap wins:
- Search for
findById,find_by_id, and rawWHERE id =: does each one also scope by owner/tenant? - Search for
password,secret,api_keyin tracked files and git history. - Check your JWT config: algorithm pinned? Expiry set? Secret from the environment?
- Open your admin endpoints and ask: what enforces admin, the UI or the server?
- Run
npm audit/pip-audit/cargo auditand count the criticals you’ve been ignoring.
If any of those made you pause, that’s signal.
Get a free code audit
We run this exact review (all ten classes, by hand, against your actual codebase) as a genuinely free audit. No trial, no card, no “free assessment” that turns into a sales deck. A Webisoft engineer reads your repository and sends you a written report of findings, ranked by real-world risk, with concrete fixes. If you want an independent set of eyes on your code before someone less friendly provides one, get a free code audit.