VYPR
High severity7.5NVD Advisory· Published Apr 3, 2025· Updated Apr 15, 2026

CVE-2025-31485

CVE-2025-31485

Description

API Platform Core is a system to create hypermedia-driven REST and GraphQL APIs. Prior to 4.0.22 and 3.4.17, a GraphQL grant on a property might be cached with different objects. The ApiPlatform\GraphQl\Serializer\ItemNormalizer::isCacheKeySafe() method is meant to prevent the caching but the parent::normalize method that is called afterwards still creates the cache key and causes the issue. This vulnerability is fixed in 4.0.22 and 3.4.17.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
api-platform/graphqlPackagist
>= 4.0.0-alpha.1, < 4.0.224.0.22
api-platform/corePackagist
>= 4.0.0-alpha.1, < 4.0.224.0.22
api-platform/graphqlPackagist
< 3.4.173.4.17
api-platform/corePackagist
< 3.4.173.4.17
api-platform/corePackagist
>= 4.1.0-alpha.1, < 4.1.54.1.5
api-platform/graphqlPackagist
>= 4.1.0-alpha.1, < 4.1.54.1.5

Patches

3
cba3acfbd517

fix(graphql): property security might be cached w/ different objects

https://github.com/api-platform/coreAntoine BluchetApr 3, 2025via ghsa
6 files changed · +403 0
  • src/GraphQl/Serializer/ItemNormalizer.php+2 0 modified
    @@ -89,6 +89,8 @@ public function normalize(mixed $object, ?string $format = null, array $context
     
             if ($this->isCacheKeySafe($context)) {
                 $context['cache_key'] = $this->getCacheKey($format, $context);
    +        } else {
    +            $context['cache_key'] = false;
             }
     
             unset($context['operation_name'], $context['operation']); // Remove operation and operation_name only when cache key has been created
    
  • tests/Fixtures/TestBundle/Document/SecuredDummyCollectionParent.php+45 0 added
    @@ -0,0 +1,45 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
    +
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ODM\Document]
    +class SecuredDummyCollectionParent
    +{
    +    #[ODM\Id]
    +    #[ODM\Field(type: 'id')]
    +    public ?string $id = null;
    +
    +    #[ODM\ReferenceOne(targetDocument: SecuredDummyCollection::class, inversedBy: 'parents')]
    +    #[ODM\Field(nullable: false)]
    +    public SecuredDummyCollection $child;
    +}
    
  • tests/Fixtures/TestBundle/Document/SecuredDummyCollection.php+60 0 added
    @@ -0,0 +1,60 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
    +
    +use ApiPlatform\Metadata\ApiProperty;
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ODM\Document]
    +class SecuredDummyCollection
    +{
    +    #[ODM\Id(strategy: 'AUTO', type: 'integer')]
    +    public ?int $id = null;
    +
    +    /**
    +     * @var string The title
    +     */
    +    #[ODM\Field]
    +    public string $title;
    +
    +    /**
    +     * @var string Secret property, only readable/writable by owners
    +     */
    +    #[ApiProperty(security: 'object == null or object.owner == user', securityPostDenormalize: 'object.owner == user')]
    +    #[ODM\Field]
    +    public ?string $ownerOnlyProperty = null;
    +
    +    /**
    +     * @var string The owner
    +     */
    +    #[ODM\Field]
    +    public string $owner;
    +}
    
  • tests/Fixtures/TestBundle/Entity/SecuredDummyCollectionParent.php+46 0 added
    @@ -0,0 +1,46 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
    +
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ORM\Mapping as ORM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ORM\Entity]
    +class SecuredDummyCollectionParent
    +{
    +    #[ORM\Column(type: 'integer')]
    +    #[ORM\Id]
    +    #[ORM\GeneratedValue(strategy: 'AUTO')]
    +    public ?int $id = null;
    +
    +    #[ORM\ManyToOne]
    +    #[ORM\JoinColumn(nullable: false)]
    +    public SecuredDummyCollection $child;
    +}
    
  • tests/Fixtures/TestBundle/Entity/SecuredDummyCollection.php+62 0 added
    @@ -0,0 +1,62 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
    +
    +use ApiPlatform\Metadata\ApiProperty;
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ORM\Mapping as ORM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ORM\Entity]
    +class SecuredDummyCollection
    +{
    +    #[ORM\Column(type: 'integer')]
    +    #[ORM\Id]
    +    #[ORM\GeneratedValue(strategy: 'AUTO')]
    +    public ?int $id = null;
    +
    +    /**
    +     * @var string The title
    +     */
    +    #[ORM\Column]
    +    public string $title;
    +
    +    /**
    +     * @var string Secret property, only readable/writable by owners
    +     */
    +    #[ApiProperty(security: 'object == null or object.owner == user', securityPostDenormalize: 'object.owner == user')]
    +    #[ORM\Column]
    +    public ?string $ownerOnlyProperty = null;
    +
    +    /**
    +     * @var string The owner
    +     */
    +    #[ORM\Column]
    +    public string $owner;
    +}
    
  • tests/Functional/GraphQl/SecurityTest.php+188 0 added
    @@ -0,0 +1,188 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Functional\GraphQl;
    +
    +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Document\SecuredDummy as DocumentSecuredDummy;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummy;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Document\SecuredDummyCollection as DocumentSecuredDummyCollection;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummyCollection;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummyCollectionParent;
    +use ApiPlatform\Tests\RecreateSchemaTrait;
    +use ApiPlatform\Tests\SetupClassResourcesTrait;
    +
    +final class SecurityTest extends ApiTestCase
    +{
    +    use RecreateSchemaTrait;
    +    use SetupClassResourcesTrait;
    +    protected static ?bool $alwaysBootKernel = false;
    +
    +    /**
    +     * @return class-string[]
    +     */
    +    public static function getResources(): array
    +    {
    +        return [SecuredDummy::class, SecuredDummyCollection::class, SecuredDummyCollectionParent::class];
    +    }
    +
    +    public function testQueryItem(): void
    +    {
    +        $resource = $this->isMongoDB() ? DocumentSecuredDummy::class : SecuredDummy::class;
    +        $this->recreateSchema([$resource]);
    +        $this->loadFixtures($resource);
    +        $client = self::createClient();
    +        $response = $client->request('POST', '/graphql', ['json' => [
    +            'query' => <<<QUERY
    +    {
    +      securedDummy(id: "/secured_dummies/1") {
    +        title
    +        description
    +      }
    +    }
    +QUERY,
    +        ]]);
    +
    +        $d = $response->toArray();
    +        $this->assertEquals('Access Denied.', $d['errors'][0]['message']);
    +    }
    +
    +    public function testCreateItemUnauthorized(): void
    +    {
    +        $resource = $this->isMongoDB() ? DocumentSecuredDummy::class : SecuredDummy::class;
    +        $this->recreateSchema([$resource]);
    +        $client = self::createClient();
    +        $response = $client->request('POST', '/graphql', ['json' => [
    +            'query' => <<<QUERY
    +mutation {
    +    createSecuredDummy(input: {owner: "me", title: "Hi", description: "Desc", adminOnlyProperty: "secret", clientMutationId: "auth"}) {
    +        securedDummy {
    +            title
    +            owner
    +        }
    +    }
    +}
    +QUERY,
    +        ]]);
    +
    +        $d = $response->toArray();
    +        $this->assertEquals('Only admins can create a secured dummy.', $d['errors'][0]['message']);
    +    }
    +
    +    public function testQueryItemWithNode(): void
    +    {
    +        $resource = $this->isMongoDB() ? DocumentSecuredDummy::class : SecuredDummy::class;
    +        $this->recreateSchema([$resource]);
    +        $this->loadFixtures($resource);
    +        $client = self::createClient();
    +        $response = $client->request('POST', '/graphql', ['json' => [
    +            'query' => <<<QUERY
    +    {
    +      node(id: "/secured_dummies/1") {
    +        ... on SecuredDummy {
    +            title
    +        }
    +      }
    +    }
    +QUERY,
    +        ]]);
    +
    +        $d = $response->toArray();
    +        $this->assertEquals('Access Denied.', $d['errors'][0]['message']);
    +    }
    +
    +    public function loadFixtures(string $resourceClass): void
    +    {
    +        $container = static::$kernel->getContainer();
    +        $registry = $this->isMongoDB() ? $container->get('doctrine_mongodb') : $container->get('doctrine');
    +        $manager = $registry->getManager();
    +        $s = new $resourceClass();
    +        $s->setTitle('Secured Dummy 1');
    +        $s->setDescription('Description 1');
    +        $s->setAdminOnlyProperty('admin secret');
    +        $s->setOwnerOnlyProperty('owner secret');
    +        $s->setAttributeBasedProperty('attribute based secret');
    +        $s->setOwner('user1');
    +
    +        $manager->persist($s);
    +        $manager->flush();
    +    }
    +
    +    public function testQueryCollection(): void
    +    {
    +        $resource = $this->isMongoDB() ? DocumentSecuredDummyCollection::class : SecuredDummyCollection::class;
    +        $this->recreateSchema([$resource, $resource.'Parent']);
    +        $this->loadFixturesQueryCollection($resource);
    +        $client = self::createClient();
    +
    +        $response = $client->request('POST', '/graphql', ['headers' => ['Authorization' => 'Basic ZHVuZ2xhczprZXZpbg=='], 'json' => [
    +            'query' => <<<QUERY
    +    {
    +        securedDummyCollectionParents {
    +            edges {
    +              node {
    +               child {
    +                  title, ownerOnlyProperty, owner
    +                }
    +              }
    +            }
    +        }
    +    }
    +QUERY,
    +        ]]);
    +
    +        $d = $response->toArray();
    +        $this->assertNull($d['data']['securedDummyCollectionParents']['edges'][1]['node']['child']['ownerOnlyProperty']);
    +    }
    +
    +    public function loadFixturesQueryCollection(string $resourceClass): void
    +    {
    +        $parentResourceClass = $resourceClass.'Parent';
    +        $container = static::$kernel->getContainer();
    +        $registry = $this->isMongoDB() ? $container->get('doctrine_mongodb') : $container->get('doctrine');
    +        $manager = $registry->getManager();
    +        $s = new $resourceClass();
    +        $s->title = 'Foo';
    +        $s->ownerOnlyProperty = 'Foo by dunglas';
    +        $s->owner = 'dunglas';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $s = new $resourceClass();
    +        $s->title = 'Bar';
    +        $s->ownerOnlyProperty = 'Bar by admin';
    +        $s->owner = 'admin';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $s = new $resourceClass();
    +        $s->title = 'Baz';
    +        $s->ownerOnlyProperty = 'Baz by dunglas';
    +        $s->owner = 'dunglas';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $s = new $resourceClass();
    +        $s->ownerOnlyProperty = 'Bat by admin';
    +        $s->owner = 'admin';
    +        $s->title = 'Bat';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $manager->flush();
    +    }
    +}
    
7af65aad1303

fix(graphql): property security might be cached w/ different objects

https://github.com/api-platform/coreAntoine BluchetApr 3, 2025via ghsa
6 files changed · +288 1
  • src/GraphQl/Serializer/ItemNormalizer.php+2 0 modified
    @@ -89,6 +89,8 @@ public function normalize(mixed $object, ?string $format = null, array $context
     
             if ($this->isCacheKeySafe($context)) {
                 $context['cache_key'] = $this->getCacheKey($format, $context);
    +        } else {
    +            $context['cache_key'] = false;
             }
     
             unset($context['operation_name'], $context['operation']); // Remove operation and operation_name only when cache key has been created
    
  • tests/Fixtures/TestBundle/Document/SecuredDummyCollectionParent.php+45 0 added
    @@ -0,0 +1,45 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
    +
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ODM\Document]
    +class SecuredDummyCollectionParent
    +{
    +    #[ODM\Id]
    +    #[ODM\Field(type: 'id')]
    +    public ?string $id = null;
    +
    +    #[ODM\ReferenceOne(targetDocument: SecuredDummyCollection::class, inversedBy: 'parents')]
    +    #[ODM\Field(nullable: false)]
    +    public SecuredDummyCollection $child;
    +}
    
  • tests/Fixtures/TestBundle/Document/SecuredDummyCollection.php+60 0 added
    @@ -0,0 +1,60 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
    +
    +use ApiPlatform\Metadata\ApiProperty;
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ODM\Document]
    +class SecuredDummyCollection
    +{
    +    #[ODM\Id(strategy: 'AUTO', type: 'integer')]
    +    public ?int $id = null;
    +
    +    /**
    +     * @var string The title
    +     */
    +    #[ODM\Field]
    +    public string $title;
    +
    +    /**
    +     * @var string Secret property, only readable/writable by owners
    +     */
    +    #[ApiProperty(security: 'object == null or object.owner == user', securityPostDenormalize: 'object.owner == user')]
    +    #[ODM\Field]
    +    public ?string $ownerOnlyProperty = null;
    +
    +    /**
    +     * @var string The owner
    +     */
    +    #[ODM\Field]
    +    public string $owner;
    +}
    
  • tests/Fixtures/TestBundle/Entity/SecuredDummyCollectionParent.php+46 0 added
    @@ -0,0 +1,46 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
    +
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ORM\Mapping as ORM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ORM\Entity]
    +class SecuredDummyCollectionParent
    +{
    +    #[ORM\Column(type: 'integer')]
    +    #[ORM\Id]
    +    #[ORM\GeneratedValue(strategy: 'AUTO')]
    +    public ?int $id = null;
    +
    +    #[ORM\ManyToOne]
    +    #[ORM\JoinColumn(nullable: false)]
    +    public SecuredDummyCollection $child;
    +}
    
  • tests/Fixtures/TestBundle/Entity/SecuredDummyCollection.php+62 0 added
    @@ -0,0 +1,62 @@
    +<?php
    +
    +/*
    + * This file is part of the API Platform project.
    + *
    + * (c) Kévin Dunglas <dunglas@gmail.com>
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
    +
    +use ApiPlatform\Metadata\ApiProperty;
    +use ApiPlatform\Metadata\ApiResource;
    +use ApiPlatform\Metadata\GraphQl\Query;
    +use ApiPlatform\Metadata\GraphQl\QueryCollection;
    +use ApiPlatform\Metadata\NotExposed;
    +use Doctrine\ORM\Mapping as ORM;
    +
    +/**
    + * Secured resource.
    + */
    +#[ApiResource(
    +    operations: [
    +        new NotExposed(),
    +    ],
    +    graphQlOperations: [
    +        new Query(),
    +        new QueryCollection(),
    +    ],
    +    security: 'is_granted(\'ROLE_USER\')'
    +)]
    +#[ORM\Entity]
    +class SecuredDummyCollection
    +{
    +    #[ORM\Column(type: 'integer')]
    +    #[ORM\Id]
    +    #[ORM\GeneratedValue(strategy: 'AUTO')]
    +    public ?int $id = null;
    +
    +    /**
    +     * @var string The title
    +     */
    +    #[ORM\Column]
    +    public string $title;
    +
    +    /**
    +     * @var string Secret property, only readable/writable by owners
    +     */
    +    #[ApiProperty(security: 'object == null or object.owner == user', securityPostDenormalize: 'object.owner == user')]
    +    #[ORM\Column]
    +    public ?string $ownerOnlyProperty = null;
    +
    +    /**
    +     * @var string The owner
    +     */
    +    #[ORM\Column]
    +    public string $owner;
    +}
    
  • tests/Functional/GraphQl/SecurityTest.php+73 1 modified
    @@ -16,20 +16,24 @@
     use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
     use ApiPlatform\Tests\Fixtures\TestBundle\Document\SecuredDummy as DocumentSecuredDummy;
     use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummy;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Document\SecuredDummyCollection as DocumentSecuredDummyCollection;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummyCollection;
    +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummyCollectionParent;
     use ApiPlatform\Tests\RecreateSchemaTrait;
     use ApiPlatform\Tests\SetupClassResourcesTrait;
     
     final class SecurityTest extends ApiTestCase
     {
         use RecreateSchemaTrait;
         use SetupClassResourcesTrait;
    +    protected static ?bool $alwaysBootKernel = false;
     
         /**
          * @return class-string[]
          */
         public static function getResources(): array
         {
    -        return [SecuredDummy::class];
    +        return [SecuredDummy::class, SecuredDummyCollection::class, SecuredDummyCollectionParent::class];
         }
     
         public function testQueryItem(): void
    @@ -113,4 +117,72 @@ public function loadFixtures(string $resourceClass): void
             $manager->persist($s);
             $manager->flush();
         }
    +
    +    public function testQueryCollection(): void
    +    {
    +        $resource = $this->isMongoDB() ? DocumentSecuredDummyCollection::class : SecuredDummyCollection::class;
    +        $this->recreateSchema([$resource, $resource.'Parent']);
    +        $this->loadFixturesQueryCollection($resource);
    +        $client = self::createClient();
    +
    +        $response = $client->request('POST', '/graphql', ['headers' => ['Authorization' => 'Basic ZHVuZ2xhczprZXZpbg=='], 'json' => [
    +            'query' => <<<QUERY
    +    {
    +        securedDummyCollectionParents {
    +            edges {
    +              node {
    +               child {
    +                  title, ownerOnlyProperty, owner
    +                }
    +              }
    +            }
    +        }
    +    }
    +QUERY,
    +        ]]);
    +
    +        $d = $response->toArray();
    +        $this->assertNull($d['data']['securedDummyCollectionParents']['edges'][1]['node']['child']['ownerOnlyProperty']);
    +    }
    +
    +    public function loadFixturesQueryCollection(string $resourceClass): void
    +    {
    +        $parentResourceClass = $resourceClass.'Parent';
    +        $container = static::$kernel->getContainer();
    +        $registry = $this->isMongoDB() ? $container->get('doctrine_mongodb') : $container->get('doctrine');
    +        $manager = $registry->getManager();
    +        $s = new $resourceClass();
    +        $s->title = 'Foo';
    +        $s->ownerOnlyProperty = 'Foo by dunglas';
    +        $s->owner = 'dunglas';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $s = new $resourceClass();
    +        $s->title = 'Bar';
    +        $s->ownerOnlyProperty = 'Bar by admin';
    +        $s->owner = 'admin';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $s = new $resourceClass();
    +        $s->title = 'Baz';
    +        $s->ownerOnlyProperty = 'Baz by dunglas';
    +        $s->owner = 'dunglas';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $s = new $resourceClass();
    +        $s->ownerOnlyProperty = 'Bat by admin';
    +        $s->owner = 'admin';
    +        $s->title = 'Bat';
    +        $manager->persist($s);
    +        $p = new $parentResourceClass();
    +        $p->child = $s;
    +        $manager->persist($p);
    +        $manager->flush();
    +    }
     }
    

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

8

News mentions

0

No linked articles in our index yet.