← Back to Feed CACHED · 2026-09-27 03:28:33 · CACHE_KEY CVE-2026-49858
CVE-2026-49858 · CWE-524 · Disclosed 2026-07-01

API Platform Core cross-user attribute leak via unsafe cache reuse in JSON:API and HAL item normalizers

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

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.

"Cache leak needs 4 aligned stars; most PHP shops run FPM and are immune."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify an API Platform endpoint serving HAL or JSON:API

The attacker locates an API Platform-powered endpoint that returns responses in 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.
Conditions required:
  • Target application uses API Platform Core ≥ 2.6.0 and < patched version
  • Endpoint is configured to serve HAL or JSON:API format
Where this breaks in practice:
  • JSON-LD is the default serialization format; HAL and JSON:API require explicit opt-in via formats configuration
  • Many API Platform deployments only expose JSON-LD or plain JSON
Detection/coverage: Content-Type header inspection; Swagger/OpenAPI docs enumeration
STEP 02

Confirm persistent-worker PHP runtime

The attacker probes whether the server uses a long-running PHP runtime by observing response-header fingerprints (e.g., 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.
Conditions required:
  • Server runs FrankenPHP worker mode, RoadRunner, Swoole, or ReactPHP
Where this breaks in practice:
  • 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
Detection/coverage: Server header fingerprinting; timing-based inference (persistent workers respond faster on warm paths)
STEP 03

Trigger a privileged user's request to populate the cache

The attacker cannot directly control this step. A legitimate user with higher privileges must make a request to the same resource on the same worker process *before* the attacker's request. The worker's componentsCache then stores the full component structure — including security-gated properties — keyed by $context['cache_key'].
Conditions required:
  • 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
Where this breaks in practice:
  • 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
STEP 04

Send unauthenticated or low-privilege request to the same resource

The attacker requests the same resource via the same HAL or JSON:API endpoint. If their request lands on the worker that served the privileged request, the cached component structure is reused without re-evaluating the 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.
Conditions required:
  • Request routed to the same worker process that holds the stale cache entry
  • No intermediate cache invalidation or worker restart between the two requests
Where this breaks in practice:
  • 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
Detection/coverage: Application-level audit logging comparing response schemas per user role; anomaly detection on response payload size differences for the same endpoint across user roles
03 · Compensating Control

1
LOW 3.5→IGNORE 0.0
SEVERITY REDUCED
Switch affected endpoints to PHP-FPM — If you are running API Platform on FrankenPHP worker mode, Swoole, or RoadRunner, switching the affected application (or just the routes using HAL/JSON:API with property-level security) to classic PHP-FPM completely eliminates the vulnerability because PHP-FPM destroys in-process memory after every request. This is the most reliable compensating control. As a LOW-severity finding, there is no noisgate mitigation SLA — treat this as backlog hygiene and patch within the noisgate remediation SLA of 365 days.
2
LOW 3.5→IGNORE 0.0
SEVERITY REDUCED
Remove #[ApiProperty(security: ...)] from affected resources — Replace property-level 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.
3
LOW 3.5→IGNORE 0.0
SEVERITY REDUCED
Override ItemNormalizer to gate cache_key — Per the vendor advisory's workaround: subclass 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.
4
LOW 3.5→IGNORE 0.0
SEVERITY REDUCED
Set worker max-requests to 1 — Configure your persistent PHP runtime to recycle workers after every request (e.g., FrankenPHP 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.
What doesn't work
  • 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/hal or api-platform/json-api as separate Composer packages, ensure those are also updated; composer update api-platform/core may not pull the split-package fixes automatically.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not listed in CISA KEV. No GreyNoise or Shadowserver tags. No threat-intel reports reference active campaigns.
Proof-of-concept availabilityNo 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 score0.00315 (≈ 0.3 percentile) — bottom tier, indicating near-zero predicted exploitation probability within 30 days.
KEV statusNot listed. No CISA KEV entry as of 2026-09-27.
CVSS vectorCVSS: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 versionsapi-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 versions4.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 CVECVE-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 & creditsDisclosed 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.

  1. GitHub Security Advisory GHSA-pjhx-3c3w-9v23
  2. OSV — CVE-2026-49858
  3. GitLab Advisory Database — api-platform/json-api
  4. NVD — CVE-2026-49858
  5. OpenCVE — CVE-2026-49858
  6. Packagist — api-platform/core
  7. CWE-524: Use of Cache Containing Sensitive Information
05 · The Call

Final Verdict
↓ DOWNGRADED to LOW (3.5/10)

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 formats configuration. 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 or security: 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.

06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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