VYPR
Medium severity5.3NVD Advisory· Published Apr 3, 2025· Updated Apr 15, 2026

CVE-2023-47639

CVE-2023-47639

Description

API Platform Core is a system to create hypermedia-driven REST and GraphQL APIs. From 3.2.0 until 3.2.4, exception messages, that are not HTTP exceptions, are visible in the JSON error response. This vulnerability is fixed in 3.2.5.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
api-platform/corePackagist
>= 3.2.0, < 3.2.53.2.5

Patches

2
ba8a7e6538bc

fix: exception message leak

https://github.com/api-platform/coresoyukaNov 14, 2023via ghsa
9 files changed · +104 27
  • src/ApiResource/Error.php+28 9 modified
    @@ -58,13 +58,14 @@
     class Error extends \Exception implements ProblemExceptionInterface, HttpExceptionInterface
     {
         public function __construct(
    -        private readonly string $title,
    -        private readonly string $detail,
    +        private string $title,
    +        private string $detail,
             #[ApiProperty(identifier: true)] private int $status,
             array $originalTrace = null,
             private ?string $instance = null,
             private string $type = 'about:blank',
    -        private array $headers = []
    +        private array $headers = [],
    +        private ?\Exception $previous = null
         ) {
             parent::__construct();
     
    @@ -85,21 +86,21 @@ public function __construct(
     
         #[SerializedName('hydra:title')]
         #[Groups(['jsonld', 'legacy_jsonld'])]
    -    public function getHydraTitle(): string
    +    public function getHydraTitle(): ?string
         {
             return $this->title;
         }
     
         #[SerializedName('hydra:description')]
         #[Groups(['jsonld', 'legacy_jsonld'])]
    -    public function getHydraDescription(): string
    +    public function getHydraDescription(): ?string
         {
             return $this->detail;
         }
     
         #[SerializedName('description')]
         #[Groups(['jsonapi', 'legacy_jsonapi'])]
    -    public function getDescription(): string
    +    public function getDescription(): ?string
         {
             return $this->detail;
         }
    @@ -108,7 +109,7 @@ public static function createFromException(\Exception|\Throwable $exception, int
         {
             $headers = ($exception instanceof SymfonyHttpExceptionInterface || $exception instanceof HttpExceptionInterface) ? $exception->getHeaders() : [];
     
    -        return new self('An error occurred', $exception->getMessage(), $status, $exception->getTrace(), type: '/errors/'.$status, headers: $headers);
    +        return new self('An error occurred', $exception->getMessage(), $status, $exception->getTrace(), type: '/errors/'.$status, headers: $headers, previous: $exception->getPrevious());
         }
     
         #[Ignore]
    @@ -123,6 +124,9 @@ public function getStatusCode(): int
             return $this->status;
         }
     
    +    /**
    +     * @param array<string, string> $headers
    +     */
         public function setHeaders(array $headers): void
         {
             $this->headers = $headers;
    @@ -134,15 +138,20 @@ public function getType(): string
             return $this->type;
         }
     
    +    public function setType(string $type): void
    +    {
    +        $this->type = $type;
    +    }
    +
         #[Groups(['jsonld', 'legacy_jsonproblem', 'jsonproblem', 'jsonapi', 'legacy_jsonapi'])]
         public function getTitle(): ?string
         {
             return $this->title;
         }
     
    -    public function setType(string $type): void
    +    public function setTitle(string $title = null): void
         {
    -        $this->type = $type;
    +        $this->title = $title;
         }
     
         #[Groups(['jsonld', 'jsonproblem', 'legacy_jsonproblem'])]
    @@ -162,9 +171,19 @@ public function getDetail(): ?string
             return $this->detail;
         }
     
    +    public function setDetail(string $detail = null): void
    +    {
    +        $this->detail = $detail;
    +    }
    +
         #[Groups(['jsonld', 'jsonproblem', 'legacy_jsonproblem'])]
         public function getInstance(): ?string
         {
             return $this->instance;
         }
    +
    +    public function setInstance(string $instance = null): void
    +    {
    +        $this->instance = $instance;
    +    }
     }
    
  • src/Hydra/Serializer/ErrorNormalizer.php+1 3 modified
    @@ -24,7 +24,7 @@
     /**
      * Converts {@see \Exception} or {@see FlattenException} to a Hydra error representation.
      *
    - * @deprecated we use ItemNormalizer instead
    + * @deprecated we will use the ItemNormalizer in 4.x instead
      *
      * @author Kévin Dunglas <dunglas@gmail.com>
      * @author Samuel ROZE <samuel.roze@gmail.com>
    @@ -47,8 +47,6 @@ public function __construct(private readonly UrlGeneratorInterface|LegacyUrlGene
          */
         public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
         {
    -        trigger_deprecation('api-platform', '3.2', sprintf('The class "%s" is deprecated in favor of using an Error resource. We fallback on "api_platform.serializer.normalizer.item".', __CLASS__));
    -
             if ($this->itemNormalizer) {
                 return $this->itemNormalizer->normalize($object, $format, $context);
             }
    
  • src/JsonApi/Serializer/ErrorNormalizer.php+1 3 modified
    @@ -22,7 +22,7 @@
     /**
      * Converts {@see \Exception} or {@see FlattenException} or to a JSON API error representation.
      *
    - * @deprecated we use ItemNormalizer instead
    + * @deprecated we will use the ItemNormalizer in 4.x instead
      *
      * @author Héctor Hurtarte <hectorh30@gmail.com>
      */
    @@ -46,8 +46,6 @@ public function __construct(private readonly bool $debug = false, array $default
          */
         public function normalize(mixed $object, string $format = null, array $context = []): array
         {
    -        trigger_deprecation('api-platform', '3.2', sprintf('The class "%s" is deprecated in favor of using an Error resource. We fallback on "api_platform.serializer.normalizer.item".', __CLASS__));
    -
             if ($this->itemNormalizer) {
                 return $this->itemNormalizer->normalize($object, $format, $context);
             }
    
  • src/Problem/Serializer/ErrorNormalizer.php+1 3 modified
    @@ -22,7 +22,7 @@
      * Normalizes errors according to the API Problem spec (RFC 7807).
      *
      * @see https://tools.ietf.org/html/rfc7807
    - * @deprecated we use ItemNormalizer instead
    + * @deprecated we will use the ItemNormalizer in 4.x instead
      *
      * @author Kévin Dunglas <dunglas@gmail.com>
      */
    @@ -47,8 +47,6 @@ public function __construct(private readonly bool $debug = false, array $default
          */
         public function normalize(mixed $object, string $format = null, array $context = []): array
         {
    -        trigger_deprecation('api-platform', '3.2', sprintf('The class "%s" is deprecated in favor of using an Error resource. We fallback on "api_platform.serializer.normalizer.item".', __CLASS__));
    -
             if ($this->itemNormalizer) {
                 return $this->itemNormalizer->normalize($object, $format, $context);
             }
    
  • src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php+1 0 modified
    @@ -251,6 +251,7 @@ private function registerCommonConfiguration(ContainerBuilder $container, array
             }
             $container->setParameter('api_platform.asset_package', $config['asset_package']);
             $container->setParameter('api_platform.defaults', $this->normalizeDefaults($config['defaults'] ?? []));
    +        $container->setParameter('api_platform.rfc_7807_compliant_errors', $config['defaults']['extra_properties']['rfc_7807_compliant_errors'] ?? false);
     
             if ($container->getParameter('kernel.debug')) {
                 $container->removeDefinition('api_platform.serializer.mapping.cache_class_metadata_factory');
    
  • src/Symfony/Bundle/Resources/config/api.xml+1 0 modified
    @@ -198,6 +198,7 @@
                 <argument key="$exceptionToStatus">%api_platform.exception_to_status%</argument>
                 <argument key="$identifiersExtractor" type="service" id="api_platform.api.identifiers_extractor"/>
                 <argument key="$resourceClassResolver" type="service" id="api_platform.resource_class_resolver"/>
    +            <argument key="$problemCompliantErrors">%api_platform.rfc_7807_compliant_errors%</argument>
             </service>
         </services>
     </container>
    
  • src/Symfony/EventListener/ErrorListener.php+23 8 modified
    @@ -57,7 +57,8 @@ public function __construct(
             private readonly array $exceptionToStatus = [],
             private readonly null|IdentifiersExtractorInterface|LegacyIdentifiersExtractorInterface $identifiersExtractor = null,
             private readonly null|ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver = null,
    -        Negotiator $negotiator = null
    +        Negotiator $negotiator = null,
    +        private readonly bool $problemCompliantErrors = true
         ) {
             parent::__construct($controller, $logger, $debug, $exceptionsMapping);
             $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
    @@ -66,16 +67,31 @@ public function __construct(
     
         protected function duplicateRequest(\Throwable $exception, Request $request): Request
         {
    -        $dup = parent::duplicateRequest($exception, $request);
    -
             $apiOperation = $this->initializeOperation($request);
    +        if (false === $this->problemCompliantErrors) {
    +            // TODO: deprecate in API Platform 3.3
    +            $this->controller = 'api_platform.action.exception';
    +            $dup = parent::duplicateRequest($exception, $request);
    +            $dup->attributes->set('_api_operation', $apiOperation);
    +
    +            return $dup;
    +        }
     
             if ($this->debug) {
                 $this->logger?->error('An exception occured, transforming to an Error resource.', ['exception' => $exception, 'operation' => $apiOperation]);
             }
     
             $format = $this->getRequestFormat($request, $this->errorFormats, false);
     
    +        // Let the error handler take this we don't handle HTML
    +        if ('html' === $format) {
    +            $this->controller = 'error_controller';
    +            $dup = parent::duplicateRequest($exception, $request);
    +
    +            return $dup;
    +        }
    +
    +        $dup = parent::duplicateRequest($exception, $request);
             if ($this->resourceMetadataCollectionFactory) {
                 if ($this->resourceClassResolver?->isResourceClass($exception::class)) {
                     $resourceCollection = $this->resourceMetadataCollectionFactory->create($exception::class);
    @@ -126,9 +142,8 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re
                 $operation = $operation->withProvider([self::class, 'provide']);
             }
     
    -        // For our swagger Ui errors
    -        if ('html' === $format) {
    -            $operation = $operation->withOutputFormats(['html' => ['text/html']]);
    +        if (!$this->debug && $operation->getStatus() >= 500) {
    +            $errorResource->setDetail('Internal Server Error');
             }
     
             $identifiers = [];
    @@ -144,7 +159,7 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re
                 ]);
             }
     
    -        if ('jsonld' === $format && !($apiOperation?->getExtraProperties()['rfc_7807_compliant_errors'] ?? false)) {
    +        if ($apiOperation && 'jsonld' === $format && !($apiOperation?->getExtraProperties()['rfc_7807_compliant_errors'] ?? false)) {
                 $operation = $operation->withOutputFormats(['jsonld' => ['application/ld+json']])
                                        ->withLinks([])
                                        ->withExtraProperties(['rfc_7807_compliant_errors' => false] + $operation->getExtraProperties());
    @@ -154,7 +169,7 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re
             $dup->attributes->set('_api_previous_operation', $apiOperation);
             $dup->attributes->set('_api_operation', $operation);
             $dup->attributes->set('_api_operation_name', $operation->getName());
    -        $dup->attributes->set('exception', $errorResource);
    +        $dup->attributes->set('exception', $exception);
             // These are for swagger
             $dup->attributes->set('_api_original_route', $request->attributes->get('_route'));
             $dup->attributes->set('_api_original_route_params', $request->attributes->get('_route_params'));
    
  • tests/Symfony/Bundle/Test/ApiTestCaseTest.php+1 1 modified
    @@ -290,7 +290,7 @@ private function recreateSchema(array $options = []): void
          */
         public function testExceptionNormalizer(): void
         {
    -        $this->expectDeprecation('Since api-platform 3.2: The class "ApiPlatform\Problem\Serializer\ErrorNormalizer" is deprecated in favor of using an Error resource. We fallback on "api_platform.serializer.normalizer.item".');
    +        // $this->expectDeprecation('Since api-platform 3.2: The class "ApiPlatform\Problem\Serializer\ErrorNormalizer" is deprecated in favor of using an Error resource. We fallback on "api_platform.serializer.normalizer.item".');
     
             $response = self::createClient()->request('GET', '/issue5921', [
                 'headers' => [
    
  • tests/Symfony/EventListener/ErrorListenerTest.php+47 0 modified
    @@ -108,4 +108,51 @@ public function testDuplicateExceptionWithErrorResource(): void
             $errorListener = new ErrorListener('action', null, true, [], $resourceMetadataCollectionFactory->reveal(), ['jsonld' => ['application/ld+json']], [], $identifiersExtractor->reveal(), $resourceClassResolver->reveal());
             $errorListener->onKernelException($exceptionEvent);
         }
    +
    +    public function testDisableErrorResourceHandling(): void
    +    {
    +        $exception = Error::createFromException(new \Exception(), 400);
    +        $operation = new Get(name: '_api_errors_hydra', priority: 0, status: 400, outputFormats: ['jsonld' => ['application/ld+json']]);
    +        $resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
    +        $resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class);
    +        $kernel = $this->prophesize(KernelInterface::class);
    +        $kernel->handle(Argument::that(function ($request) {
    +            $this->assertEquals($request->attributes->get('_api_operation'), null);
    +
    +            return true;
    +        }), HttpKernelInterface::SUB_REQUEST, false)->willReturn(new Response());
    +        $exceptionEvent = new ExceptionEvent($kernel->reveal(), Request::create('/'), HttpKernelInterface::SUB_REQUEST, $exception);
    +        $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class);
    +        $identifiersExtractor->getIdentifiersFromItem($exception, Argument::any())->willReturn(['id' => 1]);
    +        $errorListener = new ErrorListener('action', null, true, [], $resourceMetadataCollectionFactory->reveal(), ['jsonld' => ['application/ld+json']], [], $identifiersExtractor->reveal(), $resourceClassResolver->reveal(), null, false);
    +        $errorListener->onKernelException($exceptionEvent);
    +    }
    +
    +    public function testDuplicateExceptionWithErrorResourceProduction(): void
    +    {
    +        $exception = new \Exception();
    +        $operation = new Get(name: '_api_errors_hydra', priority: 0, outputFormats: ['jsonld' => ['application/ld+json']]);
    +        $resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
    +        $resourceMetadataCollectionFactory->create(Error::class)
    +                                          ->willReturn(new ResourceMetadataCollection(Error::class, [new ApiResource(operations: [$operation])]));
    +        $resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class);
    +        $resourceClassResolver->isResourceClass(\Exception::class)->willReturn(false);
    +        $kernel = $this->prophesize(KernelInterface::class);
    +        $kernel->handle(Argument::that(function ($request) {
    +            $this->assertTrue($request->attributes->has('_api_original_route'));
    +            $this->assertTrue($request->attributes->has('_api_original_route_params'));
    +            $this->assertTrue($request->attributes->has('_api_requested_operation'));
    +            $this->assertTrue($request->attributes->has('_api_previous_operation'));
    +            $this->assertEquals('_api_errors_hydra', $request->attributes->get('_api_operation_name'));
    +            $operation = $request->attributes->get('_api_operation');
    +            $this->assertEquals(\call_user_func($operation->getProvider())->getDetail(), 'Internal Server Error');
    +
    +            return true;
    +        }), HttpKernelInterface::SUB_REQUEST, false)->willReturn(new Response());
    +        $exceptionEvent = new ExceptionEvent($kernel->reveal(), Request::create('/'), HttpKernelInterface::SUB_REQUEST, $exception);
    +        $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class);
    +        $identifiersExtractor->getIdentifiersFromItem(Argument::cetera())->willThrow(new \Exception());
    +        $errorListener = new ErrorListener('action', null, false, [], $resourceMetadataCollectionFactory->reveal(), ['jsonld' => ['application/ld+json']], [], $identifiersExtractor->reveal(), $resourceClassResolver->reveal());
    +        $errorListener->onKernelException($exceptionEvent);
    +    }
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.