Secured properties in API Platform Core may be accessible within collections
Description
API Platform Core improperly caches security rule results on collection endpoints, causing resource properties to be visible to unauthorized users.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
API Platform Core improperly caches security rule results on collection endpoints, causing resource properties to be visible to unauthorized users.
Vulnerability
Overview
CVE-2023-25575 affects API Platform Core, the server component for building hypermedia and GraphQL APIs. The bug resides in the serialization process for collection endpoints. Resource properties protected by the security option of the ApiPlatform\Metadata\ApiProperty attribute can be unintentionally disclosed to unauthorized users [1]. The issue stems from how the security rule result is cached: it is executed only for the first item in a collection, and that result is then reused for all subsequent items [1]. This caching flaw leads to incorrect authorization decisions when the security rule depends on a property value of each item.
Exploitation
An attacker can exploit this vulnerability by sending a GET request to a collection endpoint (e.g., /secured_dummies) that serves resources with property-level security rules [2]. No special authentication is required beyond normal access to the endpoint; the attacker only needs to be an unprivileged user who can retrieve items that should have some properties hidden. The bug affects most serialization formats, including the default raw JSON, but not JSON-LD [1]. The caching flaw does not affect item endpoints, only collections.
Impact
The security bypass can both leak data that should remain hidden from unauthorized users and hide properties that should be visible to authorized users [1]. For example, a property such as ownerOnlyProperty intended only for an admin could be exposed to any user with collection access. Conversely, legitimate administrators might not see properties they are entitled to view. This undermines the integrity of API authorization, potentially exposing sensitive user data or business logic.
Mitigation
Patched versions are available in API Platform Core 2.7.10, 3.0.12, and 3.1.3 [1][3][4]. Upgrading to these releases is the recommended fix. As a temporary workaround, developers can replace the cache_key of the Serializer context array inside a custom normalizer that works on objects, if the security option is used [1].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
api-platform/corePackagist | >= 3.0.0, < 3.0.12 | 3.0.12 |
api-platform/corePackagist | >= 3.1.0, < 3.1.3 | 3.1.3 |
api-platform/corePackagist | >= 2.6.0, < 2.7.10 | 2.7.10 |
Affected products
2- Range: >= 3.0.0, < 3.0.12
Patches
15723d6836972Merge pull request from GHSA-vr2x-7687-h6qv
5 files changed · +49 −11
features/authorization/deny.feature+22 −0 modified@@ -181,3 +181,25 @@ Feature: Authorization checking Then the response status code should be 200 And the response should contain "ownerOnlyProperty" And the JSON node "ownerOnlyProperty" should be equal to the string "updated" + + Scenario: A user retrieves a resource with an admin only viewable property + When I add "Accept" header equal to "application/json" + And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" + And I send a "GET" request to "/secured_dummies" + Then the response status code should be 200 + And the response should contain "ownerOnlyProperty" + + Scenario: A user retrieves a resource with an admin only viewable property + When I add "Accept" header equal to "application/hal+json" + And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" + And I send a "GET" request to "/secured_dummies" + Then the response status code should be 200 + And the response should contain "ownerOnlyProperty" + + Scenario: A user retrieves a resource with an admin only viewable property + Given I add "Accept" header equal to "application/vnd.api+json" + And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" + And I send a "GET" request to "/secured_dummies" + Then the response status code should be 200 + And the response should contain "ownerOnlyProperty" +
src/Hal/Serializer/ItemNormalizer.php+4 −4 modified@@ -62,10 +62,6 @@ public function normalize($object, $format = null, array $context = []) return parent::normalize($object, $format, $context); } - if (!isset($context['cache_key'])) { - $context['cache_key'] = $this->getCacheKey($format, $context); - } - if ($this->resourceClassResolver->isResourceClass($resourceClass)) { $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); } @@ -75,6 +71,10 @@ public function normalize($object, $format = null, array $context = []) $context['iri'] = $iri; $context['api_normalize'] = true; + if (!isset($context['cache_key'])) { + $context['cache_key'] = $this->getCacheKey($format, $context); + } + $data = parent::normalize($object, $format, $context); if (!\is_array($data)) { return $data;
src/JsonApi/Serializer/ItemNormalizer.php+5 −5 modified@@ -78,11 +78,7 @@ public function normalize($object, $format = null, array $context = []) return parent::normalize($object, $format, $context); } - if (!isset($context['cache_key'])) { - $context['cache_key'] = $this->getCacheKey($format, $context); - } - - if ($isResourceClass = $this->resourceClassResolver->isResourceClass($resourceClass)) { + if ($this->resourceClassResolver->isResourceClass($resourceClass)) { $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); } @@ -91,6 +87,10 @@ public function normalize($object, $format = null, array $context = []) $context['iri'] = $iri; $context['api_normalize'] = true; + if (!isset($context['cache_key'])) { + $context['cache_key'] = $this->getCacheKey($format, $context); + } + $data = parent::normalize($object, $format, $context); if (!\is_array($data)) { return $data;
src/Serializer/AbstractItemNormalizer.php+6 −0 modified@@ -27,6 +27,7 @@ use ApiPlatform\Exception\InvalidValueException; use ApiPlatform\Exception\ItemNotFoundException; use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -207,6 +208,11 @@ public function normalize($object, $format = null, array $context = []) $context = $this->initContext($resourceClass, $context); } + if (isset($context['operation']) && $context['operation'] instanceof CollectionOperationInterface) { + unset($context['operation']); + unset($context['iri']); + } + $iri = null; if (isset($context['iri'])) { $iri = $context['iri'];
src/Serializer/CacheKeyTrait.php+12 −2 modified@@ -13,6 +13,12 @@ namespace ApiPlatform\Serializer; +/** + * Used to override Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::getCacheKey which is private + * We need the cache_key in JsonApi and Hal before it is computed in Symfony. + * + * @see https://github.com/symfony/symfony/blob/49b6ab853d81e941736a1af67845efa3401e7278/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php#L723 which isn't protected + */ trait CacheKeyTrait { /** @@ -24,11 +30,15 @@ private function getCacheKey(?string $format, array $context) unset($context[$key]); } unset($context[self::EXCLUDE_FROM_CACHE_KEY]); + unset($context[self::OBJECT_TO_POPULATE]); unset($context['cache_key']); // avoid artificially different keys try { - return md5($format.serialize($context)); - } catch (\Exception $exception) { + return hash('xxh128', $format.serialize([ + 'context' => $context, + 'ignored' => $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES], + ])); + } catch (\Exception) { // The context cannot be serialized, skip the cache return false; }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-vr2x-7687-h6qvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-25575ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/api-platform/core/CVE-2023-25575.yamlghsaWEB
- github.com/api-platform/core/commit/5723d68369722feefeb11e42528d9580db5dd0fbghsax_refsource_MISCWEB
- github.com/api-platform/core/releases/tag/v2.7.10ghsaWEB
- github.com/api-platform/core/releases/tag/v3.0.12ghsaWEB
- github.com/api-platform/core/releases/tag/v3.1.3ghsaWEB
- github.com/api-platform/core/security/advisories/GHSA-vr2x-7687-h6qvghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.