CVE-2024-29181 (published 2024-06-12T15:15Z)

Strapi is an open-source content management system. Prior to version 4.19.1, a super admin can create a collection where an item in the collection has an association to another collection. When this happens, another user with Author Role can see the list of associated items they did not create. They should see nothing but their own items they created not all items ever created. Users should upgrade @strapi/plugin-content-manager to version 4.19.1 to receive a patch.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/strapi/strapi
commit_details: repo_url: https://github.com/strapi/strapi
author: Bassel Kanso
author_date: 2024-01-29 16:29:46+02:00
msg: fix: Check component names against collectionTypeNames
repository: repo_url: https://github.com/strapi/strapi
repo_name: strapi/strapi
description: 🚀 Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable and developer-first.
repo_language: TypeScript
owner: strapi
file_changes: filename: FormModal.tsx
change_type: ModificationType.MODIFY
diff: @@ -372,6 +372,8 @@ export const FormModal = () => {
         modifiedData.category || '',
         reservedNames,
         actionType === 'edit',
+        components,
+        modifiedData.displayName || '',
         get(allDataSchema, [...pathToSchema, 'uid'], null)
         // ctbFormsAPI
       );
@@ -394,7 +396,9 @@ export const FormModal = () => {
         Object.keys(components) as Common.UID.Component[],
         get(modifiedData, 'componentToCreate.category', ''),
         reservedNames,
-        ctbFormsAPI
+        actionType === 'edit',
+        components,
+        modifiedData.componentToCreate.displayName || ''
       );
 
       // Check form validity for creating a 'common attribute'
@@ -447,7 +451,9 @@ export const FormModal = () => {
           Object.keys(components) as Common.UID.Component[],
           get(modifiedData, 'componentToCreate.category', ''),
           reservedNames,
-          ctbFormsAPI
+          actionType === 'edit',
+          components,
+          modifiedData.componentToCreate.displayName || ''
         );
       } else {
         // The form is valid

programming_language: TypeScript
filename: createComponentSchema.ts
change_type: ModificationType.MODIFY
diff: @@ -8,7 +8,9 @@ import { createComponentUid } from '../utils/createUid';
 export const createComponentSchema = (
   usedComponentNames: Array<string>,
   reservedNames: Array<string>,
-  category: string
+  category: string,
+  takenCollectionNames: Array<string>,
+  currentCollectionName: string
 ) => {
   const shape = {
     displayName: yup
@@ -23,7 +25,10 @@ export const createComponentSchema = (
 
           const name = createComponentUid(value, category);
 
-          return !usedComponentNames.includes(name);
+          return (
+            !usedComponentNames.includes(name) &&
+            !takenCollectionNames.includes(currentCollectionName)
+          );
         },
       })
       .test({

programming_language: TypeScript
filename: forms.ts
change_type: ModificationType.MODIFY
diff: @@ -12,6 +12,7 @@ import { createContentTypeSchema } from '../contentType/createContentTypeSchema'
 import { dynamiczoneForm } from '../dynamiczoneForm';
 
 import { addItemsToFormSection, FormTypeOptions } from './utils/addItemsToFormSection';
+import { createComponentCollectionName } from './utils/createCollectionName';
 import { Attribute, getUsedAttributeNames, SchemaData } from './utils/getUsedAttributeNames';
 
 import type { Common } from '@strapi/types';
@@ -308,13 +309,33 @@ export const forms = {
         models: any;
       },
       isEditing = false,
+      components: Record<string, any>,
+      componentDisplayName: string,
       compoUid: Common.UID.Component | null = null
     ) {
       const takenNames = isEditing
         ? alreadyTakenAttributes.filter((uid: Common.UID.Component) => uid !== compoUid)
         : alreadyTakenAttributes;
+      const collectionNames = Object.values(components).map((component: any) => {
+        return component?.schema?.collectionName;
+      });
+
+      const currentCollectionName = createComponentCollectionName(
+        componentDisplayName,
+        componentCategory
+      );
 
-      return createComponentSchema(takenNames, reservedNames.models, componentCategory);
+      const takenCollectionNames = isEditing
+        ? collectionNames.filter((collectionName) => collectionName !== currentCollectionName)
+        : collectionNames;
+
+      return createComponentSchema(
+        takenNames,
+        reservedNames.models,
+        componentCategory,
+        takenCollectionNames,
+        currentCollectionName
+      );
     },
     form: {
       advanced() {

programming_language: TypeScript
filename: createCollectionName.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1,10 @@
+import { snakeCase } from 'lodash/fp';
+import pluralize from 'pluralize';
+
+// TODO: create a utility for this
+// Duplicate in server/src/services/schema-builder/component-builder.ts
+const createComponentCollectionName = (name: string, category: string) => {
+  return `components_${snakeCase(category)}_${pluralize(snakeCase(name))}`;
+};
+
+export { createComponentCollectionName };

programming_language: TypeScript
filename: component-builder.ts
change_type: ModificationType.MODIFY
diff: @@ -37,10 +37,18 @@ export default function createComponentBuilder() {
         filename: `${nameToSlug(infos.displayName)}.json`,
       });
 
+      // TODO: create a utility for this
+      // Duplicate in admin/src/components/FormModal/forms/utils/createCollectionName.ts
       const collectionName = `components_${nameToCollectionName(
         infos.category
       )}_${nameToCollectionName(pluralize(infos.displayName))}`;
 
+      this.components.forEach((compo: any) => {
+        if (compo.schema.collectionName === collectionName) {
+          throw new ApplicationError('component.alreadyExists');
+        }
+      });
+
       handler
         .setUID(uid)
         .set('collectionName', collectionName)

programming_language: TypeScript