Skip to content

Commit bd561fb

Browse files
authored
docs(helpers): clarify fake() does not execute javascript (#3945)
1 parent 15cb8fb commit bd561fb

3 files changed

Lines changed: 64 additions & 8 deletions

File tree

src/modules/helpers/eval.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ const REGEX_DOT_OR_BRACKET = /\.|\(/;
4646
* const airlineMethodName = fakeEval('airline.airline.name', faker); // 'bound airline'
4747
* ```
4848
*
49-
* It is NOT possible to access any values not passed as entrypoints.
49+
* It is not possible to execute arbitrary JavaScript through this method;
50+
* expressions can only resolve properties and methods reachable from the given entrypoints.
5051
*
5152
* This method will never return arrays, as it will pick a random element from them instead.
5253
*

src/modules/helpers/module.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,7 +1177,7 @@ export class HelpersModule extends SimpleHelpersModule {
11771177
* e.g. ``const address = `${faker.location.zipCode()} ${faker.location.city()}`;``
11781178
*
11791179
* This method is useful if you have to build a random string from a static, non-executable source
1180-
* (e.g. string coming from a user, stored in a database or a file).
1180+
* that you control (e.g. a template authored by a developer or stored in a database or file).
11811181
*
11821182
* It checks the given string for placeholders and replaces them by calling faker methods:
11831183
*
@@ -1200,7 +1200,11 @@ export class HelpersModule extends SimpleHelpersModule {
12001200
* const message = faker.helpers.fake('Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.');
12011201
* ```
12021202
*
1203-
* It is also NOT possible to use any non-faker methods or plain javascript in such patterns.
1203+
* The pattern is not evaluated as JavaScript: only faker methods can be called, and any
1204+
* parameters are parsed as JSON or plain strings. Nevertheless, it is possible for certain
1205+
* maliciously crafted patterns to use large amounts of memory or CPU time, so the pattern
1206+
* itself must always be from trusted input. Do not evaluate patterns provided by untrusted
1207+
* user input or external sources.
12041208
*
12051209
* @param pattern The pattern string that will get interpolated.
12061210
*
@@ -1226,7 +1230,7 @@ export class HelpersModule extends SimpleHelpersModule {
12261230
* e.g. ``const address = `${faker.location.zipCode()} ${faker.location.city()}`;``
12271231
*
12281232
* This method is useful if you have to build a random string from a static, non-executable source
1229-
* (e.g. string coming from a user, stored in a database or a file).
1233+
* that you control (e.g. a template authored by a developer or stored in a database or file).
12301234
*
12311235
* It checks the given string for placeholders and replaces them by calling faker methods:
12321236
*
@@ -1252,7 +1256,11 @@ export class HelpersModule extends SimpleHelpersModule {
12521256
* const message = faker.helpers.fake(['Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.']);
12531257
* ```
12541258
*
1255-
* It is also NOT possible to use any non-faker methods or plain javascript in such patterns.
1259+
* The pattern is not evaluated as JavaScript: only faker methods can be called, and any
1260+
* parameters are parsed as JSON or plain strings. Nevertheless, it is possible for certain
1261+
* maliciously crafted patterns to use large amounts of memory or CPU time, so the pattern
1262+
* itself must always be from trusted input. Do not evaluate patterns provided by untrusted
1263+
* user input or external sources.
12561264
*
12571265
* @param patterns The array to select a pattern from, that will then get interpolated. Must not be empty.
12581266
*
@@ -1272,7 +1280,7 @@ export class HelpersModule extends SimpleHelpersModule {
12721280
* e.g. ``const address = `${faker.location.zipCode()} ${faker.location.city()}`;``
12731281
*
12741282
* This method is useful if you have to build a random string from a static, non-executable source
1275-
* (e.g. string coming from a user, stored in a database or a file).
1283+
* that you control (e.g. a template authored by a developer or stored in a database or file).
12761284
*
12771285
* It checks the given string for placeholders and replaces them by calling faker methods:
12781286
*
@@ -1295,7 +1303,11 @@ export class HelpersModule extends SimpleHelpersModule {
12951303
* const message = faker.helpers.fake('Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.');
12961304
* ```
12971305
*
1298-
* It is also NOT possible to use any non-faker methods or plain javascript in such patterns.
1306+
* The pattern is not evaluated as JavaScript: only faker methods can be called, and any
1307+
* parameters are parsed as JSON or plain strings. Nevertheless, it is possible for certain
1308+
* maliciously crafted patterns to use large amounts of memory or CPU time, so the pattern
1309+
* itself must always be from trusted input. Do not evaluate patterns provided by untrusted
1310+
* user input or external sources.
12991311
*
13001312
* @param pattern The pattern string that will get interpolated. If an array is passed, a random element will be picked and interpolated.
13011313
*

test/modules/helpers-eval.spec.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { describe, expect, it, vi } from 'vitest';
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22
import { FakerError, faker } from '../../src';
33
import { fakeEval } from '../../src/modules/helpers/eval';
44

5+
// A hostile definition resolving to a function returning a function, mirroring the original GHSA-qxc2-j82w-r537 proof-of-concept.
6+
const noop = () => {};
7+
8+
const hostileDefinition = { evil: () => noop };
9+
510
describe('fakeEval()', () => {
611
it('does not allow empty string input', () => {
712
expect(() => fakeEval('', faker)).toThrow(
@@ -167,4 +172,42 @@ describe('fakeEval()', () => {
167172
new FakerError("Missing closing parenthesis in '(.iataCode'")
168173
);
169174
});
175+
176+
// Regression test for GHSA-qxc2-j82w-r537: fake()/fakeEval() must never evaluate the expression as JavaScript.
177+
// In particular, a (hostile) definition that resolves to a function must not expose the `Function` constructor via a `.constructor` chain, which would allow arbitrary code execution.
178+
// Functions are always invoked (never property-accessed), so the constructor chain can never reach `Function`.
179+
describe('does not allow arbitrary code execution (GHSA-qxc2-j82w-r537)', () => {
180+
const globalWithFlag = globalThis as Record<string, unknown>;
181+
182+
// Every payload is an observable side effect (setting the global flag), so we
183+
// notice if it ever executes instead of a silent expression like `return 1`.
184+
const attacks = [
185+
'do.evil.constructor(globalThis.__fakerPwned = true)()',
186+
'do.evil().constructor(globalThis.__fakerPwned = true)()',
187+
'do.evil.constructor.constructor(globalThis.__fakerPwned = true)',
188+
'do.evil.constructor.constructor(globalThis.__fakerPwned = true)()',
189+
'string.constructor.constructor(globalThis.__fakerPwned = true)()',
190+
];
191+
192+
beforeEach(() => {
193+
globalWithFlag.__fakerPwned = false;
194+
faker.definitions.raw.do = hostileDefinition;
195+
});
196+
197+
afterEach(() => {
198+
delete faker.definitions.raw.do;
199+
delete globalWithFlag.__fakerPwned;
200+
});
201+
202+
it.each(attacks)('rejects %s via fakeEval()', (attack) => {
203+
// The expression must always be rejected with the library's own error type, never evaluated as JavaScript.
204+
expect(() => fakeEval(attack, faker)).toThrow(FakerError);
205+
expect(globalWithFlag.__fakerPwned, attack).toBe(false);
206+
});
207+
208+
it.each(attacks)('rejects %s via helpers.fake()', (attack) => {
209+
expect(() => faker.helpers.fake(`{{${attack}}}`)).toThrow(FakerError);
210+
expect(globalWithFlag.__fakerPwned, attack).toBe(false);
211+
});
212+
});
170213
});

0 commit comments

Comments
 (0)