Application file storage service (#20793)

# Introduction
Fix unsafe resource path join with expected prefix at file storage
directly
Add early paths transversal detections in metadata validators
This commit is contained in:
Paul Rastoin
2026-05-21 11:21:44 +02:00
committed by GitHub
parent 1ed347bc17
commit a3c92311e3
20 changed files with 1661 additions and 5 deletions
@@ -5,6 +5,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { isSafeRelativePath } from 'src/engine/core-modules/file-storage/utils/is-safe-relative-path.util';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { FrontComponentExceptionCode } from 'src/engine/metadata-modules/front-component/front-component.exception';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
@@ -36,6 +37,28 @@ export class FlatFrontComponentValidatorService {
});
}
if (
isDefined(flatFrontComponent.builtComponentPath) &&
!isSafeRelativePath(flatFrontComponent.builtComponentPath)
) {
validationResult.errors.push({
code: FrontComponentExceptionCode.INVALID_FRONT_COMPONENT_INPUT,
message: t`Built component path contains unsafe characters`,
userFriendlyMessage: msg`Built component path contains unsafe characters`,
});
}
if (
isDefined(flatFrontComponent.sourceComponentPath) &&
!isSafeRelativePath(flatFrontComponent.sourceComponentPath)
) {
validationResult.errors.push({
code: FrontComponentExceptionCode.INVALID_FRONT_COMPONENT_INPUT,
message: t`Source component path contains unsafe characters`,
userFriendlyMessage: msg`Source component path contains unsafe characters`,
});
}
return validationResult;
}
@@ -76,6 +99,7 @@ export class FlatFrontComponentValidatorService {
public validateFlatFrontComponentUpdate({
universalIdentifier,
flatEntityUpdate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatFrontComponentMaps: optimisticFlatFrontComponentMaps,
},
@@ -105,6 +129,28 @@ export class FlatFrontComponentValidatorService {
return validationResult;
}
if (
isDefined(flatEntityUpdate.builtComponentPath) &&
!isSafeRelativePath(flatEntityUpdate.builtComponentPath)
) {
validationResult.errors.push({
code: FrontComponentExceptionCode.INVALID_FRONT_COMPONENT_INPUT,
message: t`Built component path contains unsafe characters`,
userFriendlyMessage: msg`Built component path contains unsafe characters`,
});
}
if (
isDefined(flatEntityUpdate.sourceComponentPath) &&
!isSafeRelativePath(flatEntityUpdate.sourceComponentPath)
) {
validationResult.errors.push({
code: FrontComponentExceptionCode.INVALID_FRONT_COMPONENT_INPUT,
message: t`Source component path contains unsafe characters`,
userFriendlyMessage: msg`Source component path contains unsafe characters`,
});
}
return validationResult;
}
}
@@ -4,12 +4,13 @@ import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { isSafeRelativePath } from 'src/engine/core-modules/file-storage/utils/is-safe-relative-path.util';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { LogicFunctionExceptionCode } from 'src/engine/metadata-modules/logic-function/logic-function.exception';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { type UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
@Injectable()
export class FlatLogicFunctionValidatorService {
@@ -17,6 +18,7 @@ export class FlatLogicFunctionValidatorService {
public validateFlatLogicFunctionUpdate({
universalIdentifier,
flatEntityUpdate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatLogicFunctionMaps: optimisticFlatLogicFunctionMaps,
},
@@ -42,6 +44,30 @@ export class FlatLogicFunctionValidatorService {
message: t`Logic function not found`,
userFriendlyMessage: msg`Logic function not found`,
});
return validationResult;
}
if (
isDefined(flatEntityUpdate.builtHandlerPath) &&
!isSafeRelativePath(flatEntityUpdate.builtHandlerPath)
) {
validationResult.errors.push({
code: LogicFunctionExceptionCode.INVALID_LOGIC_FUNCTION_INPUT,
message: t`Built handler path contains unsafe characters`,
userFriendlyMessage: msg`Built handler path contains unsafe characters`,
});
}
if (
isDefined(flatEntityUpdate.sourceHandlerPath) &&
!isSafeRelativePath(flatEntityUpdate.sourceHandlerPath)
) {
validationResult.errors.push({
code: LogicFunctionExceptionCode.INVALID_LOGIC_FUNCTION_INPUT,
message: t`Source handler path contains unsafe characters`,
userFriendlyMessage: msg`Source handler path contains unsafe characters`,
});
}
return validationResult;
@@ -110,6 +136,28 @@ export class FlatLogicFunctionValidatorService {
});
}
if (
isDefined(flatLogicFunctionToValidate.builtHandlerPath) &&
!isSafeRelativePath(flatLogicFunctionToValidate.builtHandlerPath)
) {
validationResult.errors.push({
code: LogicFunctionExceptionCode.INVALID_LOGIC_FUNCTION_INPUT,
message: t`Built handler path contains unsafe characters`,
userFriendlyMessage: msg`Built handler path contains unsafe characters`,
});
}
if (
isDefined(flatLogicFunctionToValidate.sourceHandlerPath) &&
!isSafeRelativePath(flatLogicFunctionToValidate.sourceHandlerPath)
) {
validationResult.errors.push({
code: LogicFunctionExceptionCode.INVALID_LOGIC_FUNCTION_INPUT,
message: t`Source handler path contains unsafe characters`,
userFriendlyMessage: msg`Source handler path contains unsafe characters`,
});
}
return validationResult;
}
}