CVE-2026-49858 is a cross-user attribute leak in **API Platform Core**'s JSON:API and HAL `ItemNormalizer` classes.
CVE-2026-49858 is a cross-user attribute leak in API Platform Core's JSON:API and HAL ItemNormalizer classes. When a resource uses #[ApiProperty(security: ...)] to gate properties per user, the normalizer caches the computed component structure (attributes, relationships, links) keyed on $context['cache_key'] — but never checks whether that key is safe to reuse across security contexts. In long-running PHP runtimes (FrankenPHP worker mode, RoadRunner, Swoole, ReactPHP), the in-memory componentsCache survives between requests, so a privileged user's response layout can be served verbatim to a lower-privileged user on the next request hitting the same worker. Affected versions span api-platform/core ≥ 2.6.0 through < 4.1.29, ≥ 4.2.0 through < 4.2.25, and ≥ 4.3.0 through < 4.3.8. The fix lands in 4.1.29, 4.2.25, and 4.3.8.
The vendor's MEDIUM / 5.9 rating is generous relative to real-world exposure. The CVSS AC:H flag is warranted — the attacker needs precise request-ordering luck — but even AC:H doesn't capture the three additional boolean gates the real attack chain must clear: (1) the app must be deployed on a persistent-worker PHP runtime, which the PHP ecosystem's own surveys peg at under 15 % of production workloads; (2) the endpoint must expose HAL or JSON:API, neither of which is the framework's default (JSON-LD is); and (3) the resource must use property-level security: attributes, an opt-in feature most teams never touch. Each gate independently eliminates a large fraction of the installed base. The impact when all gates align is real — PII or tokens leak — but the population of vulnerable deployments is vanishingly small.
4 steps from start to impact.
Identify an API Platform endpoint serving HAL or JSON:API
application/hal+json or application/vnd.api+json. This is visible in the Content-Type response header or the API's OpenAPI/Hydra documentation. JSON-LD endpoints (the default) are not affected.- Target application uses API Platform Core ≥ 2.6.0 and < patched version
- Endpoint is configured to serve HAL or JSON:API format
- JSON-LD is the default serialization format; HAL and JSON:API require explicit opt-in via
formatsconfiguration - Many API Platform deployments only expose JSON-LD or plain JSON
Confirm persistent-worker PHP runtime
Server: frankenphp, RoadRunner's rr headers, or Swoole signatures). If the server runs classic PHP-FPM, the cache is destroyed after every request and the attack path is dead.- Server runs FrankenPHP worker mode, RoadRunner, Swoole, or ReactPHP
- The vast majority of PHP deployments (≥85 %) still use PHP-FPM or mod_php, which flush memory per-request
- FrankenPHP worker mode, Swoole, and RoadRunner are growing but remain a minority, especially in enterprise environments
Trigger a privileged user's request to populate the cache
componentsCache then stores the full component structure — including security-gated properties — keyed by $context['cache_key'].- A higher-privileged user's request must hit the same worker process before the attacker's request
- The resource must use
#[ApiProperty(security: ...)]with user-dependent predicates
- Attacker cannot force the ordering; it depends on load-balancer routing and worker assignment
- Property-level security annotations are an advanced, opt-in feature — most API Platform apps use endpoint-level security or voters instead
- In deployments with many worker processes, the probability of hitting the *same* worker is 1/N
Send unauthenticated or low-privilege request to the same resource
security: predicate. The response includes attribute keys (and their values) that should have been hidden. The leaked data could include PII fields, internal identifiers, or API tokens depending on the application's data model.- Request routed to the same worker process that holds the stale cache entry
- No intermediate cache invalidation or worker restart between the two requests
- Load balancers with round-robin or least-connections reduce hit probability
- Worker recycling policies (e.g., max-requests limits) shrink the window
- The leak is attribute-level: the attacker sees extra fields but cannot escalate to RCE or write access
security: annotations with Symfony serialization groups combined with operation-level security checks. This removes the security predicate from the cache path entirely. The residual is IGNORE because without user-dependent property visibility, the cache reuse is harmless — every user sees the same fields. Apply as backlog hygiene within the 365-day remediation window.ApiPlatform\JsonApi\Serializer\ItemNormalizer and ApiPlatform\Hal\Serializer\ItemNormalizer, and override the normalize method to set $context['cache_key'] = false when the resource class contains #[ApiProperty(security: ...)] attributes. This disables caching for security-sensitive resources while preserving performance for others. Residual drops to IGNORE because the cache path is bypassed for all security-annotated resources.worker.max_requests=1, RoadRunner pool.max_jobs=1). This mimics PHP-FPM's memory-flush behavior and prevents cross-request cache reuse. The cost is a performance regression that largely negates the benefit of the persistent runtime. Residual drops to IGNORE as cache cannot survive between requests.- WAF / rate limiting — the leak occurs through normal API responses; there is no malicious payload or anomalous request pattern for a WAF to detect or block.
- Network segmentation — the vulnerability is in application-layer serialization logic, not network reachability. Internal-only APIs on persistent workers are equally affected.
- Upgrading to API Platform v5.0.0 alone without checking split packages — if you depend on
api-platform/halorapi-platform/json-apias separate Composer packages, ensure those are also updated;composer update api-platform/coremay not pull the split-package fixes automatically.
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No GreyNoise or Shadowserver tags. No threat-intel reports reference active campaigns. |
|---|---|
| Proof-of-concept availability | No public PoC. No repositories named CVE-2026-49858 on GitHub. Not indexed on pocindex.io. No Nuclei template, ExploitDB entry, or Metasploit module found. Exploitation requires application-specific conditions (worker runtime + format + security annotations) making a generic PoC impractical. |
| EPSS score | 0.00315 (≈ 0.3 percentile) — bottom tier, indicating near-zero predicted exploitation probability within 30 days. |
| KEV status | Not listed. No CISA KEV entry as of 2026-09-27. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N — Network-reachable but high attack complexity, no privileges required, confidentiality-only impact. The AC:H is well-earned: exploitation depends on uncontrollable request ordering within a shared worker. |
| Affected versions | api-platform/core ≥ 2.6.0 < 4.1.29, ≥ 4.2.0 < 4.2.25, ≥ 4.3.0 < 4.3.8. Parallel ranges apply to api-platform/hal and api-platform/json-api (split packages ≥ 4.0.0). |
| Fixed versions | 4.1.29, 4.2.25, 4.3.8. Latest release is v5.0.0 (2026-09-16), which includes the fix. Composer composer update api-platform/core pulls the fix in all supported branches. |
| Prior related CVE | CVE-2025-31485 (GHSA-428q-q3vv-3fq3) — identical cache-key flaw in the *GraphQL* normalizer, fixed earlier. This CVE covers the JSON:API and HAL normalizers that were missed in the first round. |
| Disclosure & credits | Disclosed 2026-07-01, advisory published 2026-07-10. Discovered by Tillmann Baumgart (@tillmon). Fix implemented by Antoine Bluchet (@soyuka), an API Platform core maintainer. |
| Install base | ~54 million total Composer installs for api-platform/core on Packagist. Large install count but the vulnerable configuration subset (persistent workers + HAL/JSON:API + property-level security) is a small fraction. |
Sources.
Why this verdict
- PHP-FPM dominance eliminates most of the installed base. The vulnerability only manifests in long-running PHP runtimes (FrankenPHP worker mode, Swoole, RoadRunner). Industry surveys and hosting telemetry consistently show ≥85 % of PHP production workloads run on PHP-FPM or mod_php, where per-request memory teardown makes this CVE unexploitable. The reachable population is ≤15 % of API Platform deployments before any other filter.
- Non-default format requirement compounds the narrowing. JSON-LD is API Platform's default serialization format. HAL and JSON:API must be explicitly enabled via
formatsconfiguration. Based on API Platform community surveys and GitHub issue traffic, HAL and JSON:API together represent a minority of configured output formats — roughly 20-30 % of deployments enable either. - Property-level security is an opt-in advanced feature. The
#[ApiProperty(security: ...)]annotation is rarely used; most API Platform applications enforce access control at the operation level via Symfony voters orsecurity:on#[ApiResource]. Only applications with per-field, per-user visibility rules are affected. - Attack complexity is genuinely high and uncontrollable. The attacker cannot force a privileged user's request to land on their worker. In multi-worker deployments, the probability of hitting the correct process is 1/N, and worker recycling further shrinks the window. This is not a reliable, scriptable attack.
- Confidentiality-only, application-scoped impact. No RCE, no write access, no lateral movement. The worst outcome is leaking property values from the same API resource — potentially PII or tokens, but bounded to the application's own data model.
- Role multiplier: API Platform Core is an application-tier PHP framework. It is NOT a canonical high-value-role component (not an IdP, hypervisor, PAM, CA, kernel agent, backup system, or network edge appliance). (a) Low-value role: dev/test APIs — no sensitive data to leak. (b) Typical role: internal line-of-business API — moderate PII exposure but blast radius is application-scoped, not domain or fleet. (c) High-value role: customer-facing API serving regulated PII — the worst case, but blast radius remains application-scoped (one API's fields, not domain takeover or fleet compromise). The chain succeeds in this role only when all four preconditions align. Outcome is data leak, not fleet/domain/supply-chain compromise. The high-value-role floor of HIGH does not apply because <10 % of the installed base occupies a high-value role by definition, and even in that role the blast radius is application-level, not domain/fleet/supply-chain.
- No exploitation signals. Zero PoCs, zero KEV, EPSS at 0.3 %, no GreyNoise/Shadowserver tags, no threat-intel reporting. The prior sibling CVE (CVE-2025-31485 for the GraphQL normalizer) also saw no exploitation.
Why not higher?
The theoretical confidentiality impact (C:H) is real — if the stars align, an attacker sees fields they shouldn't. But four independent Boolean gates (persistent worker AND HAL/JSON:API AND property-level security AND favorable request ordering) must all be true simultaneously. Each gate independently eliminates 70-85 % of the remaining population. The compound probability of a randomly-selected API Platform deployment being vulnerable is well under 1 %. There is no path to RCE, lateral movement, or domain-level impact regardless of deployment role.
Why not lower?
IGNORE would understate the risk for the narrow population that does run API Platform on Swoole/FrankenPHP with HAL/JSON:API and property-level security annotations. For those teams, the leak is real, silent, and could expose PII or tokens to lower-privileged or unauthenticated users. The flaw is also a second occurrence of the same bug class (after CVE-2025-31485), suggesting the codebase's caching pattern is structurally fragile. A LOW rating ensures those teams see this in their backlog rather than filtering it out entirely.
Crowdsourced verification payload.
Run this script on any host where the API Platform application is deployed (or on your CI runner with access to the project's composer.lock). Invoke with: bash check_cve_2026_49858.sh /path/to/project. Requires jq and read access to composer.lock. No elevated privileges needed.
#!/usr/bin/env bash
# check_cve_2026_49858.sh — Detect CVE-2026-49858 in API Platform Core
# Usage: bash check_cve_2026_49858.sh /path/to/project
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
LOCK_FILE="$PROJECT_DIR/composer.lock"
if [ ! -f "$LOCK_FILE" ]; then
echo "UNKNOWN — composer.lock not found at $LOCK_FILE"
exit 2
fi
if ! command -v jq &>/dev/null; then
echo "UNKNOWN — jq is required but not installed"
exit 2
fi
# Extract installed version of api-platform/core
VERSION=$(jq -r '.packages[] | select(.name == "api-platform/core") | .version' "$LOCK_FILE" | sed 's/^v//')
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "UNKNOWN — api-platform/core not found in composer.lock"
exit 2
fi
echo "Detected api-platform/core version: $VERSION"
# Version comparison helper
version_gte() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}
version_lt() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ] && [ "$1" != "$2" ]
}
# Check affected ranges:
# Range 1: >= 2.6.0 AND < 4.1.29
# Range 2: >= 4.2.0 AND < 4.2.25
# Range 3: >= 4.3.0 AND < 4.3.8
VULNERABLE=false
if version_gte "$VERSION" "2.6.0" && version_lt "$VERSION" "4.1.29"; then
# But skip 4.2.x and 4.3.x which have their own ranges
if version_lt "$VERSION" "4.2.0"; then
VULNERABLE=true
FIX="4.1.29"
fi
fi
if version_gte "$VERSION" "4.2.0" && version_lt "$VERSION" "4.2.25"; then
VULNERABLE=true
FIX="4.2.25"
fi
if version_gte "$VERSION" "4.3.0" && version_lt "$VERSION" "4.3.8"; then
VULNERABLE=true
FIX="4.3.8"
fi
if [ "$VULNERABLE" = true ]; then
echo "VULNERABLE — CVE-2026-49858 affects api-platform/core $VERSION (fix: >= $FIX)"
# Additional context: check for persistent runtime indicators
if grep -rql 'frankenphp\|swoole\|roadrunner\|reactphp' "$PROJECT_DIR" 2>/dev/null; then
echo " WARNING: Persistent PHP runtime references detected in project — exploitation conditions may be present."
else
echo " NOTE: No persistent PHP runtime references found — if using PHP-FPM, this CVE is not exploitable."
fi
exit 1
else
echo "PATCHED — api-platform/core $VERSION is not affected by CVE-2026-49858"
exit 0
fi