@@ -2,6 +2,7 @@ import { Tournament } from '@n8n/tournament';
22
33import {
44 DollarSignValidator ,
5+ FunctionThisSanitizer ,
56 PrototypeSanitizer ,
67 sanitizer ,
78 DOLLAR_SIGN_ERROR ,
@@ -14,7 +15,7 @@ const tournament = new Tournament(
1415 undefined ,
1516 undefined ,
1617 {
17- before : [ ] ,
18+ before : [ FunctionThisSanitizer ] ,
1819 after : [ PrototypeSanitizer , DollarSignValidator ] ,
1920 } ,
2021) ;
@@ -226,3 +227,141 @@ describe('PrototypeSanitizer', () => {
226227 } ) ;
227228 } ) ;
228229} ) ;
230+
231+ describe ( 'FunctionThisSanitizer' , ( ) => {
232+ describe ( 'call expression where callee is function expression' , ( ) => {
233+ it ( 'should transform call expression' , ( ) => {
234+ const result = tournament . execute ( '{{ (function() { return this.process; })() }}' , {
235+ __sanitize : sanitizer ,
236+ } ) ;
237+ expect ( result ) . toEqual ( { } ) ;
238+ } ) ;
239+
240+ it ( 'should handle recursive call expression' , ( ) => {
241+ const result = tournament . execute (
242+ '{{ (function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1); })(5) }}' ,
243+ {
244+ __sanitize : sanitizer ,
245+ } ,
246+ ) ;
247+ expect ( result ) . toBe ( 120 ) ;
248+ } ) ;
249+
250+ it ( 'should not expose process.env through named function' , ( ) => {
251+ const result = tournament . execute ( '{{ (function test(){ return this.process.env })() }}' , {
252+ __sanitize : sanitizer ,
253+ } ) ;
254+ expect ( result ) . toBe ( undefined ) ;
255+ } ) ;
256+
257+ it ( 'should still allow access to workflow data via variables' , ( ) => {
258+ const result = tournament . execute ( '{{ (function() { return $json.value; })() }}' , {
259+ __sanitize : sanitizer ,
260+ $json : { value : 'test-value' } ,
261+ } ) ;
262+ expect ( result ) . toBe ( 'test-value' ) ;
263+ } ) ;
264+
265+ it ( 'should handle nested call expression' , ( ) => {
266+ const result = tournament . execute (
267+ '{{ (function() { return (function() { return this.process; })(); })() }}' ,
268+ {
269+ __sanitize : sanitizer ,
270+ } ,
271+ ) ;
272+ expect ( result ) . toEqual ( { } ) ;
273+ } ) ;
274+
275+ it ( 'should handle nested recursive call expression' , ( ) => {
276+ const result = tournament . execute (
277+ '{{ (function() { return (function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1); })(5); })() }}' ,
278+ {
279+ __sanitize : sanitizer ,
280+ } ,
281+ ) ;
282+ expect ( result ) . toBe ( 120 ) ;
283+ } ) ;
284+ } ) ;
285+
286+ describe ( 'function expression' , ( ) => {
287+ it ( 'should transform function expression' , ( ) => {
288+ const result = tournament . execute ( '{{ [1].map(function() { return this.process; }) }}' , {
289+ __sanitize : sanitizer ,
290+ } ) ;
291+ expect ( result ) . toEqual ( [ { } ] ) ;
292+ } ) ;
293+
294+ it ( 'should handle recursive function expression' , ( ) => {
295+ const result = tournament . execute (
296+ '{{ [1, 2, 3, 4, 5].map(function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1); }) }}' ,
297+ {
298+ __sanitize : sanitizer ,
299+ } ,
300+ ) ;
301+ expect ( result ) . toEqual ( [ 1 , 2 , 6 , 24 , 120 ] ) ;
302+ } ) ;
303+
304+ it ( 'should handle nested function expression' , ( ) => {
305+ const result = tournament . execute (
306+ '{{ [1, 2, 3].map(function(n) { return function() { return n * 2; }; }).map(function(fn) { return fn(); }) }}' ,
307+ {
308+ __sanitize : sanitizer ,
309+ } ,
310+ ) ;
311+ expect ( result ) . toEqual ( [ 2 , 4 , 6 ] ) ;
312+ } ) ;
313+
314+ it ( 'should handle nested recursion' , ( ) => {
315+ const result = tournament . execute (
316+ '{{ (function fibonacci(n) { return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2); })(7) }}' ,
317+ {
318+ __sanitize : sanitizer ,
319+ } ,
320+ ) ;
321+ expect ( result ) . toBe ( 13 ) ;
322+ } ) ;
323+ } ) ;
324+
325+ describe ( 'process.env security' , ( ) => {
326+ it ( 'should bind function expressions to empty process object' , ( ) => {
327+ const processResult = tournament . execute ( '{{ (function(){ return this.process })() }}' , {
328+ __sanitize : sanitizer ,
329+ } ) ;
330+ expect ( processResult ) . toEqual ( { } ) ;
331+ expect ( Object . keys ( processResult as object ) ) . toEqual ( [ ] ) ;
332+
333+ const envResult = tournament . execute ( '{{ (function(){ return this.process.env })() }}' , {
334+ __sanitize : sanitizer ,
335+ } ) ;
336+ expect ( envResult ) . toBe ( undefined ) ;
337+ } ) ;
338+
339+ it ( 'should block process.env in nested functions' , ( ) => {
340+ const result = tournament . execute (
341+ '{{ (function outer(){ return (function inner(){ return this.process.env })(); })() }}' ,
342+ {
343+ __sanitize : sanitizer ,
344+ } ,
345+ ) ;
346+ expect ( result ) . toBe ( undefined ) ;
347+ } ) ;
348+
349+ it ( 'should block process.env in callbacks' , ( ) => {
350+ const result = tournament . execute (
351+ '{{ [1].map(function(){ return this.process.env; })[0] }}' ,
352+ {
353+ __sanitize : sanitizer ,
354+ } ,
355+ ) ;
356+ expect ( result ) . toBe ( undefined ) ;
357+ } ) ;
358+
359+ it ( 'should still allow access to workflow variables' , ( ) => {
360+ const result = tournament . execute ( '{{ (function(){ return $json.value })() }}' , {
361+ __sanitize : sanitizer ,
362+ $json : { value : 'workflow-data' } ,
363+ } ) ;
364+ expect ( result ) . toBe ( 'workflow-data' ) ;
365+ } ) ;
366+ } ) ;
367+ } ) ;
0 commit comments