chore:little fixes and formating and linting and patches
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import { ALLOWED_GLOBALS, ALLOWED_INSTANCE_METHODS, ALLOWED_METHODS } from './constants';
|
||||
|
||||
/**
|
||||
* Simple recursive AST walker that doesn't require @babel/traverse
|
||||
*/
|
||||
@@ -7,9 +5,9 @@ export function walkNode(
|
||||
node: unknown,
|
||||
visitor: (
|
||||
node: Record<string, unknown>,
|
||||
parent?: Record<string, unknown>,
|
||||
parent?: Record<string, unknown>
|
||||
) => void,
|
||||
parent?: Record<string, unknown>,
|
||||
parent?: Record<string, unknown>
|
||||
): void {
|
||||
if (!node || typeof node !== 'object') {
|
||||
return;
|
||||
@@ -82,7 +80,7 @@ export function collectDeclaredIdentifiers(ast: unknown): Set<string> {
|
||||
*/
|
||||
function collectPatternIdentifiers(
|
||||
pattern: Record<string, unknown>,
|
||||
declared: Set<string>,
|
||||
declared: Set<string>
|
||||
): void {
|
||||
if (pattern.type === 'Identifier') {
|
||||
declared.add(pattern.name as string);
|
||||
@@ -92,12 +90,12 @@ function collectPatternIdentifiers(
|
||||
if (prop.type === 'ObjectProperty') {
|
||||
collectPatternIdentifiers(
|
||||
prop.value as Record<string, unknown>,
|
||||
declared,
|
||||
declared
|
||||
);
|
||||
} else if (prop.type === 'RestElement') {
|
||||
collectPatternIdentifiers(
|
||||
prop.argument as Record<string, unknown>,
|
||||
declared,
|
||||
declared
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -111,13 +109,13 @@ function collectPatternIdentifiers(
|
||||
} else if (pattern.type === 'RestElement') {
|
||||
collectPatternIdentifiers(
|
||||
pattern.argument as Record<string, unknown>,
|
||||
declared,
|
||||
declared
|
||||
);
|
||||
} else if (pattern.type === 'AssignmentPattern') {
|
||||
// Default parameter values: (x = 5) => ...
|
||||
collectPatternIdentifiers(
|
||||
pattern.left as Record<string, unknown>,
|
||||
declared,
|
||||
declared
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -127,9 +125,11 @@ function collectPatternIdentifiers(
|
||||
*/
|
||||
export function isPropertyKey(
|
||||
node: Record<string, unknown>,
|
||||
parent?: Record<string, unknown>,
|
||||
parent?: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!parent) return false;
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Property in object literal: { foo: value } - foo is a key
|
||||
if (
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
export function execute(
|
||||
code: string,
|
||||
payload: Record<string, unknown>,
|
||||
payload: Record<string, unknown>
|
||||
): unknown {
|
||||
try {
|
||||
// Create the function code that will be executed
|
||||
@@ -25,7 +25,7 @@ export function execute(
|
||||
throw new Error(
|
||||
`Error executing JavaScript template: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { validate } from './validate';
|
||||
export { execute } from './execute';
|
||||
export { validate } from './validate';
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('validate', () => {
|
||||
it('should reject function expression', () => {
|
||||
// Function expressions are not allowed - only arrow functions
|
||||
const result = validate(
|
||||
'(function(payload) { return { event: payload.name }; })',
|
||||
'(function(payload) { return { event: payload.name }; })'
|
||||
);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('arrow functions');
|
||||
@@ -83,7 +83,7 @@ describe('validate', () => {
|
||||
|
||||
it('should block while loops', () => {
|
||||
const result = validate(
|
||||
'(payload) => { while(true) {} return payload; }',
|
||||
'(payload) => { while(true) {} return payload; }'
|
||||
);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('Loops are not allowed');
|
||||
@@ -91,7 +91,7 @@ describe('validate', () => {
|
||||
|
||||
it('should block for loops', () => {
|
||||
const result = validate(
|
||||
'(payload) => { for(let i = 0; i < 10; i++) {} return payload; }',
|
||||
'(payload) => { for(let i = 0; i < 10; i++) {} return payload; }'
|
||||
);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('Loops are not allowed');
|
||||
@@ -99,7 +99,7 @@ describe('validate', () => {
|
||||
|
||||
it('should block try/catch', () => {
|
||||
const result = validate(
|
||||
'(payload) => { try { return payload; } catch(e) {} }',
|
||||
'(payload) => { try { return payload; } catch(e) {} }'
|
||||
);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('try/catch');
|
||||
@@ -107,7 +107,7 @@ describe('validate', () => {
|
||||
|
||||
it('should block async/await', () => {
|
||||
const result = validate(
|
||||
'async (payload) => { await something(); return payload; }',
|
||||
'async (payload) => { await something(); return payload; }'
|
||||
);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('async/await');
|
||||
@@ -133,7 +133,7 @@ describe('validate', () => {
|
||||
|
||||
it('should allow new Date()', () => {
|
||||
const result = validate(
|
||||
'(payload) => ({ timestamp: new Date().toISOString() })',
|
||||
'(payload) => ({ timestamp: new Date().toISOString() })'
|
||||
);
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
@@ -163,8 +163,8 @@ describe('execute', () => {
|
||||
device: 'desktop',
|
||||
os: 'Windows',
|
||||
browser: 'Chrome',
|
||||
longitude: -73.935242,
|
||||
latitude: 40.73061,
|
||||
longitude: -73.935_242,
|
||||
latitude: 40.730_61,
|
||||
createdAt: '2024-01-15T10:30:00Z',
|
||||
properties: {
|
||||
plan: 'premium',
|
||||
@@ -218,7 +218,7 @@ describe('execute', () => {
|
||||
const result = execute(code, basePayload);
|
||||
expect(result).toHaveProperty('timestamp');
|
||||
expect((result as { timestamp: string }).timestamp).toBe(
|
||||
'2024-01-15T10:30:00.000Z',
|
||||
'2024-01-15T10:30:00.000Z'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { parse } from '@babel/parser';
|
||||
|
||||
import {
|
||||
ALLOWED_GLOBALS,
|
||||
ALLOWED_INSTANCE_METHODS,
|
||||
ALLOWED_METHODS,
|
||||
} from './constants';
|
||||
import {
|
||||
collectDeclaredIdentifiers,
|
||||
isPropertyKey,
|
||||
walkNode,
|
||||
} from './ast-walker';
|
||||
import {
|
||||
ALLOWED_GLOBALS,
|
||||
ALLOWED_INSTANCE_METHODS,
|
||||
ALLOWED_METHODS,
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
* Validates that a JavaScript function is safe to execute
|
||||
@@ -222,11 +221,13 @@ export function validate(code: string): {
|
||||
const methodName = prop.name as string;
|
||||
|
||||
// Check if it's a call on an allowed global object
|
||||
if (ALLOWED_GLOBALS.has(objName) && ALLOWED_METHODS[objName]) {
|
||||
if (!ALLOWED_METHODS[objName].has(methodName)) {
|
||||
validationError = `Method '${objName}.${methodName}' is not allowed. Only safe methods are permitted.`;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
ALLOWED_GLOBALS.has(objName) &&
|
||||
ALLOWED_METHODS[objName] &&
|
||||
!ALLOWED_METHODS[objName].has(methodName)
|
||||
) {
|
||||
validationError = `Method '${objName}.${methodName}' is not allowed. Only safe methods are permitted.`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,13 +239,12 @@ export function validate(code: string): {
|
||||
// If calling on something other than an allowed global,
|
||||
// check if the method is in the allowed instance methods
|
||||
if (
|
||||
obj.type !== 'Identifier' ||
|
||||
!ALLOWED_GLOBALS.has(obj.name as string)
|
||||
(obj.type !== 'Identifier' ||
|
||||
!ALLOWED_GLOBALS.has(obj.name as string)) &&
|
||||
!ALLOWED_INSTANCE_METHODS.has(methodName)
|
||||
) {
|
||||
if (!ALLOWED_INSTANCE_METHODS.has(methodName)) {
|
||||
validationError = `Method '.${methodName}()' is not allowed. Only safe methods are permitted.`;
|
||||
return;
|
||||
}
|
||||
validationError = `Method '.${methodName}()' is not allowed. Only safe methods are permitted.`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user