Most CTOs we talk to know "OWASP Top 10" as the web checklist, the one with XSS, SQLi, CSRF and the rest. Fewer know that OWASP publishes a completely separate list specifically for APIs, and that if your product looks anything like the modern stack (a React or Vue frontend talking to a REST or GraphQL backend, maybe a mobile app hitting the same endpoints) then the API Top 10 is the list that actually maps to your real attack surface. The web one tells you what your CDN and framework probably already block. The API one tells you where your own code is leaking.
We've broken hundreds of APIs in the last few years: REST, GraphQL, internal microservices someone exposed to the internet by accident, mobile backends nobody had touched in three years. The pattern is brutally consistent: four of the ten categories cause the overwhelming majority of the damage. Almost every "stop everything and patch this tonight" call falls into one of those four.
This post is for CTOs and tech leads who want to know which four to make their dev team care about first. We're going to be opinionated, point at the four that account for roughly three-quarters of what we find, and tell you exactly what to ask your team this week.
Why APIs are different from web apps
Most classic web vulnerabilities require a user to be tricked. XSS needs someone to click your payload. CSRF needs someone already logged in to visit your attacker page. Clickjacking needs a user to be looking at the wrong thing. The defender has a margin of error because the attack has a human in the loop.
API vulnerabilities don't work like that. Anyone with a valid token (or often no token at all) can hit the endpoint in a loop, from a script, with no UI, no user, and no convincing. Authorization on an API is enforced by your code, not by the absence of a button in your frontend. A missing check on /api/users/:id isn't a "what if a user is tricked" problem. It's a data breach with one curl. That mental shift is the single most useful thing a CTO can internalise about API security.
API1: Broken Object Level Authorization (BOLA)
BOLA is the number one finding in API pentests. Not "frequently in the top three." It is, hands down, the bug we find most often, and it is almost always at least a High severity. If you only ever fix one class of API bug, fix this one.
The shape is always the same. An endpoint accepts an identifier (an order ID, a user ID, an invoice number, a document UUID) and returns the corresponding object. The authentication check confirms you're a logged-in user. There is no further check confirming that this particular user is allowed to see this particular object. So you change the ID in the URL and read someone else's data.
In multi-tenant SaaS this becomes tenant-boundary BOLA, which is worse: you don't just leak one user's data, you leak one customer company's entire dataset to another. We've found this in payroll platforms, EHR systems, internal HR tooling, B2B billing dashboards, anywhere a developer assumed "we filter by tenant in the frontend" was enough.
Why it's so common: developers test the feature as themselves. They open the orders page, they see their orders, the page works, they ship it. They never log in as a second user and try to read the first user's data, because that's not a story in the sprint. The check that ties the object to the requesting user lives in nobody's definition of done.
Why it's so bad: it's scriptable. An attacker who finds one BOLA on one endpoint typically tries every other endpoint, finds half of them have the same bug, and walks out with the whole database in an afternoon.
How to find it: every route handler that takes an ID parameter must, somewhere, check that the requesting user has the right to see that ID: middleware, a policy object, a row-level filter in the ORM, whatever. A pentester finds it with two test accounts in two tenants: walk every endpoint as Alice, replay every request as Bob. Anything that returns 200 instead of 403 is a finding.
API2: Broken Authentication
Authentication on APIs goes wrong in a small number of very familiar ways, and we find at least one in most engagements:
- No rate limit on login. Credential stuffing is a script. If your login endpoint accepts 50 requests a second with no lockout or backoff, someone with a leaked password list will own a percentage of your users this quarter.
- JWT footguns. We still find
alg=noneaccepted in 2026. We find weak HMAC secrets ("secret", "changeme", the app name). We find expired tokens still accepted because someone commented out the expiry check during debugging. We find refresh tokens that never rotate. - OAuth state missing. Skip the
stateparameter and an attacker can attach their own session to a victim's account via the callback. - Password reset that confirms the username. "User not found" vs "Reset email sent" tells an attacker which emails are real. Combine with no rate limit and you have a free enumeration oracle.
- Refresh-token reuse. A leaked refresh token should be detectably leaked the moment it's used twice. Most implementations we test don't notice.
None of these are clever bugs. All of them are still everywhere.
API3: Broken Object Property Level Authorization (Mass Assignment)
This used to be called Mass Assignment, and most developers still know it under that name. The shape: your user-update endpoint takes a JSON body and applies whatever fields are in it to the user record. Your frontend sends {"name": "...", "email": "..."}. The backend trustingly calls User.update(req.body). The users table has a role column. Guess what happens.
Same bug, infinite variants. {"is_verified": true}. {"subscription_tier": "enterprise"}. {"organization_id": 1} to jump tenants. {"balance": 999999} on a wallet endpoint we tested last year.
Why developers miss it: their UI never sends those fields, so they assume nobody else will. But the API doesn't know what the UI sends. It only knows what arrives. The fix is to whitelist the fields the endpoint accepts (a DTO, a Pydantic model, a Joi schema, an explicit permit(:name, :email) in Rails) and reject everything else. Reading req.body directly into your ORM is, in 2026, malpractice.
API4: Excessive Data Exposure
The bug: your API returns the full database row for an object, and your frontend only displays some of the fields. The fields the frontend hides are still in the JSON. An attacker who opens devtools (or just curls the endpoint) reads them all.
The fields that should never be in the response and routinely are: password_hash, mfa_secret, password_reset_token, internal_notes, stripe_customer_id, session_token, full date of birth, government ID numbers, internal credit scores, fraud-review flags. We've seen all of these come back from production endpoints in the last year.
The mental model that fixes this: if the field is in the response, the field is leaked. Hiding it in the frontend is not security. It's interior decoration. The endpoint must filter the output: a response schema, a serializer with an explicit allowlist of fields, a GraphQL type that doesn't include the sensitive columns. Sending the whole row "because the frontend doesn't show it" is the same mistake as mass assignment in reverse.
The other six: quick tour
These exist, we find them, but they're either rarer or lower-impact in practice than the four above:
- API5: Broken Function Level Authorization (BFLA). Like BOLA but for whole endpoints. A regular user calls
/api/admin/users/deleteand it works because the route is only hidden, not protected. BFLA does cause real damage and we find it often. It's the strongest of the "other six." - API6: Unrestricted Access to Sensitive Business Flows. Bots scraping your inventory, scalping a launch, abusing a coupon-issue endpoint. Less a code bug than an abuse-design problem.
- API7: Server-Side Request Forgery (SSRF). An endpoint that fetches a URL on behalf of the user (image proxy, webhook tester, URL preview) and can be pointed at
http://169.254.169.254/or your internal services. Real damage when it lands, especially in cloud environments. - API8: Security Misconfiguration. Verbose error messages, debug endpoints in prod, permissive CORS, missing security headers, default credentials on an admin panel.
- API9: Improper Inventory Management. The classic "we forgot we had a
v1still up." Old API versions, undocumented endpoints, staging boxes accidentally indexed by Google. - API10: Unsafe Consumption of APIs. Your backend trusts a third-party API's response too much, and an attacker who compromises that third party owns you too.
BFLA and SSRF in particular cause real damage. But across our engagements, the top four (BOLA, broken auth, mass assignment, excessive data exposure) account for roughly seventy-five percent of our serious API findings. Fix those four classes systematically and your API attack surface shrinks more than any other single intervention.
What a CTO should ask their dev team this week
Six questions. The answers tell you most of what you need to know:
- Do we have a written rule that every API route has an authorization check, and is there a way to fail CI if a new route ships without one? "It's in the code review checklist" doesn't count.
- If a logged-in user changed an ID in the URL, could they read another user's data? The honest answer for most teams is "we don't know, we've never tested." That's a finding by itself.
- Do we whitelist input fields with a schema (DTO, Pydantic, Joi, Zod, strong parameters) or do we accept raw
req.bodyinto the ORM? - Do we filter output fields with a serializer or response schema, or do we send the whole DB row?
- Do we rate-limit login, password reset, MFA-verify, and signup endpoints? Specifically those, not just a global API rate limit.
- If we use JWT: what algorithm, where does the secret live, who can rotate it, and how do we revoke a token before its expiry?
If those six conversations produce confident, specific answers, you're ahead of most of the companies we test. If they produce shrugs, "I think so," or "we should probably check," you have a backlog.
Where this fits in a pentest
If you'd rather not run that audit yourself, this is what an API engagement is for. A typical API security test from us runs four to seven days for a mid-sized API surface. We ask for at least two test accounts (often three when there's a tenant boundary to break), a Postman collection or OpenAPI spec from your team, and a staging environment we can hammer without setting off prod alerting.
What we do with that: walk every endpoint as one user, replay every request as the other, fuzz IDs, fuzz request bodies, try every JWT trick, scan every response for fields that shouldn't be there, and probe the auth flow for the classics. The output is a findings report your dev team can action: each issue mapped to the OWASP API category, ranked by severity, with reproduction steps and a recommended fix.
The four categories in this post are what we look for first, and the ones we expect to find.