DEV Community

Davi
Davi

Posted on Originally published at blog.mago.team

API Versioning: When /api/v1/ Survives Without the Authentication Added in /api/v2/

The API is secured. The documentation says v2 requires a Bearer token, the rate limiter is active, and the input sanitizer runs at the middleware layer. None of that matters if /api/v1/ is still responding.

API versioning creates a permission differential, not a migration. When v2 adds authentication, rate limiting, or input validation, v1 often remains accessible with none of those controls. Testers miss it because tools probe the documented version, not all versions.

The Version Increment Is Not a Migration: It Is a Permission Differential

Developers version APIs to avoid breaking existing clients, not to fork security posture. That business constraint forces v1 to remain live with its original, weaker security surface intact.

OWASP API9:2019 documents the canonical scenario: replacing /api/v2/ with /api/v1/ in the URL gave an attacker access to the old, unprotected API, exposing PII of more than 100 million users. CVE-2026-23760 (SmarterMail, CVSS 9.3) fixed the pattern in code: /api/v1/auth/force-reset-password decorated with AllowAnonymous = true while newer endpoints required credentials. The endpoint was being actively exploited in production at the time of disclosure.

Salt Security measured the scale in 2024: 40% of APIs in production do not match their documentation, and enterprises have on average 3 times more endpoints than security teams know about. The "don't break clients" invariant is inherited by security teams without translation into equivalent controls. Deprecated versions are rarely shut down; they receive lower priority in the backlog until someone exploits them.

Enumeration: All the Paths That Reach the Same Backend

The OpenAPI spec documents /api/v2/. Dynamic scanners consume that spec and test the listed endpoints. None of the undocumented paths pointing to the same backend are tested.

An API at /api/v2/ has at least 10 other reachable paths that automated scanners ignore. Path-based version variants include /api/v0/, /api/v1/, /api/v3/, /api/beta/, /api/internal/, /api/dev/, /api/admin/, /api/test/, and /api/staging/. None of those routes need to appear in documentation to respond to HTTP traffic.

Format variants for the same version reach the same controller: /API/V1/, /api/1/, /api/1.0/, and /api/v1.0/ differ by capitalization and dot notation. Most routing frameworks treat all of these as equivalent, routing to the same handler with different auth configuration. An attacker who knows /api/v2/ exists has an 80% chance that /api/v1/ also responds.

Header-based versioning is invisible to path-oriented scanners. The headers Accept: application/vnd.api+json;version=1 and API-Version: 1 specify version without modifying the URL, making the older version completely opaque to tools that analyze paths. Swagger/OpenAPI specs typically document only the current version; older versions have no spec entry but remain live on the server. DNS and subdomain variants extend the surface: api-v1.target.com and v1.api.target.com often point to the same code with different auth configuration.

Auth Differential: When v2 Adds the Middleware That v1 Skips

The middleware architecture of most web frameworks means v2 authentication is a new layer added on top of existing routes. v1 routes predate that middleware and are never retroactively covered.

The Express.js pattern makes the problem concrete:

// v1 registered first, no auth middleware
app.use('/api/v1/', v1Router);

// Middleware added later, protects v2 namespace only
app.use('/api/v2/', authMiddleware);
app.use('/api/v2/', v2Router);
Enter fullscreen mode Exit fullscreen mode

Registration order determines which middleware covers which route. Developers who added auth to v2 protected the v2 namespace; v1 continues receiving requests without passing through the middleware. CVE-2025-29927 (Next.js, CVSS 9.1) is the structural proof: the x-middleware-subrequest header causes Next.js to skip middleware execution entirely. Middleware-layer auth is a single point of failure, and versioning exposes that point directly.

The bug bounty record confirms the pattern in real production systems. H1 #1218461 (U.S. General Services Administration) exposed system accounts via an API endpoint that required no authentication. H1 #1627980 (U.S. Department of Defense) documented multiple API calls with unauthenticated access to internal APIs, where auth was enforced at the gateway but not at the route level.

H1 #1218680 (Elastic App Search) revealed that any App Search user could access all API keys via /api/as/v1/credentials/ regardless of assigned role. Authorization was applied only to the v2 path namespace; the v1 endpoint had no such coverage.

Field and Validation Differential: v1 Returns What v2 Hides

v1 serializers expose fields and accept inputs that v2 deliberately restricts. This half of the vulnerability is routinely ignored because authentication scans do not surface it.

Django REST Framework illustrates the serialization problem precisely: a v1 ModelSerializer auto-includes all model fields, including password_hash, is_admin, and internal_id. v2 introduces an explicit fields list to filter sensitive data, but v1 continues returning the full model. The mass assignment vector is often more dangerous than the read differential: v1 accepts is_admin=true in the request body before that input filter existed in v2.

CVE-2018-1778 (IBM LoopBack, CVSS 7.7) is the classic schema exposure: the AccessToken model exposed over the REST API allows anyone to create a token for any user given only a userID. CVE-2026-54367 (CentreStack, CVSS 8.6) documents cryptographic material in the schema: a static shared encryption key was embedded in the v1 API response, allowing account identifiers to be forged. SQL injection and command injection guards added to the v2 middleware do not cover v1 routes, which reach the raw query layer with the same user input.

CVE Evidence and a Four-Step Detection Methodology

The CVE record for this vulnerability class is systematic and growing. Detection requires four steps that most security tools do not run by default.

CVE-2026-23760 (SmarterMail, CVSS 9.3) is actively exploited: /api/v1/auth/force-reset-password accepts IsSysAdmin=true with no credential validation, resetting the admin password in a single unauthenticated POST. CVE-2025-13915 (IBM API Connect, CVSS 9.8) is the most telling case: the authentication bypass was in the API management gateway itself, the system designed to enforce API security. CVE-2026-29773 (Kubewarden) documents 3 deprecated host-callback APIs without adequate controls. Deprecated means not removed, just forgotten.

Step 1 is the version wordlist probe: test /api/v0/ through /api/v5/, plus /api/beta/, /api/internal/, /api/dev/, /api/admin/, and /api/test/. Step 2 is the differential auth probe: send authenticated and unauthenticated requests to each discovered version and compare the returned HTTP status codes. A 200 status on both requests confirms the differential.

Step 3 is the schema differential: compare JSON body fields across versions for the same endpoint and flag fields present in v1 but absent in v2. Step 4 is the header-based version probe: replay v2-authenticated requests with the API-Version: 1 and Accept: application/vnd.api+json;version=1 headers, checking whether the backend returns a different schema or skips auth.

The MAGO team tool (mago.team) automates all four steps: enumerates version variants, compares authenticated vs anonymous responses, extracts schema diffs, and tests header-based versioning.

Every new security control added to v2 is simultaneously an implicit disclosure: it tells an attacker exactly what v1 is missing. Treat version increments as attack surface deltas, not just feature boundaries.

Top comments (0)