mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
fix(HTTP Request Node): Fix prototype pollution vulnerability (#15463)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { type ASTAfterHook, astBuilders as b, astVisit } from '@n8n/tournament';
|
||||
|
||||
import { ExpressionError } from './errors';
|
||||
import { isSafeObjectProperty } from './utils';
|
||||
|
||||
const forbiddenMembers = ['__proto__', 'prototype', 'constructor', 'getPrototypeOf'];
|
||||
export const sanitizerName = '__sanitize';
|
||||
const sanitizerIdentifier = b.identifier(sanitizerName);
|
||||
|
||||
@@ -20,14 +20,14 @@ export const PrototypeSanitizer: ASTAfterHook = (ast, dataNode) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (forbiddenMembers.includes(node.property.name)) {
|
||||
if (!isSafeObjectProperty(node.property.name)) {
|
||||
throw new ExpressionError(
|
||||
`Cannot access "${node.property.name}" due to security concerns`,
|
||||
);
|
||||
}
|
||||
} else if (node.property.type === 'StringLiteral' || node.property.type === 'Literal') {
|
||||
// Check any static strings against our forbidden list
|
||||
if (forbiddenMembers.includes(node.property.value as string)) {
|
||||
if (!isSafeObjectProperty(node.property.value as string)) {
|
||||
throw new ExpressionError(
|
||||
`Cannot access "${node.property.value as string}" due to security concerns`,
|
||||
);
|
||||
@@ -52,7 +52,7 @@ export const PrototypeSanitizer: ASTAfterHook = (ast, dataNode) => {
|
||||
};
|
||||
|
||||
export const sanitizer = (value: unknown): unknown => {
|
||||
if (forbiddenMembers.includes(value as string)) {
|
||||
if (!isSafeObjectProperty(value as string)) {
|
||||
throw new ExpressionError(`Cannot access "${value as string}" due to security concerns`);
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -35,6 +35,8 @@ export {
|
||||
updateDisplayOptions,
|
||||
randomInt,
|
||||
randomString,
|
||||
isSafeObjectProperty,
|
||||
setSafeObjectProperty,
|
||||
} from './utils';
|
||||
export {
|
||||
isINodeProperties,
|
||||
|
||||
@@ -283,3 +283,30 @@ export function randomString(minLength: number, maxLength?: number): string {
|
||||
export function hasKey<T extends PropertyKey>(value: unknown, key: T): value is Record<T, unknown> {
|
||||
return value !== null && typeof value === 'object' && value.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
const unsafeObjectProperties = new Set(['__proto__', 'prototype', 'constructor', 'getPrototypeOf']);
|
||||
|
||||
/**
|
||||
* Checks if a property key is safe to use on an object, preventing prototype pollution.
|
||||
* setting untrusted properties can alter the object's prototype chain and introduce vulnerabilities.
|
||||
*
|
||||
* @see setSafeObjectProperty
|
||||
*/
|
||||
export function isSafeObjectProperty(property: string) {
|
||||
return !unsafeObjectProperties.has(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely sets a property on an object, preventing prototype pollution.
|
||||
*
|
||||
* @see isSafeObjectProperty
|
||||
*/
|
||||
export function setSafeObjectProperty(
|
||||
target: Record<string, unknown>,
|
||||
property: string,
|
||||
value: unknown,
|
||||
) {
|
||||
if (isSafeObjectProperty(property)) {
|
||||
target[property] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
randomInt,
|
||||
randomString,
|
||||
hasKey,
|
||||
isSafeObjectProperty,
|
||||
setSafeObjectProperty,
|
||||
} from '@/utils';
|
||||
|
||||
describe('isObjectEmpty', () => {
|
||||
@@ -366,3 +368,29 @@ describe('hasKey', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSafeObjectProperty', () => {
|
||||
it.each([
|
||||
['__proto__', false],
|
||||
['prototype', false],
|
||||
['constructor', false],
|
||||
['getPrototypeOf', false],
|
||||
['safeKey', true],
|
||||
['anotherKey', true],
|
||||
['toString', true],
|
||||
])('should return %s for key "%s"', (key, expected) => {
|
||||
expect(isSafeObjectProperty(key)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSafeObjectProperty', () => {
|
||||
it.each([
|
||||
['safeKey', 123, { safeKey: 123 }],
|
||||
['__proto__', 456, {}],
|
||||
['constructor', 'test', {}],
|
||||
])('should set property "%s" safely', (key, value, expected) => {
|
||||
const obj: Record<string, unknown> = {};
|
||||
setSafeObjectProperty(obj, key, value);
|
||||
expect(obj).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user