fix(workflow): sanitize prototype-polluted keys in ResolverValidationPipe plainToInstance call
https://sonarly.com/issue/36199?type=bug The `updateWorkflowVersionStep` mutation crashes with `TypeError: targetType is not a constructor` when a request contains `"constructor"` as a JSON key inside the free-form `logicFunctionInput` field, causing `class-transformer`'s recursive object traversal to call `new (plain-object)()` on a non-constructor value. Fix: ## What changed **File:** `packages/twenty-server/src/engine/core-modules/graphql/pipes/resolver-validation.pipe.ts` `ResolverValidationPipe.transform` calls `plainToInstance(metatype, value)` before any error guard. When the incoming GraphQL input contains `"constructor"` as a JSON key (e.g. inside the freeform `logicFunctionInput` field of a workflow step), `class-transformer`'s `TransformOperationExecutor` recurses into the object, resolves `targetType` from the key's value (a plain object, not a constructor function), and throws `TypeError: targetType is not a constructor` at `new targetType()`. The fix wraps `plainToInstance` in a `try/catch` that returns the original value on failure, exactly matching the pattern the team already applied to `validate()` via `safeClassValidatorValidateWrapper` in commit `b31845b7ba`. If `plainToInstance` throws, the raw value is returned and normal resolver processing continues — for freeform JSON fields (which carry no class-validator constraints), this has no observable behavioral difference. A `Logger.warn` is also added so that `plainToInstance` failures are visible in structured logs with the metatype name and error message, rather than surfacing as unhandled Sentry exceptions.
This commit is contained in:
+19
-1
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
type ArgumentMetadata,
|
||||
Injectable,
|
||||
Logger,
|
||||
type PipeTransform,
|
||||
type Type,
|
||||
} from '@nestjs/common';
|
||||
@@ -22,6 +23,8 @@ const safeClassValidatorValidateWrapper = async (
|
||||
|
||||
@Injectable()
|
||||
export class ResolverValidationPipe implements PipeTransform {
|
||||
private readonly logger = new Logger(ResolverValidationPipe.name);
|
||||
|
||||
async transform(value: unknown, metadata: ArgumentMetadata) {
|
||||
const { metatype } = metadata;
|
||||
|
||||
@@ -29,7 +32,22 @@ export class ResolverValidationPipe implements PipeTransform {
|
||||
return value;
|
||||
}
|
||||
|
||||
const object = plainToInstance(metatype, value);
|
||||
// class-transformer throws when the plain object contains prototype-
|
||||
// pollution keys (e.g. "constructor") because it tries to call
|
||||
// `new targetType()` on a non-constructor value. Skip validation and
|
||||
// return the raw value so the resolver can handle it normally.
|
||||
let object: object;
|
||||
|
||||
try {
|
||||
object = plainToInstance(metatype, value);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`plainToInstance failed for ${metatype.name}, skipping validation: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const errors = await safeClassValidatorValidateWrapper(object);
|
||||
|
||||
if (errors.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user