Skip to content

Commit 1c327df

Browse files
committed
better prototype pollution protections
1 parent c1a9604 commit 1c327df

File tree

7 files changed

+44
-17
lines changed

7 files changed

+44
-17
lines changed

packages/@aws-cdk/mixins-preview/lib/core/applicator.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ export class MixinApplicator {
2525
const constructs = this.selector.select(this.scope);
2626
for (const construct of constructs) {
2727
if (mixin.supports(construct)) {
28-
mixin.applyTo(construct);
2928
const errors = mixin.validate?.(construct) ?? [];
3029
if (errors.length > 0) {
31-
throw new ValidationError(`Mixin validation failed: ${errors.join(', ')}`, construct);
30+
throw new ValidationError(`Mixin validation failed: ${errors.join(', ')}`, this.scope);
3231
}
32+
mixin.applyTo(construct);
3333
}
3434
}
3535
return this;
@@ -43,16 +43,16 @@ export class MixinApplicator {
4343
let applied = false;
4444
for (const construct of constructs) {
4545
if (mixin.supports(construct)) {
46-
mixin.applyTo(construct);
4746
const errors = mixin.validate?.(construct) ?? [];
4847
if (errors.length > 0) {
4948
throw new ValidationError(`Mixin validation failed: ${errors.join(', ')}`, construct);
5049
}
50+
mixin.applyTo(construct);
5151
applied = true;
5252
}
5353
}
5454
if (!applied) {
55-
throw new UnscopedValidationError(`Mixin ${mixin.constructor.name} could not be applied to any constructs`);
55+
throw new ValidationError(`Mixin ${mixin.constructor.name} could not be applied to any constructs`, this.scope);
5656
}
5757
return this;
5858
}

packages/@aws-cdk/mixins-preview/lib/core/mixins.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import type { IConstruct } from 'constructs';
22
import type { ConstructSelector } from './selectors';
33
import { MixinApplicator } from './applicator';
44

5+
// this will change when we update the interface to deliberately break compatibility checks
6+
const MIXIN_SYMBOL = Symbol.for('@aws-cdk/mixins-preview.Mixin.pre1');
7+
58
/**
69
* Main entry point for applying mixins.
710
*/
@@ -39,6 +42,20 @@ export interface IMixin {
3942
* Abstract base class for mixins that provides default implementations.
4043
*/
4144
export abstract class Mixin implements IMixin {
45+
/**
46+
* Checks if `x` is a Mixin.
47+
*
48+
* @param x Any object
49+
* @returns true if `x` is an object created from a class which extends `Mixin`.
50+
*/
51+
static isMixin(x: any): x is Mixin {
52+
return x != null && typeof x === 'object' && MIXIN_SYMBOL in x;
53+
}
54+
55+
constructor() {
56+
Object.defineProperty(this, MIXIN_SYMBOL, { value: true });
57+
}
58+
4259
public supports(_construct: IConstruct): boolean {
4360
return true;
4461
}

packages/@aws-cdk/mixins-preview/lib/core/selectors.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,7 @@ export abstract class ConstructSelector {
4141

4242
class AllConstructsSelector extends ConstructSelector {
4343
select(scope: IConstruct): IConstruct[] {
44-
const result: IConstruct[] = [];
45-
const visit = (node: IConstruct) => {
46-
result.push(node);
47-
for (const child of node.node.children) {
48-
visit(child);
49-
}
50-
};
51-
visit(scope);
52-
return result;
44+
return scope.node.findAll();
5345
}
5446
}
5547

packages/@aws-cdk/mixins-preview/lib/util/property-mixins.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function deepMerge(target: any, source: any, mergeOnly: string[]): any {
2121

2222
if (typeof sourceValue === 'object' && sourceValue != null && !Array.isArray(sourceValue) &&
2323
typeof targetValue === 'object' && targetValue != null && !Array.isArray(targetValue)) {
24-
target[key] = deepMergeCopy({}, targetValue, sourceValue);
24+
target[key] = deepMergeCopy(Object.create(null), targetValue, sourceValue);
2525
} else {
2626
target[key] = sourceValue;
2727
}
@@ -58,6 +58,9 @@ const MERGE_EXCLUDE_KEYS: string[] = [
5858
];
5959

6060
/**
61+
* This is an unchanged copy from packages/aws-cdk-lib/core/lib/cfn-resource.ts
62+
* The intention will be to use this function from core once the package is merged.
63+
*
6164
* Merges `source` into `target`, overriding any existing values.
6265
* `null`s will cause a value to be deleted.
6366
*/
@@ -68,7 +71,7 @@ function deepMergeCopy(target: any, ...sources: any[]) {
6871
}
6972

7073
for (const key of Object.keys(source)) {
71-
if (key === '__proto__' || key === 'constructor') {
74+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
7275
continue;
7376
}
7477

packages/@aws-cdk/mixins-preview/test/mixin.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,18 @@ describe('IMixin', () => {
2626
expect((result as any).mixinApplied).toBe(true);
2727
});
2828
});
29+
30+
describe('Mixin', () => {
31+
test('isMixin returns true for Mixin instances', () => {
32+
const mixin = new TestMixin();
33+
expect(Mixin.isMixin(mixin)).toBe(true);
34+
});
35+
36+
test('isMixin returns false for non-Mixin objects', () => {
37+
expect(Mixin.isMixin({})).toBe(false);
38+
expect(Mixin.isMixin(null)).toBe(false);
39+
expect(Mixin.isMixin(undefined)).toBe(false);
40+
expect(Mixin.isMixin('string')).toBe(false);
41+
expect(Mixin.isMixin(123)).toBe(false);
42+
});
43+
});

packages/aws-cdk-lib/aws-ec2/lib/cfn-init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ function deepMerge(target?: Record<string, any>, src?: Record<string, any>) {
302302
if (src == null) { return target; }
303303

304304
for (const [key, value] of Object.entries(src)) {
305-
if (key === '__proto__' || key === 'constructor') {
305+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
306306
continue;
307307
}
308308

packages/aws-cdk-lib/core/lib/cfn-resource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ function deepMerge(target: any, ...sources: any[]) {
661661
}
662662

663663
for (const key of Object.keys(source)) {
664-
if (key === '__proto__' || key === 'constructor') {
664+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
665665
continue;
666666
}
667667

0 commit comments

Comments
 (0)