Skip to main content

OWASP API Security Top 10: what a source code scan can and cannot find

· 9 min read
Vulkro
Security research

The OWASP API Security Top 10 is the list most teams reach for when someone asks "is our API secure?". The 2023 edition has ten items, from broken object level authorization to unsafe consumption of third-party APIs. It is a good list. It is also a mix of very different kinds of problem, and no single tool sees all of them.

Some items are visible in the code that handles a request: a lookup with no ownership predicate, a URL fetched straight from the body. Some are partly in code and partly in the gateway, the CDN and the deployment. And some are about what your business would lose if a flow were automated, which no parser can read.

This post goes through all ten and says plainly which a source code scan can find, which it can only hint at, and what Vulkro checks for each.

Why a source code scan sees some items and not others​

A static scan reads routes, handlers, middleware, queries and configuration files. It can follow request data to a dangerous call, and it can notice a check that is missing between them. It cannot see:

  • Runtime configuration set outside the repository: gateway rate limits, WAF rules, headers added by a proxy.
  • Deployed reality: an old API version still running on a forgotten host, or a staging environment exposed to the internet.
  • Business intent: whether a record should be shared, or whether ten thousand signups an hour is a launch or an attack.

So the honest answer per item is one of three: the code shows it, the code shows part of it, or the code cannot show it. Each Vulkro finding carries the same honesty as Proven, Unproven or Not checked, explained in Proven, Unproven, Not checked.

OWASP API Security Top 10 mapped to source code checks​

ItemVisible in source?What Vulkro checks
API1 Broken Object Level AuthorizationMostlyLookups and writes by a caller-supplied id with no ownership or tenant predicate (idor-authn-no-ownership), tenant ids taken from the request body
API2 Broken AuthenticationMostlyRoutes reachable with no auth guard, tokens decoded without signature verification (forgeable-token-auth), alg: none, hardcoded JWT secrets
API3 Broken Object Property Level AuthorizationPartlyMass assignment: a request body bound straight into a model or ORM update
API4 Unrestricted Resource ConsumptionPartlyRoutes with no rate limiter in the request pipeline, list endpoints without pagination, GraphQL without depth or complexity limits
API5 Broken Function Level AuthorizationMostlyAdmin-pattern routes with no role check, role changes with no role check, over-wide permitAll rules
API6 Unrestricted Access to Sensitive Business FlowsBarelyA heuristic: handlers named like checkout, signup, password reset or transfer with no rate limiter or challenge
API7 Server Side Request ForgeryYesRequest data followed into HTTP client calls with no recognised URL guard
API8 Security MisconfigurationPartlyCORS reflecting origins with credentials, debug mode, insecure cookies, weak crypto, committed secrets, cloud and container config files
API9 Improper Inventory ManagementPartlyThe endpoint inventory from code, drift against a committed OpenAPI file, endpoint changes between git refs
API10 Unsafe Consumption of APIsPartlyUpstream API responses passed to clients or used in decisions without validation

Items a source scan finds well​

API1, broken object level authorization. The bug is a missing predicate, and the predicate would be in the code if it existed. Vulkro reports authenticated lookups by a caller-supplied id that are not tied to the caller. These findings are marked Not checked, because whether a record should be shared is sometimes a business rule. We go through Express and NestJS fixes in IDOR prevention and the multi-tenant version in the missing tenant column.

API2, broken authentication. Two shapes dominate. A route with no guard at all, which needs an accurate map of which middleware applies to which route (including Next.js server actions, which we covered in server actions are public endpoints). And sign-in code that reads a token without verifying it:

// Vulnerable: decode reads the claims, it does not check the signature
const claims = jwt.decode(req.headers.authorization?.slice(7) ?? '');
req.user = { id: claims.sub };

// Fixed: verify against your key and pin the algorithm
const claims = jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] });

Vulkro reports the first as Critical, with the number of endpoints that sign-in guards, and those endpoints then read as unauthenticated. The full story is in the sign-in that never checks the signature.

API5, broken function level authorization. Admin routes with authentication but no role, and handlers that write a role field from caller input without checking the caller's own role. Source shows both.

API7, server-side request forgery. A data-flow problem: the request reaches an HTTP client. That is the kind of thing static analysis was built for, and Vulkro shows each step from the request to the call. The guards it recognises, and the fixes that hold, are in SSRF in Node.js.

Items a source scan sees only in part​

API3, object property level authorization. The write half (mass assignment, Object.assign(user, req.body)) is visible and is what Vulkro checks. The read half, an endpoint that returns more fields than the client should see, depends on which fields are sensitive to whom. The database schema map helps here by labelling personal, financial and credential columns, but deciding what each role may read is a review, not a finding.

API4, unrestricted resource consumption. Vulkro reports routes with no rate limiter in the request pipeline, lists without pagination and GraphQL without limits. If your limits live in an API gateway or a CDN, the code will look unprotected while production is fine. Read these findings against your infrastructure.

API8, security misconfiguration. Configuration in the repository is in scope: CORS settings, cookie flags, debug switches, Terraform, Kubernetes and Docker files, secrets. Configuration applied at deploy time, or by a proxy, is not.

API9, improper inventory management. This item is about knowing what you run. Source gives you half: the routes the code actually serves. vulkro discover lists every endpoint with its handler and guards, vulkro openapi writes an OpenAPI 3.1 spec from the code, and vulkro diff <ref> shows which endpoints changed since a branch, tag or commit. When the repository has its own OpenAPI file, the scan reports drift in both directions: routes in code but not in the spec, and routes in the spec with no code. What source cannot tell you is which old versions are still deployed, on which hosts. That needs an inventory of your running environments.

API10, unsafe consumption of APIs. Vulkro flags upstream responses passed through or acted on without a schema check. Whether the upstream itself is trustworthy, and what happens when it is compromised, is a design question.

Items a source scan cannot really find​

API6, unrestricted access to sensitive business flows. OWASP's examples are buying up limited stock to resell it, reserving every available slot, and scripting fake signups to farm referral credit. The flaw is not a bad line of code. It is the absence of a business-level control against automation, and only the business knows which flows are sensitive. Vulkro's check is a deliberately modest heuristic: sensitive-sounding handlers with no limiter or challenge. Treat it as a prompt to have the conversation, and expect gaps in both directions.

The parts of API4 and API9 that live outside the repository belong here too. For those, use runtime testing against a deployed environment, gateway configuration review and an asset inventory.

OWASP API Security Top 10 checklist​

  1. Every route has an explicit authentication decision, including server actions and webhooks.
  2. Every lookup by a caller-supplied id is scoped to the caller or tenant.
  3. Tokens are verified, never just decoded.
  4. Request bodies are mapped to allowed fields, never bound wholesale.
  5. Admin functions check a role, not just a login.
  6. Outbound fetches of user-supplied URLs are allowlisted or checked at connect time.
  7. Rate limits exist somewhere, in code or at the edge, and you know where.
  8. The endpoint list from code matches the spec, and old versions are retired.
  9. Business-critical flows have been reviewed for automation abuse by someone who owns the business risk.

Frequently asked questions​

Can SAST cover the OWASP API Security Top 10?​

It covers the items that are visible in code well (authorization, authentication, SSRF, mass assignment), parts of the configuration and inventory items, and very little of business flow abuse. A full programme adds runtime testing and infrastructure review.

What changed in the 2023 OWASP API Security Top 10?​

The 2023 edition added server-side request forgery, sensitive business flows and unsafe consumption of APIs as their own items, and merged excessive data exposure and mass assignment into broken object property level authorization. Broken object level authorization stayed first.

Is API rate limiting something a code scan can check?​

Partly. It can see whether a limiter is wired into a route in code. It cannot see limits enforced by a gateway, load balancer or CDN, so a missing-limiter finding should be checked against your infrastructure before you act on it.

How do I get an inventory of my API endpoints from code?​

Run vulkro discover for a list of every route with its method, handler and guards, or vulkro openapi to generate an OpenAPI 3.1 specification from the code. Compare it with what is deployed to find the endpoints nobody documented.

Where to see it​

The API security use case shows the route-to-call view, and the OWASP API Top 10 reference maps every finding to its category. For broader context, see broken access control is still number one. To run it on your own API, start here.