@memberjunction/global 6.2.0-edge.6 → 6.2.0-edge.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +8 -4
  2. package/dist/ClassFactory.d.ts +30 -0
  3. package/dist/ClassFactory.d.ts.map +1 -1
  4. package/dist/ClassFactory.js +69 -5
  5. package/dist/ClassFactory.js.map +1 -1
  6. package/dist/SafeExpressionEvaluator.d.ts +93 -109
  7. package/dist/SafeExpressionEvaluator.d.ts.map +1 -1
  8. package/dist/SafeExpressionEvaluator.js +494 -346
  9. package/dist/SafeExpressionEvaluator.js.map +1 -1
  10. package/dist/__tests__/ClassFactory.collision.test.js +110 -0
  11. package/dist/__tests__/ClassFactory.collision.test.js.map +1 -1
  12. package/dist/__tests__/ClassFactory.test.js +21 -0
  13. package/dist/__tests__/ClassFactory.test.js.map +1 -1
  14. package/dist/__tests__/FieldTransformEngine.screen.test.d.ts +2 -0
  15. package/dist/__tests__/FieldTransformEngine.screen.test.d.ts.map +1 -0
  16. package/dist/__tests__/FieldTransformEngine.screen.test.js +41 -0
  17. package/dist/__tests__/FieldTransformEngine.screen.test.js.map +1 -0
  18. package/dist/__tests__/FieldTransformEngine.security.test.d.ts +2 -0
  19. package/dist/__tests__/FieldTransformEngine.security.test.d.ts.map +1 -0
  20. package/dist/__tests__/FieldTransformEngine.security.test.js +74 -0
  21. package/dist/__tests__/FieldTransformEngine.security.test.js.map +1 -0
  22. package/dist/__tests__/PlatformStringCompliance.baseline.d.ts +10 -0
  23. package/dist/__tests__/PlatformStringCompliance.baseline.d.ts.map +1 -0
  24. package/dist/__tests__/PlatformStringCompliance.baseline.js +12 -0
  25. package/dist/__tests__/PlatformStringCompliance.baseline.js.map +1 -0
  26. package/dist/__tests__/PlatformStringCompliance.test.d.ts +8 -0
  27. package/dist/__tests__/PlatformStringCompliance.test.d.ts.map +1 -0
  28. package/dist/__tests__/PlatformStringCompliance.test.js +152 -0
  29. package/dist/__tests__/PlatformStringCompliance.test.js.map +1 -0
  30. package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.d.ts +2 -0
  31. package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.d.ts.map +1 -0
  32. package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.js +200 -0
  33. package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.js.map +1 -0
  34. package/dist/__tests__/SafeExpressionEvaluator.security.test.js +11 -0
  35. package/dist/__tests__/SafeExpressionEvaluator.security.test.js.map +1 -1
  36. package/dist/__tests__/SafeExpressionEvaluator.test.js +4 -2
  37. package/dist/__tests__/SafeExpressionEvaluator.test.js.map +1 -1
  38. package/dist/__tests__/UUIDCompliance.test.js +2 -3
  39. package/dist/__tests__/UUIDCompliance.test.js.map +1 -1
  40. package/dist/fieldRules/FieldTransformEngine.d.ts +12 -1
  41. package/dist/fieldRules/FieldTransformEngine.d.ts.map +1 -1
  42. package/dist/fieldRules/FieldTransformEngine.js +21 -8
  43. package/dist/fieldRules/FieldTransformEngine.js.map +1 -1
  44. package/dist/fieldRules/SafeTransformAstEvaluator.d.ts +16 -0
  45. package/dist/fieldRules/SafeTransformAstEvaluator.d.ts.map +1 -0
  46. package/dist/fieldRules/SafeTransformAstEvaluator.js +797 -0
  47. package/dist/fieldRules/SafeTransformAstEvaluator.js.map +1 -0
  48. package/dist/fieldRules/index.d.ts +1 -0
  49. package/dist/fieldRules/index.d.ts.map +1 -1
  50. package/dist/fieldRules/index.js +1 -0
  51. package/dist/fieldRules/index.js.map +1 -1
  52. package/dist/util/BinaryEncoding.d.ts +2 -0
  53. package/dist/util/BinaryEncoding.d.ts.map +1 -1
  54. package/dist/util/BinaryEncoding.js +3 -5
  55. package/dist/util/BinaryEncoding.js.map +1 -1
  56. package/package.json +1 -1
@@ -0,0 +1,797 @@
1
+ import { parse } from 'acorn';
2
+ import { SAFE_GLOBAL_NAMESPACE_METHODS } from '../SafeExpressionEvaluator.js';
3
+ const MAX_EXPRESSION_LENGTH = 2000;
4
+ const MAX_AST_DEPTH = 64;
5
+ const MAX_PAD_LENGTH = 10_000;
6
+ const MAX_FLAT_DEPTH = 5;
7
+ /** Sentinel propagated inside a `ChainExpression` when an `optional` link hits null/undefined. */
8
+ const OPTIONAL_SHORT_CIRCUIT = Symbol('OPTIONAL_SHORT_CIRCUIT');
9
+ const DANGEROUS_IDENTIFIERS = new Set([
10
+ 'eval', 'Function', 'globalThis', 'global', 'window', 'document', 'process',
11
+ 'require', 'module', 'exports', '__dirname', '__filename', 'self', 'top',
12
+ 'parent', 'frames', 'Reflect', 'Proxy', 'WebAssembly', 'Atomics',
13
+ 'SharedArrayBuffer', 'import', 'arguments', 'constructor', 'prototype', '__proto__',
14
+ 'fetch', 'Buffer', 'XMLHttpRequest', 'WebSocket', 'Worker', 'SharedWorker',
15
+ 'ServiceWorker', 'BroadcastChannel', 'MessageChannel', 'setTimeout', 'setInterval',
16
+ 'setImmediate', 'queueMicrotask', 'crypto', 'navigator', 'location', 'history',
17
+ 'localStorage', 'sessionStorage', 'indexedDB', 'caches', 'cookieStore', 'Deno', 'Bun',
18
+ ]);
19
+ const DANGEROUS_PROPERTY_NAMES = new Set([
20
+ 'constructor', '__proto__', 'prototype',
21
+ ]);
22
+ const ALLOWED_BINARY_OPERATORS = new Set([
23
+ '==', '!=', '===', '!==', '<', '<=', '>', '>=', '+', '-', '*', '/', '%', '**',
24
+ ]);
25
+ const ALLOWED_LOGICAL_OPERATORS = new Set(['&&', '||', '??']);
26
+ const ALLOWED_UNARY_OPERATORS = new Set(['!', '-', '+', 'typeof']);
27
+ const SAFE_TRANSFORM_CALLABLES = new Map([
28
+ ['parseInt', (s, radix) => Number.parseInt(String(s), radix === undefined ? undefined : Number(radix))],
29
+ ['parseFloat', (s) => Number.parseFloat(String(s))],
30
+ ['isNaN', (v) => isNaN(Number(v))],
31
+ ['isFinite', (v) => isFinite(Number(v))],
32
+ ['Number', (v) => (v === undefined ? 0 : Number(v))],
33
+ ['String', (v) => (v === undefined ? '' : String(v))],
34
+ ['Boolean', (v) => Boolean(v)],
35
+ ['encodeURIComponent', (v) => encodeURIComponent(String(v))],
36
+ ['decodeURIComponent', (v) => decodeURIComponent(String(v))],
37
+ ]);
38
+ const SAFE_CALLBACK_IDENTIFIERS = new Set([
39
+ 'Boolean', 'Number', 'String', 'isNaN', 'isFinite',
40
+ ]);
41
+ const HIGHER_ORDER_ARRAY_METHODS = new Set([
42
+ 'some', 'every', 'find', 'findIndex', 'filter', 'map', 'reduce', 'flatMap',
43
+ ]);
44
+ const SAFE_STRING_METHODS = new Set([
45
+ 'includes', 'startsWith', 'endsWith', 'indexOf', 'lastIndexOf',
46
+ 'toLowerCase', 'toUpperCase', 'trim', 'trimStart', 'trimEnd',
47
+ 'slice', 'substring', 'split', 'replace', 'replaceAll',
48
+ 'padStart', 'padEnd', 'charAt', 'charCodeAt', 'at', 'concat',
49
+ 'toString', 'valueOf',
50
+ ]);
51
+ const SAFE_ARRAY_VALUE_METHODS = new Set([
52
+ 'includes', 'indexOf', 'lastIndexOf', 'join', 'slice', 'concat', 'flat', 'at', 'toString',
53
+ ]);
54
+ const SAFE_NUMBER_METHODS = new Set(['toFixed', 'toPrecision', 'toString', 'valueOf']);
55
+ const SAFE_BOOLEAN_METHODS = new Set(['toString', 'valueOf']);
56
+ const SAFE_DATE_METHODS = new Set([
57
+ 'toISOString', 'getTime', 'getFullYear', 'getUTCFullYear',
58
+ 'getMonth', 'getUTCMonth', 'getDate', 'getUTCDate', 'toString', 'valueOf',
59
+ ]);
60
+ const ALL_SAFE_INSTANCE_METHODS = new Set([
61
+ ...SAFE_STRING_METHODS,
62
+ ...SAFE_ARRAY_VALUE_METHODS,
63
+ ...HIGHER_ORDER_ARRAY_METHODS,
64
+ ...SAFE_NUMBER_METHODS,
65
+ ...SAFE_BOOLEAN_METHODS,
66
+ ...SAFE_DATE_METHODS,
67
+ ]);
68
+ /**
69
+ * Parses, structurally validates against an AST allowlist, and compiles a transform/formula
70
+ * expression into a pure AST-interpreted {@link CompiledExpression} closure.
71
+ *
72
+ * Security invariants:
73
+ * - **Never invokes `new Function` or `eval`** — eliminates dynamic JS compilation, CSP `unsafe-eval`
74
+ * dependence, and ambient host-global reachability (`process`, `globalThis`, `fetch`, `Buffer`, …).
75
+ * - **Own-property-only object reads** — prototype-chain climbs (`constructor`, `__proto__`,
76
+ * `prototype`) are blocked at both compile time and runtime.
77
+ * - **Intrinsic-bound method dispatch** — instance methods dispatch only against intrinsic
78
+ * `String` / `Array` / `Number` / `Boolean` / `Date` receivers, never arbitrary functions stored
79
+ * on untrusted data records.
80
+ */
81
+ export function CompileTransformExpression(expression) {
82
+ if (typeof expression !== 'string' || expression.trim().length === 0) {
83
+ return new Error('Transform expression must be a non-empty string');
84
+ }
85
+ if (expression.length > MAX_EXPRESSION_LENGTH) {
86
+ return new Error(`Transform expression exceeds maximum length of ${MAX_EXPRESSION_LENGTH} characters`);
87
+ }
88
+ const parsed = parseSingleExpression(expression);
89
+ if (parsed instanceof Error) {
90
+ return parsed;
91
+ }
92
+ const policyError = validateNode(parsed, 0);
93
+ if (policyError) {
94
+ return new Error(`Transform expression rejected by the safe-expression allowlist: ${policyError}`);
95
+ }
96
+ return (value, fields) => {
97
+ const safeFields = fields && typeof fields === 'object' ? fields : {};
98
+ const rootBindings = new Map([
99
+ ['value', value],
100
+ ['fields', safeFields],
101
+ ['undefined', undefined],
102
+ ['NaN', NaN],
103
+ ['Infinity', Infinity],
104
+ ]);
105
+ const rootScope = { bindings: rootBindings, parent: null };
106
+ const out = evaluateNode(parsed, rootScope, 0);
107
+ return out === OPTIONAL_SHORT_CIRCUIT ? undefined : out;
108
+ };
109
+ }
110
+ function parseSingleExpression(expression) {
111
+ let program;
112
+ try {
113
+ program = parse(expression, { ecmaVersion: 2022 });
114
+ }
115
+ catch (err) {
116
+ // Bare object literals like `{ a: 1 }` parse as a labeled BlockStatement unless parenthesized.
117
+ if (expression.trim().startsWith('{')) {
118
+ return parseObjectLiteralFallback(expression, err);
119
+ }
120
+ return err instanceof Error ? err : new Error(String(err));
121
+ }
122
+ if (program.body.length === 1 && program.body[0].type === 'ExpressionStatement') {
123
+ return program.body[0].expression;
124
+ }
125
+ if (expression.trim().startsWith('{') && program.body.length === 1 && program.body[0].type === 'BlockStatement') {
126
+ return parseObjectLiteralFallback(expression, new Error('Block statements are not allowed'));
127
+ }
128
+ return new Error('Transform expression must be a single expression — statements, blocks, and semicolons are not allowed');
129
+ }
130
+ function parseObjectLiteralFallback(expression, originalError) {
131
+ try {
132
+ const wrapped = parse(`(\n${expression}\n)`, { ecmaVersion: 2022 });
133
+ if (wrapped.body.length === 1 &&
134
+ wrapped.body[0].type === 'ExpressionStatement' &&
135
+ wrapped.body[0].expression.type === 'ObjectExpression') {
136
+ return wrapped.body[0].expression;
137
+ }
138
+ }
139
+ catch {
140
+ // Fall through to original error
141
+ }
142
+ return originalError instanceof Error ? originalError : new Error(String(originalError));
143
+ }
144
+ function forbidden(detail) {
145
+ return `Expression contains a forbidden construct: ${detail}`;
146
+ }
147
+ function validateNode(node, depth) {
148
+ if (depth > MAX_AST_DEPTH) {
149
+ return forbidden(`expression nesting exceeds maximum depth of ${MAX_AST_DEPTH}`);
150
+ }
151
+ const next = depth + 1;
152
+ switch (node.type) {
153
+ case 'Literal': {
154
+ const lit = node;
155
+ if (lit.value instanceof RegExp || 'regex' in lit) {
156
+ return forbidden('regular expression literals are not allowed');
157
+ }
158
+ if ('bigint' in lit) {
159
+ return forbidden('BigInt literals are not allowed');
160
+ }
161
+ return null;
162
+ }
163
+ case 'TemplateLiteral': {
164
+ const tpl = node;
165
+ for (const expr of tpl.expressions) {
166
+ const err = validateNode(expr, next);
167
+ if (err)
168
+ return err;
169
+ }
170
+ return null;
171
+ }
172
+ case 'Identifier': {
173
+ const name = node.name;
174
+ if (DANGEROUS_IDENTIFIERS.has(name)) {
175
+ return forbidden(`the identifier "${name}"`);
176
+ }
177
+ return null;
178
+ }
179
+ case 'ChainExpression':
180
+ return validateNode(node.expression, next);
181
+ case 'MemberExpression':
182
+ return validateMemberExpression(node, next);
183
+ case 'CallExpression':
184
+ return validateCallExpression(node, next);
185
+ case 'ArrowFunctionExpression':
186
+ return validateArrowFunction(node, next);
187
+ case 'BinaryExpression': {
188
+ const bin = node;
189
+ if (!ALLOWED_BINARY_OPERATORS.has(bin.operator)) {
190
+ return forbidden(`the operator "${bin.operator}"`);
191
+ }
192
+ return validateNode(bin.left, next) ?? validateNode(bin.right, next);
193
+ }
194
+ case 'LogicalExpression': {
195
+ const log = node;
196
+ if (!ALLOWED_LOGICAL_OPERATORS.has(log.operator)) {
197
+ return forbidden(`the logical operator "${log.operator}"`);
198
+ }
199
+ return validateNode(log.left, next) ?? validateNode(log.right, next);
200
+ }
201
+ case 'UnaryExpression': {
202
+ const un = node;
203
+ if (!ALLOWED_UNARY_OPERATORS.has(un.operator)) {
204
+ return forbidden(`the unary operator "${un.operator}"`);
205
+ }
206
+ return validateNode(un.argument, next);
207
+ }
208
+ case 'ConditionalExpression': {
209
+ const cond = node;
210
+ return validateNode(cond.test, next)
211
+ ?? validateNode(cond.consequent, next)
212
+ ?? validateNode(cond.alternate, next);
213
+ }
214
+ case 'ArrayExpression': {
215
+ const arr = node;
216
+ for (const el of arr.elements) {
217
+ if (!el)
218
+ continue;
219
+ if (el.type === 'SpreadElement') {
220
+ return forbidden('spread elements are not allowed');
221
+ }
222
+ const err = validateNode(el, next);
223
+ if (err)
224
+ return err;
225
+ }
226
+ return null;
227
+ }
228
+ case 'ObjectExpression':
229
+ return validateObjectExpression(node, next);
230
+ default:
231
+ return forbidden(`${node.type} is not allowed`);
232
+ }
233
+ }
234
+ function validateObjectExpression(node, depth) {
235
+ for (const prop of node.properties) {
236
+ if (prop.type === 'SpreadElement') {
237
+ return forbidden('object spread elements are not allowed');
238
+ }
239
+ const p = prop;
240
+ if (p.kind !== 'init' || p.method || p.computed) {
241
+ return forbidden('only plain non-computed data properties are allowed in object literals');
242
+ }
243
+ let keyName;
244
+ if (p.key.type === 'Identifier') {
245
+ keyName = p.key.name;
246
+ }
247
+ else if (p.key.type === 'Literal' && (typeof p.key.value === 'string' || typeof p.key.value === 'number')) {
248
+ keyName = String(p.key.value);
249
+ }
250
+ else {
251
+ return forbidden('object property keys must be identifiers or string/number literals');
252
+ }
253
+ if (DANGEROUS_PROPERTY_NAMES.has(keyName)) {
254
+ return forbidden(`object property key "${keyName}"`);
255
+ }
256
+ const err = validateNode(p.value, depth);
257
+ if (err)
258
+ return err;
259
+ }
260
+ return null;
261
+ }
262
+ function validateMemberExpression(node, depth) {
263
+ if (node.property.type === 'PrivateIdentifier') {
264
+ return forbidden('private fields are not allowed');
265
+ }
266
+ if (node.computed) {
267
+ if (node.property.type !== 'Literal') {
268
+ return forbidden('computed member access must use a literal index/key');
269
+ }
270
+ const val = node.property.value;
271
+ if (typeof val === 'string') {
272
+ if (DANGEROUS_PROPERTY_NAMES.has(val)) {
273
+ return forbidden(`access to "${val}"`);
274
+ }
275
+ }
276
+ else if (typeof val !== 'number') {
277
+ return forbidden('computed member access must use a string or numeric literal');
278
+ }
279
+ }
280
+ else {
281
+ const name = node.property.name;
282
+ if (DANGEROUS_PROPERTY_NAMES.has(name)) {
283
+ return forbidden(`access to "${name}"`);
284
+ }
285
+ }
286
+ return validateNode(node.object, depth);
287
+ }
288
+ function validateCallExpression(node, depth) {
289
+ const callee = node.callee;
290
+ if (callee.type === 'Identifier') {
291
+ const name = callee.name;
292
+ if (!SAFE_TRANSFORM_CALLABLES.has(name)) {
293
+ return forbidden(`calling "${name}" — only safe methods and safe global functions may be invoked`);
294
+ }
295
+ return validateCallArguments(node, depth);
296
+ }
297
+ if (callee.type !== 'MemberExpression') {
298
+ return forbidden('only method calls on the safe-method allowlist may be invoked');
299
+ }
300
+ const member = callee;
301
+ if (member.computed || member.property.type !== 'Identifier') {
302
+ return forbidden('methods must be called by a dotted, non-computed name');
303
+ }
304
+ const method = member.property.name;
305
+ const isNamespaceMethod = member.object.type === 'Identifier' &&
306
+ SAFE_GLOBAL_NAMESPACE_METHODS.get(member.object.name)?.has(method) === true;
307
+ if (!isNamespaceMethod && !ALL_SAFE_INSTANCE_METHODS.has(method)) {
308
+ return forbidden(`the method "${method}" is not on the safe-method allowlist`);
309
+ }
310
+ if (!isNamespaceMethod && HIGHER_ORDER_ARRAY_METHODS.has(method)) {
311
+ const cb = node.arguments[0];
312
+ if (!cb) {
313
+ return forbidden(`"${method}" requires a callback argument`);
314
+ }
315
+ const isAllowedCallback = cb.type === 'ArrowFunctionExpression' ||
316
+ (cb.type === 'Identifier' && SAFE_CALLBACK_IDENTIFIERS.has(cb.name));
317
+ if (!isAllowedCallback) {
318
+ return forbidden(`"${method}" callback must be an arrow function or safe coercion (Boolean/Number/String)`);
319
+ }
320
+ }
321
+ const receiverErr = validateNode(member.object, depth);
322
+ if (receiverErr)
323
+ return receiverErr;
324
+ return validateCallArguments(node, depth);
325
+ }
326
+ function validateCallArguments(node, depth) {
327
+ for (const arg of node.arguments) {
328
+ if (arg.type === 'SpreadElement') {
329
+ return forbidden('spread arguments are not allowed');
330
+ }
331
+ const err = validateNode(arg, depth);
332
+ if (err)
333
+ return err;
334
+ }
335
+ return null;
336
+ }
337
+ function validateArrowFunction(node, depth) {
338
+ if (node.async || node.generator) {
339
+ return forbidden('async/generator functions are not allowed');
340
+ }
341
+ for (const param of node.params) {
342
+ if (param.type !== 'Identifier') {
343
+ return forbidden('arrow-function parameters must be simple identifiers');
344
+ }
345
+ const name = param.name;
346
+ if (DANGEROUS_IDENTIFIERS.has(name)) {
347
+ return forbidden(`the parameter "${name}"`);
348
+ }
349
+ }
350
+ if (node.body.type === 'BlockStatement') {
351
+ return forbidden('arrow-function bodies must be an expression, not a block');
352
+ }
353
+ return validateNode(node.body, depth);
354
+ }
355
+ // ─── Runtime AST Interpreter ─────────────────────────────────────────────────
356
+ function evaluateNode(node, scope, depth) {
357
+ if (depth > MAX_AST_DEPTH) {
358
+ throw new RangeError(`Transform expression evaluation exceeded maximum depth of ${MAX_AST_DEPTH}`);
359
+ }
360
+ const next = depth + 1;
361
+ switch (node.type) {
362
+ case 'Literal':
363
+ return node.value;
364
+ case 'TemplateLiteral': {
365
+ const tpl = node;
366
+ let out = '';
367
+ for (let i = 0; i < tpl.quasis.length; i++) {
368
+ out += tpl.quasis[i].value.cooked ?? tpl.quasis[i].value.raw;
369
+ if (i < tpl.expressions.length) {
370
+ const part = unwrapOptional(evaluateNode(tpl.expressions[i], scope, next));
371
+ out += String(part);
372
+ }
373
+ }
374
+ return out;
375
+ }
376
+ case 'Identifier':
377
+ return resolveIdentifier(node.name, scope, true);
378
+ case 'ChainExpression': {
379
+ const inner = evaluateNode(node.expression, scope, next);
380
+ return inner === OPTIONAL_SHORT_CIRCUIT ? undefined : inner;
381
+ }
382
+ case 'MemberExpression':
383
+ return evaluateMemberExpression(node, scope, next);
384
+ case 'CallExpression':
385
+ return evaluateCallExpression(node, scope, next);
386
+ case 'ArrowFunctionExpression':
387
+ return evaluateArrowFunction(node, scope, next);
388
+ case 'BinaryExpression':
389
+ return evaluateBinaryExpression(node, scope, next);
390
+ case 'LogicalExpression':
391
+ return evaluateLogicalExpression(node, scope, next);
392
+ case 'UnaryExpression':
393
+ return evaluateUnaryExpression(node, scope, next);
394
+ case 'ConditionalExpression': {
395
+ const cond = node;
396
+ const test = unwrapOptional(evaluateNode(cond.test, scope, next));
397
+ return test
398
+ ? unwrapOptional(evaluateNode(cond.consequent, scope, next))
399
+ : unwrapOptional(evaluateNode(cond.alternate, scope, next));
400
+ }
401
+ case 'ArrayExpression': {
402
+ const arr = node;
403
+ return arr.elements.map((el) => (el ? unwrapOptional(evaluateNode(el, scope, next)) : undefined));
404
+ }
405
+ case 'ObjectExpression': {
406
+ const obj = node;
407
+ const result = {};
408
+ for (const prop of obj.properties) {
409
+ const p = prop;
410
+ const key = p.key.type === 'Identifier'
411
+ ? p.key.name
412
+ : String(p.key.value);
413
+ if (!DANGEROUS_PROPERTY_NAMES.has(key)) {
414
+ result[key] = unwrapOptional(evaluateNode(p.value, scope, next));
415
+ }
416
+ }
417
+ return result;
418
+ }
419
+ default:
420
+ throw new Error(`Unsupported AST node type: ${node.type}`);
421
+ }
422
+ }
423
+ function unwrapOptional(val) {
424
+ return val === OPTIONAL_SHORT_CIRCUIT ? undefined : val;
425
+ }
426
+ function hasLexicalBinding(name, scope) {
427
+ let cur = scope;
428
+ while (cur) {
429
+ if (cur.bindings.has(name))
430
+ return true;
431
+ cur = cur.parent;
432
+ }
433
+ return false;
434
+ }
435
+ function resolveIdentifier(name, scope, throwIfUnbound) {
436
+ let cur = scope;
437
+ while (cur) {
438
+ if (cur.bindings.has(name)) {
439
+ return cur.bindings.get(name);
440
+ }
441
+ cur = cur.parent;
442
+ }
443
+ const callable = SAFE_TRANSFORM_CALLABLES.get(name);
444
+ if (callable) {
445
+ return callable;
446
+ }
447
+ if (throwIfUnbound) {
448
+ throw new ReferenceError(`${name} is not defined`);
449
+ }
450
+ return undefined;
451
+ }
452
+ function evaluateMemberExpression(node, scope, depth) {
453
+ const target = evaluateNode(node.object, scope, depth);
454
+ if (target === OPTIONAL_SHORT_CIRCUIT) {
455
+ return OPTIONAL_SHORT_CIRCUIT;
456
+ }
457
+ if (node.optional && (target === null || target === undefined)) {
458
+ return OPTIONAL_SHORT_CIRCUIT;
459
+ }
460
+ const key = node.computed
461
+ ? node.property.value
462
+ : node.property.name;
463
+ return readSafeProperty(target, key);
464
+ }
465
+ function readSafeProperty(target, key) {
466
+ if (target === null || target === undefined) {
467
+ throw new TypeError(`Cannot read properties of ${String(target)} (reading '${String(key)}')`);
468
+ }
469
+ const keyStr = String(key);
470
+ if (DANGEROUS_PROPERTY_NAMES.has(keyStr)) {
471
+ throw new Error(`Access to property "${keyStr}" is forbidden`);
472
+ }
473
+ if (typeof target === 'string') {
474
+ if (key === 'length')
475
+ return target.length;
476
+ const idx = typeof key === 'number' ? key : (/^\d+$/.test(keyStr) ? Number(keyStr) : -1);
477
+ return Number.isInteger(idx) && idx >= 0 ? target[idx] : undefined;
478
+ }
479
+ if (Array.isArray(target)) {
480
+ if (key === 'length')
481
+ return target.length;
482
+ const idx = typeof key === 'number' ? key : (/^\d+$/.test(keyStr) ? Number(keyStr) : -1);
483
+ return Number.isInteger(idx) && idx >= 0 ? target[idx] : undefined;
484
+ }
485
+ if (typeof target === 'object') {
486
+ return Object.prototype.hasOwnProperty.call(target, keyStr)
487
+ ? target[keyStr]
488
+ : undefined;
489
+ }
490
+ return undefined;
491
+ }
492
+ function evaluateCallExpression(node, scope, depth) {
493
+ const callee = node.callee;
494
+ if (callee.type === 'Identifier') {
495
+ const name = callee.name;
496
+ const fn = SAFE_TRANSFORM_CALLABLES.get(name);
497
+ if (!fn) {
498
+ throw new TypeError(`${name} is not a safe callable function`);
499
+ }
500
+ const args = node.arguments.map((a) => unwrapOptional(evaluateNode(a, scope, depth)));
501
+ return fn(...args);
502
+ }
503
+ const member = callee;
504
+ const method = member.property.name;
505
+ // Namespace static method (Math.abs, JSON.parse, Object.keys, Array.isArray, Number.isFinite, Date.now)
506
+ if (member.object.type === 'Identifier' &&
507
+ !hasLexicalBinding(member.object.name, scope) &&
508
+ SAFE_GLOBAL_NAMESPACE_METHODS.get(member.object.name)?.has(method) === true) {
509
+ const nsName = member.object.name;
510
+ const args = node.arguments.map((a) => unwrapOptional(evaluateNode(a, scope, depth)));
511
+ return invokeNamespaceMethod(nsName, method, args);
512
+ }
513
+ const receiver = evaluateNode(member.object, scope, depth);
514
+ if (receiver === OPTIONAL_SHORT_CIRCUIT) {
515
+ return OPTIONAL_SHORT_CIRCUIT;
516
+ }
517
+ if ((member.optional || node.optional) && (receiver === null || receiver === undefined)) {
518
+ return OPTIONAL_SHORT_CIRCUIT;
519
+ }
520
+ if (receiver === null || receiver === undefined) {
521
+ throw new TypeError(`Cannot read properties of ${String(receiver)} (reading '${method}')`);
522
+ }
523
+ const args = node.arguments.map((a) => unwrapOptional(evaluateNode(a, scope, depth)));
524
+ return invokeInstanceMethod(receiver, method, args);
525
+ }
526
+ function invokeMathMethod(method, args) {
527
+ const n0 = Number(args[0]);
528
+ const n1 = Number(args[1]);
529
+ switch (method) {
530
+ case 'abs': return Math.abs(n0);
531
+ case 'ceil': return Math.ceil(n0);
532
+ case 'floor': return Math.floor(n0);
533
+ case 'round': return Math.round(n0);
534
+ case 'trunc': return Math.trunc(n0);
535
+ case 'sign': return Math.sign(n0);
536
+ case 'min': return Math.min(...args.map((a) => Number(a)));
537
+ case 'max': return Math.max(...args.map((a) => Number(a)));
538
+ case 'pow': return Math.pow(n0, n1);
539
+ case 'sqrt': return Math.sqrt(n0);
540
+ case 'cbrt': return Math.cbrt(n0);
541
+ case 'log': return Math.log(n0);
542
+ case 'log2': return Math.log2(n0);
543
+ case 'log10': return Math.log10(n0);
544
+ case 'exp': return Math.exp(n0);
545
+ case 'hypot': return Math.hypot(...args.map((a) => Number(a)));
546
+ default:
547
+ throw new TypeError(`Unsupported Math method "${method}"`);
548
+ }
549
+ }
550
+ function invokeNamespaceMethod(ns, method, args) {
551
+ switch (ns) {
552
+ case 'Math':
553
+ return invokeMathMethod(method, args);
554
+ case 'JSON':
555
+ if (method === 'parse')
556
+ return JSON.parse(String(args[0]));
557
+ if (method === 'stringify') {
558
+ const replacer = Array.isArray(args[1]) ? args[1] : null;
559
+ const space = args[2] === undefined
560
+ ? undefined
561
+ : typeof args[2] === 'number'
562
+ ? Math.min(Math.max(0, args[2]), 10)
563
+ : String(args[2]).slice(0, 10);
564
+ return JSON.stringify(args[0], replacer, space);
565
+ }
566
+ break;
567
+ case 'Object':
568
+ if (args[0] === null || args[0] === undefined) {
569
+ throw new TypeError('Cannot convert undefined or null to object');
570
+ }
571
+ if (method === 'keys')
572
+ return Object.keys(Object(args[0]));
573
+ if (method === 'values')
574
+ return Object.values(Object(args[0]));
575
+ if (method === 'entries')
576
+ return Object.entries(Object(args[0]));
577
+ break;
578
+ case 'Array':
579
+ if (method === 'isArray')
580
+ return Array.isArray(args[0]);
581
+ break;
582
+ case 'Number':
583
+ if (method === 'isInteger')
584
+ return Number.isInteger(args[0]);
585
+ if (method === 'isFinite')
586
+ return Number.isFinite(args[0]);
587
+ if (method === 'isNaN')
588
+ return Number.isNaN(args[0]);
589
+ if (method === 'isSafeInteger')
590
+ return Number.isSafeInteger(args[0]);
591
+ if (method === 'parseInt')
592
+ return Number.parseInt(String(args[0]), args[1] === undefined ? undefined : Number(args[1]));
593
+ if (method === 'parseFloat')
594
+ return Number.parseFloat(String(args[0]));
595
+ break;
596
+ case 'Date':
597
+ if (method === 'now')
598
+ return Date.now();
599
+ break;
600
+ }
601
+ throw new TypeError(`Unsupported namespace call ${ns}.${method}`);
602
+ }
603
+ function invokeInstanceMethod(receiver, method, args) {
604
+ if (typeof receiver === 'string' && SAFE_STRING_METHODS.has(method)) {
605
+ return invokeStringMethod(receiver, method, args);
606
+ }
607
+ if (Array.isArray(receiver) && (SAFE_ARRAY_VALUE_METHODS.has(method) || HIGHER_ORDER_ARRAY_METHODS.has(method))) {
608
+ return invokeArrayMethod(receiver, method, args);
609
+ }
610
+ if (typeof receiver === 'number' && SAFE_NUMBER_METHODS.has(method)) {
611
+ if (method === 'toFixed')
612
+ return receiver.toFixed(args[0] === undefined ? undefined : Number(args[0]));
613
+ if (method === 'toPrecision')
614
+ return receiver.toPrecision(args[0] === undefined ? undefined : Number(args[0]));
615
+ if (method === 'toString')
616
+ return receiver.toString(args[0] === undefined ? undefined : Number(args[0]));
617
+ if (method === 'valueOf')
618
+ return receiver.valueOf();
619
+ }
620
+ if (typeof receiver === 'boolean' && SAFE_BOOLEAN_METHODS.has(method)) {
621
+ return method === 'toString' ? receiver.toString() : receiver.valueOf();
622
+ }
623
+ if (receiver instanceof Date && SAFE_DATE_METHODS.has(method)) {
624
+ return invokeDateMethod(receiver, method);
625
+ }
626
+ throw new TypeError(`Method "${method}" is not callable on value of type ${typeof receiver}`);
627
+ }
628
+ function invokeStringMethod(s, method, args) {
629
+ switch (method) {
630
+ case 'includes': return s.includes(String(args[0]), args[1] === undefined ? undefined : Number(args[1]));
631
+ case 'startsWith': return s.startsWith(String(args[0]), args[1] === undefined ? undefined : Number(args[1]));
632
+ case 'endsWith': return s.endsWith(String(args[0]), args[1] === undefined ? undefined : Number(args[1]));
633
+ case 'indexOf': return s.indexOf(String(args[0]), args[1] === undefined ? undefined : Number(args[1]));
634
+ case 'lastIndexOf': return s.lastIndexOf(String(args[0]), args[1] === undefined ? undefined : Number(args[1]));
635
+ case 'toLowerCase': return s.toLowerCase();
636
+ case 'toUpperCase': return s.toUpperCase();
637
+ case 'trim': return s.trim();
638
+ case 'trimStart': return s.trimStart();
639
+ case 'trimEnd': return s.trimEnd();
640
+ case 'slice': return s.slice(args[0] === undefined ? undefined : Number(args[0]), args[1] === undefined ? undefined : Number(args[1]));
641
+ case 'substring': return s.substring(Number(args[0] ?? 0), args[1] === undefined ? undefined : Number(args[1]));
642
+ case 'split': return s.split(args[0] === undefined ? '' : String(args[0]), args[1] === undefined ? undefined : Number(args[1]));
643
+ case 'replace': {
644
+ if (typeof args[0] !== 'string' || typeof args[1] !== 'string') {
645
+ throw new TypeError('String.replace in safe expressions requires string search and replacement arguments');
646
+ }
647
+ const replacement = args[1];
648
+ return s.replace(args[0], () => replacement);
649
+ }
650
+ case 'replaceAll': {
651
+ if (typeof args[0] !== 'string' || typeof args[1] !== 'string') {
652
+ throw new TypeError('String.replaceAll in safe expressions requires string search and replacement arguments');
653
+ }
654
+ const replacement = args[1];
655
+ return s.replaceAll(args[0], () => replacement);
656
+ }
657
+ case 'padStart': {
658
+ const targetLen = Math.min(Number(args[0] ?? 0), MAX_PAD_LENGTH);
659
+ return s.padStart(targetLen, args[1] === undefined ? undefined : String(args[1]));
660
+ }
661
+ case 'padEnd': {
662
+ const targetLen = Math.min(Number(args[0] ?? 0), MAX_PAD_LENGTH);
663
+ return s.padEnd(targetLen, args[1] === undefined ? undefined : String(args[1]));
664
+ }
665
+ case 'charAt': return s.charAt(Number(args[0] ?? 0));
666
+ case 'charCodeAt': return s.charCodeAt(Number(args[0] ?? 0));
667
+ case 'at': return s.at(Number(args[0] ?? 0));
668
+ case 'concat': return s.concat(...args.map((a) => String(a)));
669
+ case 'toString':
670
+ case 'valueOf':
671
+ return s;
672
+ default:
673
+ throw new TypeError(`Unsupported string method "${method}"`);
674
+ }
675
+ }
676
+ function invokeArrayMethod(arr, method, args) {
677
+ if (HIGHER_ORDER_ARRAY_METHODS.has(method)) {
678
+ const cb = args[0];
679
+ if (typeof cb !== 'function') {
680
+ throw new TypeError(`"${method}" callback is not a function`);
681
+ }
682
+ const fn = cb;
683
+ switch (method) {
684
+ case 'some': return arr.some((item, idx, a) => Boolean(fn(item, idx, a)));
685
+ case 'every': return arr.every((item, idx, a) => Boolean(fn(item, idx, a)));
686
+ case 'find': return arr.find((item, idx, a) => Boolean(fn(item, idx, a)));
687
+ case 'findIndex': return arr.findIndex((item, idx, a) => Boolean(fn(item, idx, a)));
688
+ case 'filter': return arr.filter((item, idx, a) => Boolean(fn(item, idx, a)));
689
+ case 'map': return arr.map((item, idx, a) => fn(item, idx, a));
690
+ case 'flatMap': return arr.flatMap((item, idx, a) => fn(item, idx, a));
691
+ case 'reduce': {
692
+ const reducer = cb;
693
+ return args.length >= 2
694
+ ? arr.reduce((acc, item, idx, a) => reducer(acc, item, idx, a), args[1])
695
+ : arr.reduce((acc, item, idx, a) => reducer(acc, item, idx, a));
696
+ }
697
+ }
698
+ }
699
+ switch (method) {
700
+ case 'includes': return arr.includes(args[0], args[1] === undefined ? undefined : Number(args[1]));
701
+ case 'indexOf': return arr.indexOf(args[0], args[1] === undefined ? undefined : Number(args[1]));
702
+ case 'lastIndexOf': return arr.lastIndexOf(args[0], args[1] === undefined ? undefined : Number(args[1]));
703
+ case 'join': return arr.join(args[0] === undefined ? ',' : String(args[0]));
704
+ case 'slice': return arr.slice(args[0] === undefined ? undefined : Number(args[0]), args[1] === undefined ? undefined : Number(args[1]));
705
+ case 'concat': return arr.concat(...args);
706
+ case 'flat': return arr.flat(Math.min(Math.max(0, Number(args[0] ?? 1)), MAX_FLAT_DEPTH));
707
+ case 'at': return arr.at(Number(args[0] ?? 0));
708
+ case 'toString': return arr.toString();
709
+ default:
710
+ throw new TypeError(`Unsupported array method "${method}"`);
711
+ }
712
+ }
713
+ function invokeDateMethod(d, method) {
714
+ switch (method) {
715
+ case 'toISOString': return d.toISOString();
716
+ case 'getTime': return d.getTime();
717
+ case 'getFullYear': return d.getFullYear();
718
+ case 'getUTCFullYear': return d.getUTCFullYear();
719
+ case 'getMonth': return d.getMonth();
720
+ case 'getUTCMonth': return d.getUTCMonth();
721
+ case 'getDate': return d.getDate();
722
+ case 'getUTCDate': return d.getUTCDate();
723
+ case 'toString': return d.toString();
724
+ case 'valueOf': return d.valueOf();
725
+ default:
726
+ throw new TypeError(`Unsupported Date method "${method}"`);
727
+ }
728
+ }
729
+ function evaluateArrowFunction(node, parentScope, depth) {
730
+ const paramNames = node.params.map((p) => p.name);
731
+ return (...callArgs) => {
732
+ const bindings = new Map();
733
+ for (let i = 0; i < paramNames.length; i++) {
734
+ bindings.set(paramNames[i], callArgs[i]);
735
+ }
736
+ const childScope = { bindings, parent: parentScope };
737
+ return unwrapOptional(evaluateNode(node.body, childScope, depth));
738
+ };
739
+ }
740
+ function evaluateBinaryExpression(node, scope, depth) {
741
+ const left = unwrapOptional(evaluateNode(node.left, scope, depth));
742
+ const right = unwrapOptional(evaluateNode(node.right, scope, depth));
743
+ switch (node.operator) {
744
+ // eslint-disable-next-line eqeqeq
745
+ case '==': return left == right;
746
+ // eslint-disable-next-line eqeqeq
747
+ case '!=': return left != right;
748
+ case '===': return left === right;
749
+ case '!==': return left !== right;
750
+ case '<': return left < right;
751
+ case '<=': return left <= right;
752
+ case '>': return left > right;
753
+ case '>=': return left >= right;
754
+ case '+':
755
+ if (typeof left === 'string' || typeof right === 'string') {
756
+ return `${String(left)}${String(right)}`;
757
+ }
758
+ return Number(left) + Number(right);
759
+ case '-': return Number(left) - Number(right);
760
+ case '*': return Number(left) * Number(right);
761
+ case '/': return Number(left) / Number(right);
762
+ case '%': return Number(left) % Number(right);
763
+ case '**': return Number(left) ** Number(right);
764
+ default:
765
+ throw new Error(`Unsupported binary operator "${node.operator}"`);
766
+ }
767
+ }
768
+ function evaluateLogicalExpression(node, scope, depth) {
769
+ const left = unwrapOptional(evaluateNode(node.left, scope, depth));
770
+ switch (node.operator) {
771
+ case '&&': return left ? unwrapOptional(evaluateNode(node.right, scope, depth)) : left;
772
+ case '||': return left ? left : unwrapOptional(evaluateNode(node.right, scope, depth));
773
+ case '??': return left !== null && left !== undefined ? left : unwrapOptional(evaluateNode(node.right, scope, depth));
774
+ default:
775
+ throw new Error(`Unsupported logical operator "${node.operator}"`);
776
+ }
777
+ }
778
+ function evaluateUnaryExpression(node, scope, depth) {
779
+ if (node.operator === 'typeof') {
780
+ if (node.argument.type === 'Identifier') {
781
+ const name = node.argument.name;
782
+ if (!hasLexicalBinding(name, scope) && !SAFE_TRANSFORM_CALLABLES.has(name)) {
783
+ return 'undefined';
784
+ }
785
+ }
786
+ return typeof unwrapOptional(evaluateNode(node.argument, scope, depth));
787
+ }
788
+ const arg = unwrapOptional(evaluateNode(node.argument, scope, depth));
789
+ switch (node.operator) {
790
+ case '!': return !arg;
791
+ case '-': return -Number(arg);
792
+ case '+': return +Number(arg);
793
+ default:
794
+ throw new Error(`Unsupported unary operator "${node.operator}"`);
795
+ }
796
+ }
797
+ //# sourceMappingURL=SafeTransformAstEvaluator.js.map