OWASP published its second edition of the API Security Top 10 in 2023, and the list looks meaningfully different from the 2019 original: Injection, a mainstay of the classic OWASP Top 10 since the project began, no longer appears as its own numbered category, while Server Side Request Forgery and Unsafe Consumption of APIs are new entries entirely. That's not OWASP declaring injection solved — it's an acknowledgment that API-specific risk has shifted toward authorization logic and architecture, not just unsanitized input. The 2023 edition, released under a Creative Commons Attribution-ShareAlike 4.0 license by the OWASP API Security Project team, ranks Broken Object Level Authorization (API1:2023) as the single most common API flaw for the second edition running, and it's easy to see why: a REST endpoint like GET /api/orders/{id} that checks a JWT is valid but never checks whether the JWT's owner is allowed to see that order ID is a one-line authorization bug, not a code-injection flaw, and no amount of input sanitization catches it. GraphQL APIs inherit every one of these ten risks and add a few of their own, since a single POST /graphql endpoint can expose the entire schema through introspection and let one query touch dozens of resolvers at once. This post walks through the categories that matter most and how to actually test for them.
Why is Broken Object Level Authorization still the top API risk?
API1:2023 – Broken Object Level Authorization (BOLA) tops the OWASP list because it's the easiest mistake to make and the hardest to catch with a generic scanner. The pattern is always the same: an endpoint accepts an object identifier — an order ID, an invoice number, a user UUID — in the URL path or request body, authenticates the caller, but never verifies that the caller owns the specific object being requested. A REST example is GET /api/invoices/4471 returning invoice 4471 to any logged-in user, not just the account that created it; the GraphQL equivalent is a resolver like invoice(id: ID!) with the same missing ownership check buried one layer deeper, behind a schema instead of a URL. Testing for BOLA means building an authorization matrix — every endpoint or resolver crossed with every role and every "object I don't own" — and systematically swapping IDs between two authenticated test accounts to see which requests that should be denied instead succeed. Automated BOLA fuzzers exist, but object-ownership logic is business-specific enough that manual matrix testing during design review still catches cases pure fuzzing misses.
How does Broken Object Property Level Authorization differ from the old Mass Assignment and Excessive Data Exposure risks?
API3:2023 – Broken Object Property Level Authorization merges two separate 2019-era categories, Excessive Data Exposure and Mass Assignment, into one property-level lens, because both were really the same underlying failure viewed from opposite directions. Excessive Data Exposure is a read-side leak: an endpoint returns a full user object — including a salary or isAdmin field — and relies on the client to hide fields it shouldn't display. Mass Assignment is the write-side mirror: an endpoint blindly binds a request body onto an internal object, so a client that adds an unexpected role: "admin" field to a profile-update request gets it accepted because the deserializer never restricted which properties were writable. GraphQL makes both directions worse by default, since a single query can request every field a type exposes and a single mutation can accept every field a schema allows unless the resolver explicitly allow-lists them. Testing means diffing the full response schema against what the client actually needs, and separately attempting to set fields — role, price, ownerId — that a legitimate user should never be able to write.
What changed with Unrestricted Resource Consumption, and why does GraphQL make it worse?
API4:2023 – Unrestricted Resource Consumption replaced the narrower 2019 "Lack of Resources & Rate Limiting" category with a broader view covering CPU, memory, storage, and third-party API spend, not just request-per-second throttling. A REST API without pagination limits can be told to return 500,000 rows in one call; a GraphQL API has a sharper version of the same problem because a single query can nest nine levels deep or request an aliased field a thousand times in one HTTP call, multiplying cost far beyond what a naive per-request rate limit would ever flag. Testing REST for this means checking for hard caps on page size, upload size, and batch-operation counts. Testing GraphQL requires query-cost analysis: assigning a numeric cost to every field and rejecting queries above a threshold, plus enforcing maximum query depth and disabling batching on endpoints that don't need it. Without cost-based limits, a functionally "valid" GraphQL query — one that violates no authorization rule at all — can still take a database down.
Why is Improper Inventory Management its own category now?
API9:2023 – Improper Inventory Management earned its own slot in the 2023 list because the biggest source of API breaches in practice isn't a flaw in a documented, current endpoint — it's a forgotten one. Deprecated /v1 endpoints left reachable after a /v2 migration, staging environments with production data still resolving on the public internet, and internal-only APIs exposed with the same authentication as public ones all fall under this category, and none of them show up in a scan that only tests the OpenAPI spec someone remembers to keep updated. GraphQL compounds the discovery problem in the opposite direction: introspection, which lists every type, field, and resolver in the schema, is often left enabled in production so tooling can auto-generate documentation, effectively handing an attacker a complete map of the API's internals. Testing for this category starts with inventory, not vulnerability scanning: enumerating every live host and API version against what's actually documented, and confirming introspection is disabled (or authenticated-only) on any GraphQL endpoint outside a development environment.
How do you test for Server Side Request Forgery in an API that consumes webhooks or third-party data?
API7:2023 – Server Side Request Forgery is new to the API-specific list, reflecting how often modern APIs fetch a URL on the caller's behalf — an avatar image URL, a webhook callback, a PDF-generation source — without validating where that URL actually points. If an attacker can supply a URL like http://169.254.169.254/latest/meta-data/ (a cloud instance metadata endpoint) or a URL pointing at an internal-only service, and the API server dutifully fetches it, that's SSRF turning the API into a proxy into the internal network. Testing means identifying every parameter that triggers a server-side fetch — not just an obvious url field, but also file-import features, image-processing pipelines, and webhook-registration endpoints — and confirming the server enforces an allow-list of destination hosts and blocks requests to private IP ranges and cloud metadata addresses, rather than only blocking a denylist of "known bad" hosts that's trivial to bypass with a redirect.
How does Safeguard fit into API security testing?
Testing an API against ten OWASP categories manually, across every endpoint and every GraphQL resolver, doesn't scale past a handful of services. Safeguard's application security testing runs SAST and DAST as a connected pipeline rather than disconnected tools: SAST traces untrusted input from a source — a request parameter, a GraphQL argument — to a dangerous sink across your codebase, producing a dataflow trace with a CWE and OWASP mapping instead of a bare line number, which is exactly the kind of tracing that turns "this endpoint touches user input" into "this specific resolver is missing an ownership check." DAST complements that by sending safe, non-destructive requests against verified, in-scope targets only — no active check runs until a target proves ownership via DNS TXT record, file upload, or a similar method — so an API can be exercised for real, observable behavior (missing authorization on an object-ID swap, an unrestricted response size, a fetch that reaches an internal host) without risking production data. Findings from both engines land in one unified, tenant-scoped model, so a DAST-confirmed authorization gap and the SAST-identified code path that causes it get correlated and prioritized together instead of triaged as two separate tickets.