@@ -764,8 +764,12 @@ private function getResourceFromIri(string $data, array $context, string $resour
764764 try {
765765 $ item = $ this ->iriConverter ->getResourceFromIri ($ data , $ context + ['fetch_data ' => true ]);
766766
767- // Type-confusion guard: declared relation class must match the IRI's resource.
768- if (!is_a ($ item , $ resourceClass )) {
767+ // Type-confusion guard (CWE-843 / GHSA-9rjg-x2p2-h68h): the resolved
768+ // resource must be an instance of the declared class. For union-typed
769+ // properties (e.g. Foo[]|Bar[]) every resource class in the union is
770+ // accepted; a genuinely foreign type is still rejected.
771+ $ allowedClasses = $ context ['_api_union_resource_classes ' ] ?? [$ resourceClass ];
772+ if (!\array_reduce ($ allowedClasses , static fn (bool $ ok , string $ cls ): bool => $ ok || \is_a ($ item , $ cls ), false )) {
769773 throw new NotNormalizableValueException (\sprintf ('The iri "%s" does not reference the correct resource. ' , $ data ));
770774 }
771775
@@ -1191,6 +1195,38 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
11911195 $ isMultipleTypes = \count ($ types ) > 1 ;
11921196 $ denormalizationException = null ;
11931197
1198+ // Build the full set of resource classes across all union types so that
1199+ // getResourceFromIri()'s is_a guard can accept any valid union member.
1200+ // Without this, a Foo[]|Bar[] property rejects mixed collections because
1201+ // the guard only sees the single class being tried in each iteration.
1202+ unset($ context ['_api_union_resource_classes ' ]);
1203+ if ($ isMultipleTypes ) {
1204+ $ unionResourceClasses = [];
1205+ foreach ($ types as $ ut ) {
1206+ if ($ ut instanceof CollectionType) {
1207+ $ cvt = $ ut ->getCollectionValueType ();
1208+ if ($ cvt instanceof ObjectType && $ this ->resourceClassResolver ->isResourceClass ($ cvt ->getClassName ())) {
1209+ $ unionResourceClasses [] = $ cvt ->getClassName ();
1210+ }
1211+ } elseif ($ ut instanceof ObjectType && $ this ->resourceClassResolver ->isResourceClass ($ ut ->getClassName ())) {
1212+ $ unionResourceClasses [] = $ ut ->getClassName ();
1213+ } elseif ($ ut instanceof LegacyType) {
1214+ if ($ ut ->isCollection ()) {
1215+ foreach ($ ut ->getCollectionValueTypes () as $ cvt ) {
1216+ if (null !== ($ cn = $ cvt ->getClassName ()) && $ this ->resourceClassResolver ->isResourceClass ($ cn )) {
1217+ $ unionResourceClasses [] = $ cn ;
1218+ }
1219+ }
1220+ } elseif (null !== ($ cn = $ ut ->getClassName ()) && $ this ->resourceClassResolver ->isResourceClass ($ cn )) {
1221+ $ unionResourceClasses [] = $ cn ;
1222+ }
1223+ }
1224+ }
1225+ if ($ unionResourceClasses ) {
1226+ $ context ['_api_union_resource_classes ' ] = array_unique ($ unionResourceClasses );
1227+ }
1228+ }
1229+
11941230 foreach ($ types as $ t ) {
11951231 if ($ type instanceof Type) {
11961232 $ isNullable = $ type ->isNullable ();
0 commit comments