Back to Blog
Ahmed Allam

The API Security Gaps That Static Analysis Misses

The API Security Gaps That Static Analysis Misses

Static analysis tools are genuinely useful. They catch injection sinks where user input flows to dangerous operations, identify hardcoded credentials, flag weak cryptography implementations, and surface a class of code patterns that are reliably wrong regardless of context. Running a SAST tool in CI is a sound practice and one we recommend alongside dynamic testing.

But there is a category of API vulnerability that SAST cannot find by definition: authorization failures that only manifest when you test how a specific token behaves against a specific resource at runtime. Broken Object Level Authorization (BOLA) is the canonical example, and it remains the top category in the OWASP API Security Top 10 precisely because it is invisible to static analysis and widely present in production APIs.

Why BOLA Is Fundamentally a Dynamic Problem

Broken Object Level Authorization describes a failure where an API endpoint returns or modifies a resource belonging to one user when called with the credentials of a different user. The classic pattern: a GET request to /api/v1/invoices/12345 returns the invoice with ID 12345 regardless of whether the authenticated user owns that invoice. The endpoint validates that the caller is authenticated, but not that the caller is authorized to access the specific object they requested.

Static analysis sees the code that handles this request. It sees the authentication check. It does not, and cannot, determine whether the code that fetches the invoice verifies that the authenticated user's ID matches the owner of invoice 12345. That verification might be in an ORM layer, a database query, a separate authorization service, or not present at all. Determining whether the check exists and whether it is applied consistently requires looking at the full code path from the endpoint to the data retrieval, which SAST tools do in some cases, but even when they can trace the full path, they cannot determine whether the ownership verification is semantically correct without understanding your data model.

More practically: many BOLA vulnerabilities arise from inconsistent application of authorization logic rather than its complete absence. Your main invoice endpoint might correctly verify ownership. A batch export endpoint added for a reporting feature six months later might not, because it was written by a different developer who used a slightly different query pattern. The inconsistency is invisible to static analysis because both patterns look like valid code.

Testing BOLA at Scale Across a Large API

Testing BOLA systematically requires: an authenticated test user A, the resources belonging to user A, an authenticated test user B, and a set of requests where user B attempts to access user A's resources through every endpoint that accepts resource identifiers. If any of those requests succeeds, that endpoint has a BOLA vulnerability.

For an API with hundreds of endpoints, this is non-trivial. Object identifiers appear in URL path parameters, query parameters, and request bodies. The identifier format varies (integer IDs, UUIDs, slugs). Some endpoints accept multiple resource identifiers in a single request. Some have hierarchical resources where access to a parent implicitly grants access to children, and the business rule about whether that is correct is application-specific.

Automated dynamic testing generates this test matrix from the API schema (OpenAPI, GraphQL introspection, or discovered endpoints), creates the required test accounts and resources, and executes the cross-user access tests systematically. A human tester can do this, but manual testing at scale across hundreds of endpoints in multiple resource dimensions is slow and error-prone. The endpoints that get skipped during manual testing are exactly the ones that end up having BOLA vulnerabilities.

Function-Level Authorization Failures

Closely related to BOLA is Broken Function Level Authorization (BFLA), which OWASP lists separately. Where BOLA is about horizontal access (accessing another user's objects at the same privilege level), BFLA is about vertical access (a regular user accessing admin-level operations).

BFLA is also mostly invisible to static analysis because it depends on your application's role model and how consistently that model is enforced across endpoints. An admin-only endpoint that returns 403 when called without authentication might return 200 when called by a user with the standard role if the authorization check verifies authentication but not role. The code looks correct to a SAST tool. It only fails when tested with the right combination of token type and request.

A pattern we see repeatedly: authorization checks applied correctly in v1 API endpoints, then inconsistently applied in v2 endpoints written later by a different team. The v2 endpoints copy the v1 structure but miss the authorization middleware. Static analysis cannot detect this because each endpoint individually looks fine. The failure is in the comparison between what the v1 endpoints do and what the v2 endpoints do, which requires runtime behavioral analysis across both versions.

Mass Assignment Vulnerabilities

Mass assignment is another API vulnerability class that SAST catches inconsistently and dynamic testing catches reliably. The pattern: an endpoint that accepts a JSON body and maps it directly to a model object accepts fields that should not be user-settable, such as internal flags, account balance fields, or admin privilege indicators. The endpoint was designed to accept a name and email update. It also accepts a is_admin: true field that was never intended to be user-settable.

Some SAST tools flag direct model binding patterns in specific frameworks (Rails strong_parameters misconfigurations, for example). But the rule is framework-specific and pattern-specific. Mass assignment that happens through a custom deserialization path, or in a framework the SAST tool's rules do not cover, will not be flagged. Dynamic testing that sends unexpectedly complete JSON bodies with extra fields and checks whether those fields are accepted and applied catches this reliably regardless of the underlying framework.

Rate Limiting, Resource Enumeration, and Business Logic at the API Layer

Beyond authorization vulnerabilities, several other API security categories are only testable dynamically. Rate limiting failures only manifest under actual request volume. Resource enumeration vulnerabilities (where predictable identifiers allow automated discovery of other users' resources) require actually sending the enumeration requests and observing whether the responses distinguish between "resource does not exist" and "resource exists but you are not authorized." A SAST tool reading the code can determine whether an error handling path exists, but not whether the error responses are distinguishable in practice.

GraphQL APIs introduce additional complexity. A GraphQL endpoint exposes a schema, and introspection allows enumerating the full type system. The security test is not just whether individual resolvers are authorized, but whether authorization is enforced consistently across nested queries, mutations that accept object references, and batch operations. A query that fetches a resource the user owns might also traverse relationships to resources they do not own. Testing the full graph of authorization behavior requires dynamic execution against the live schema.

How Static and Dynamic Testing Work Together

We are not arguing that static analysis is not worth running. For the vulnerability classes it reliably catches (injection, hardcoded secrets, certain authentication bypasses, weak cryptography implementations), it is fast, integrates cleanly into the code pipeline, and provides findings that are easy to reproduce and fix. Running SAST in CI on every push is a good practice and should remain so.

The argument is that treating SAST coverage as equivalent to API security coverage is a category error. They catch different things. A team that runs SAST and considers their API security posture addressed has checked the code patterns. They have not checked the runtime authorization behavior. Those are different tests answering different questions.

API security testing that covers BOLA, BFLA, mass assignment, rate limiting, and schema-level behavior requires dynamic testing with real tokens against real endpoints. That test needs to run on every code change that modifies API endpoints, not quarterly and not only during pre-release security reviews. Authorization logic is modified frequently in active development, and the regressions introduced by those modifications are precisely what continuous dynamic testing is designed to catch.

Strix runs continuous penetration testing so your findings do not go stale.

Get Early Access