diff --git a/SECURITY.md b/SECURITY.md index 54771a6..c21c44e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -82,6 +82,45 @@ When using `om-data-mapper`: 4. Follow the principle of least privilege when processing untrusted data 5. Validate and sanitize all input data before mapping +### ⚠️ Critical: Dynamic Code Generation Security + +This library uses dynamic code generation (`new Function()`) for performance optimization. **Mapping configurations MUST come from trusted sources only.** + +#### ✅ Safe Usage + +```typescript +// ✅ SAFE: Developer-defined configuration +const userMapper = Mapper.create({ + name: 'user.fullName', + email: 'user.email' +}); + +// ✅ SAFE: Using Decorator API (recommended) +@Mapper() +class UserDTO { + @Map('user.fullName') + name: string; +} +``` + +#### ❌ Unsafe Usage - NEVER DO THIS + +```typescript +// ❌ DANGEROUS: User input as mapping config +const userConfig = JSON.parse(request.body.mappingConfig); +const mapper = Mapper.create(userConfig); // CODE INJECTION RISK! + +// ❌ DANGEROUS: External untrusted source +const externalConfig = await fetch('https://untrusted-api.com/config'); +const mapper = Mapper.create(externalConfig); // CODE INJECTION RISK! +``` + +**Why this matters**: If an attacker can control the mapping configuration, they could inject arbitrary JavaScript code that executes with your application's privileges. + +**Recommended approach**: Use the Decorator API (`@Mapper`, `@Map`, `@Transform`) which is compile-time safe and provides better performance (112-474% faster). + +See the class documentation and `docs/DECORATOR_API.md` for more details. + ## Additional Resources - [GitHub Security Advisories](https://github.com/Isqanderm/data-mapper/security/advisories) diff --git a/examples/01-basic/nested-mapping/index.ts b/examples/01-basic/nested-mapping/index.ts index 983cbe1..83dc4ee 100644 --- a/examples/01-basic/nested-mapping/index.ts +++ b/examples/01-basic/nested-mapping/index.ts @@ -1,4 +1,3 @@ -import { MappingConfiguration } from '../../src/interface'; import { Mapper } from '../../src'; class Employee { diff --git a/examples/02-advanced/error-handling/complex.ts b/examples/02-advanced/error-handling/complex.ts index b4ddcbc..a2e37d0 100644 --- a/examples/02-advanced/error-handling/complex.ts +++ b/examples/02-advanced/error-handling/complex.ts @@ -1,4 +1,4 @@ -import { Mapper, MappingConfiguration } from '../../src'; +import { Mapper } from '../../src'; class Country { name?: string; diff --git a/src/core/Mapper.ts b/src/core/Mapper.ts index 81eb2c2..68df47a 100644 --- a/src/core/Mapper.ts +++ b/src/core/Mapper.ts @@ -12,6 +12,10 @@ import { getValueByPath, PathObject } from './utils'; * - Backward compatibility with existing code * - Dynamic mapping scenarios where decorators can't be used * + * ⚠️ **SECURITY NOTICE**: This mapper uses dynamic code generation (`new Function()`) + * for performance optimization. Mapping configurations MUST come from trusted sources only. + * Never pass user-controlled data as mapping configuration to prevent code injection attacks. + * * See docs/DECORATOR_API.md for the recommended approach. * See docs/MIGRATION_GUIDE.md for migration instructions. * @@ -37,6 +41,43 @@ export class Mapper { this.createCompiler = this.createCompiler.bind(this); } + /** + * Creates a new Mapper instance with the specified configuration. + * + * ⚠️ **SECURITY WARNING**: The `mappingConfig` parameter MUST come from a trusted source. + * This mapper uses dynamic code generation for performance. Passing user-controlled data + * as mapping configuration can lead to arbitrary code execution vulnerabilities. + * + * **Safe usage examples**: + * ```typescript + * // ✅ Safe: Configuration defined by developer + * const mapper = Mapper.create({ + * name: 'user.fullName', + * email: 'user.email' + * }); + * + * // ✅ Safe: Configuration from trusted internal source + * const mapper = Mapper.create(TRUSTED_MAPPING_CONFIGS.userMapper); + * ``` + * + * **Unsafe usage examples**: + * ```typescript + * // ❌ UNSAFE: User input as mapping config + * const userConfig = JSON.parse(request.body); + * const mapper = Mapper.create(userConfig); // DANGEROUS! + * + * // ❌ UNSAFE: External untrusted source + * const externalConfig = await fetch('untrusted-api.com/config'); + * const mapper = Mapper.create(externalConfig); // DANGEROUS! + * ``` + * + * @param mappingConfig - Mapping configuration (MUST be from trusted source) + * @param defaultValues - Optional default values for target properties + * @param config - Optional mapper configuration + * @returns A new Mapper instance + * + * @public + */ public static create( mappingConfig: MappingConfiguration, defaultValues?: DefaultValues, @@ -268,6 +309,36 @@ export class Mapper { .join('\n'); } + /** + * Compiles mapping configuration into an optimized function using dynamic code generation. + * + * ⚠️ **SECURITY WARNING**: This method uses `new Function()` for performance optimization. + * The mapping configuration MUST come from trusted sources only (developer-defined code). + * DO NOT pass user-controlled data as mapping configuration, as this could lead to + * arbitrary code execution. + * + * **Safe usage**: Mapping configuration is defined by developers at compile-time + * ```typescript + * const mapper = Mapper.create({ + * name: 'user.fullName', // ✅ Safe: developer-defined + * age: 'user.age' + * }); + * ``` + * + * **Unsafe usage**: DO NOT DO THIS + * ```typescript + * const userConfig = JSON.parse(request.body); // ❌ UNSAFE: user input + * const mapper = Mapper.create(userConfig); + * ``` + * + * @param mappingConfig - Mapping configuration (MUST be from trusted source) + * @param parentTarget - Optional parent target path for nested mappings + * @returns Compiled mapping function optimized for performance + * + * @internal + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function + * @see https://owasp.org/www-community/attacks/Code_Injection + */ private getCompiledFn( mappingConfig: MappingConfiguration, parentTarget?: string, @@ -278,6 +349,7 @@ export class Mapper { cache: { [key: string]: any }, ) => MappingResult { const body = this.getCompiledFnBody(mappingConfig, parentTarget, undefined, this.config); + // Using new Function for performance optimization - mapping config MUST be from trusted source const func = new Function(`source, target, __errors, cache`, `${body}`); return func as ( diff --git a/tests/benchmarks/memory-leak.test.ts b/tests/benchmarks/memory-leak.test.ts index 6f6b2ed..33c5f8f 100644 --- a/tests/benchmarks/memory-leak.test.ts +++ b/tests/benchmarks/memory-leak.test.ts @@ -3,7 +3,7 @@ * Ensures that validation and transformation operations don't cause memory leaks */ -import { describe, it, expect, beforeEach } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { validateSync } from '../../src/compat/class-validator'; import { plainToInstance } from '../../src/compat/class-transformer'; import { diff --git a/tests/unit/compat/class-validator/branch-coverage-boost.test.ts b/tests/unit/compat/class-validator/branch-coverage-boost.test.ts index d152b63..8622526 100644 --- a/tests/unit/compat/class-validator/branch-coverage-boost.test.ts +++ b/tests/unit/compat/class-validator/branch-coverage-boost.test.ts @@ -17,14 +17,7 @@ import { ValidateIf, ValidateNested, IsArray, - ArrayMinSize, - ArrayMaxSize, MinLength, - MaxLength, - IsDateString, - IsUUID, - IsURL, - IsPort, IsLatitude, IsLongitude, IsISO31661Alpha2, diff --git a/tests/unit/compat/class-validator/complex-combinations.test.ts b/tests/unit/compat/class-validator/complex-combinations.test.ts index 316cfc0..816767c 100644 --- a/tests/unit/compat/class-validator/complex-combinations.test.ts +++ b/tests/unit/compat/class-validator/complex-combinations.test.ts @@ -4,7 +4,7 @@ */ import { describe, it, expect } from 'vitest'; -import { validate, validateSync } from '../../../../src/compat/class-validator'; +import { validate } from '../../../../src/compat/class-validator'; import { IsOptional, IsString, @@ -18,7 +18,6 @@ import { MinLength, MaxLength, Min, - Max, } from '../../../../src/compat/class-validator/decorators'; import { Type } from '../../../../src/compat/class-transformer'; diff --git a/tests/unit/compat/class-validator/high-priority-validators.test.ts b/tests/unit/compat/class-validator/high-priority-validators.test.ts index 8c665bf..c89a942 100644 --- a/tests/unit/compat/class-validator/high-priority-validators.test.ts +++ b/tests/unit/compat/class-validator/high-priority-validators.test.ts @@ -10,13 +10,7 @@ import { IsMobilePhone, IsPostalCode, IsMongoId, - IsJWT, - IsStrongPassword, - IsPort, - IsMACAddress, - IsBase64, validate, - validateSync, } from '../../../../src/compat/class-validator'; describe('class-validator-compat - High Priority Validators', () => { diff --git a/tests/unit/compat/class-validator/phase2-validators.test.ts b/tests/unit/compat/class-validator/phase2-validators.test.ts index 7e2afdc..c3ac2c3 100644 --- a/tests/unit/compat/class-validator/phase2-validators.test.ts +++ b/tests/unit/compat/class-validator/phase2-validators.test.ts @@ -23,8 +23,6 @@ import { // Number IsDivisibleBy, IsDecimal, - IsPositive, - IsNegative, // Date MinDate, MaxDate, diff --git a/tests/unit/integration/validation-and-mapping.test.ts b/tests/unit/integration/validation-and-mapping.test.ts index ad53fd1..5c70151 100644 --- a/tests/unit/integration/validation-and-mapping.test.ts +++ b/tests/unit/integration/validation-and-mapping.test.ts @@ -31,12 +31,10 @@ import { IsBoolean, IsArray, MinLength, - MaxLength, Min, Max, IsOptional, IsDefined, - IsNotEmpty, ValidateNested, } from '../../../src/compat/class-validator';