first commit
This commit is contained in:
137
node_modules/n8n-workflow/dist/esm/expression-sandboxing.js
generated
vendored
Normal file
137
node_modules/n8n-workflow/dist/esm/expression-sandboxing.js
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
import { astBuilders as b, astVisit } from '@n8n/tournament';
|
||||
import { ExpressionError } from './errors';
|
||||
import { isSafeObjectProperty } from './utils';
|
||||
export const sanitizerName = '__sanitize';
|
||||
const sanitizerIdentifier = b.identifier(sanitizerName);
|
||||
export const DOLLAR_SIGN_ERROR = 'Cannot access "$" without calling it as a function';
|
||||
/**
|
||||
* Helper to check if an expression is a valid property access with $ as the property.
|
||||
* Returns true for obj.$ or obj.nested.$ but false for bare $ or other expression contexts.
|
||||
*/
|
||||
const isValidDollarPropertyAccess = (expr) => {
|
||||
if (typeof expr !== 'object' ||
|
||||
expr === null ||
|
||||
!('type' in expr) ||
|
||||
expr.type !== 'MemberExpression' ||
|
||||
!('property' in expr) ||
|
||||
!('object' in expr)) {
|
||||
return false;
|
||||
}
|
||||
const property = expr.property;
|
||||
const object = expr.object;
|
||||
// $ must be the property
|
||||
const isPropertyDollar = typeof property === 'object' &&
|
||||
property !== null &&
|
||||
'name' in property &&
|
||||
property.name === '$';
|
||||
// $ must NOT be the object (to block $.something)
|
||||
const isObjectDollar = typeof object === 'object' && object !== null && 'name' in object && object.name === '$';
|
||||
// Object must be an Identifier (obj) or MemberExpression (obj.nested)
|
||||
// This excludes bare $ or $ in other expression contexts
|
||||
const isObjectValid = typeof object === 'object' &&
|
||||
object !== null &&
|
||||
'type' in object &&
|
||||
(object.type === 'Identifier' || object.type === 'MemberExpression');
|
||||
return isPropertyDollar && !isObjectDollar && isObjectValid;
|
||||
};
|
||||
/**
|
||||
* Validates that the $ identifier is only used in allowed contexts.
|
||||
* This prevents user errors like `{{ $ }}` which would return the function object itself.
|
||||
*
|
||||
* Allowed contexts:
|
||||
* - As a function call: $()
|
||||
* - As a property name: obj.$ (where $ is a valid property name in JavaScript)
|
||||
*
|
||||
* Disallowed contexts:
|
||||
* - Bare identifier: $
|
||||
* - As object in member expression: $.property
|
||||
* - In expressions: "prefix" + $, [1, 2, $], etc.
|
||||
*/
|
||||
export const DollarSignValidator = (ast, _dataNode) => {
|
||||
astVisit(ast, {
|
||||
visitIdentifier(path) {
|
||||
this.traverse(path);
|
||||
const node = path.node;
|
||||
// Only check for the exact identifier '$'
|
||||
if (node.name !== '$')
|
||||
return;
|
||||
// Runtime type checking since path properties are typed as 'any'
|
||||
const parent = path.parent;
|
||||
// Check if parent is a path object with a 'name' property
|
||||
if (typeof parent !== 'object' || parent === null || !('name' in parent)) {
|
||||
throw new ExpressionError(DOLLAR_SIGN_ERROR);
|
||||
}
|
||||
// Allow $ when it's the callee: $()
|
||||
// parent.name === 'callee' means the parent path represents the callee field
|
||||
if (parent.name === 'callee') {
|
||||
return;
|
||||
}
|
||||
// Block when $ is the object in a MemberExpression: $.something
|
||||
// parent.name === 'object' means the parent path represents the object field
|
||||
if (parent.name === 'object') {
|
||||
throw new ExpressionError(DOLLAR_SIGN_ERROR);
|
||||
}
|
||||
// Check if $ is the property of a MemberExpression: obj.$
|
||||
// For obj.$: parent.name is 'expression' and grandparent has ExpressionStatement
|
||||
// The ExpressionStatement should contain a MemberExpression with $ as property
|
||||
if ('parent' in parent && typeof parent.parent === 'object' && parent.parent !== null) {
|
||||
const grandparent = parent.parent;
|
||||
if ('value' in grandparent &&
|
||||
typeof grandparent.value === 'object' &&
|
||||
grandparent.value !== null) {
|
||||
const gpNode = grandparent.value;
|
||||
// ExpressionStatement has an 'expression' field containing the actual expression
|
||||
if ('type' in gpNode && gpNode.type === 'ExpressionStatement' && 'expression' in gpNode) {
|
||||
// Check if this is a valid property access like obj.$
|
||||
if (isValidDollarPropertyAccess(gpNode.expression)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Disallow all other cases (bare $, $ in expressions, etc.)
|
||||
throw new ExpressionError(DOLLAR_SIGN_ERROR);
|
||||
},
|
||||
});
|
||||
};
|
||||
export const PrototypeSanitizer = (ast, dataNode) => {
|
||||
astVisit(ast, {
|
||||
visitMemberExpression(path) {
|
||||
this.traverse(path);
|
||||
const node = path.node;
|
||||
if (!node.computed) {
|
||||
// This is static, so we're safe to error here
|
||||
if (node.property.type !== 'Identifier') {
|
||||
throw new ExpressionError(`Unknown property type ${node.property.type} while sanitising expression`);
|
||||
}
|
||||
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 (!isSafeObjectProperty(node.property.value)) {
|
||||
throw new ExpressionError(`Cannot access "${node.property.value}" due to security concerns`);
|
||||
}
|
||||
}
|
||||
else if (!node.property.type.endsWith('Literal')) {
|
||||
// This isn't a literal value, so we need to wrap it
|
||||
path.replace(b.memberExpression(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
||||
node.object,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
b.callExpression(b.memberExpression(dataNode, sanitizerIdentifier), [
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
node.property,
|
||||
]), true));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
export const sanitizer = (value) => {
|
||||
if (!isSafeObjectProperty(value)) {
|
||||
throw new ExpressionError(`Cannot access "${value}" due to security concerns`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
//# sourceMappingURL=expression-sandboxing.js.map
|
||||
Reference in New Issue
Block a user