Skip to content

Commit bf0a6f4

Browse files
fix(core): replace new Function() with CSP-safe SafeExpressionParser
Replaces eval()-equivalent `new Function()` in ExpressionCache.compileExpression() with a recursive-descent SafeExpressionParser that works under strict CSP headers that forbid 'unsafe-eval'. Supports: comparisons, logical/ternary/nullish ops, arithmetic, unary, dot/bracket/optional-chaining access, formula functions, Math.*, single-param arrow functions (.filter/.map/.find), array literals, new Date()/RegExp(), all literal types. 41 new CSP-safety tests added. All 822 tests pass." Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/16c7c01e-a482-4f8c-b565-2e084a49162e Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent 4027153 commit bf0a6f4

5 files changed

Lines changed: 1055 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
### Fixed
1919

20+
- **CSP-safe expression evaluation** (`@object-ui/core`): Replaced `new Function()` in `ExpressionCache.compileExpression()` with a new `SafeExpressionParser` — a recursive-descent interpreter that evaluates expressions without any dynamic code execution. This fixes `Content Security Policy` violations (`'unsafe-eval' is not an allowed source of script`) that occurred when evaluating schema expressions like `${stage !== 'closed_won' && stage !== 'closed_lost'}` in enterprise deployments with strict CSP headers. The `SafeExpressionParser` supports the full ObjectUI expression language: all comparison and logical operators, ternary and nullish coalescing, arithmetic, unary operators (`!`, `-`, `+`, `typeof`), dot/bracket/optional-chaining member access, function calls (formula functions, method calls, `Math.*`), single-param arrow functions (for `.filter(u => u.isActive)`, `.map(x => x.name)` etc.), array literals, `new Date()` / `new RegExp()` constructors, and all literal types. The `ExpressionCache` public API is unchanged. 41 new tests added.
21+
2022
- **CI build errors** (`@object-ui/console`): Removed unused imports (`resolveI18nLabel` in `HomePage.tsx`, `Upload`/`FileText` in `QuickActions.tsx`) that caused TS6133 errors. Fixed `appConfig``appConfigs[0]` in `i18n-translations.test.ts` (TS2552). Extracted `customReportsConfig` from aggregated `sharedConfig` into a standalone export so the mock server kernel includes the `sales_performance_q1` report, fixing `ReportView.test.tsx` failures.
2123

2224
- **Charts groupBy value→label resolution** (`@object-ui/plugin-charts`): Chart X-axis labels now display human-readable labels instead of raw values. Select/picklist fields resolve value→label via field metadata options, lookup/master_detail fields batch-fetch referenced record names, and all other fields fall back to `humanizeLabel()` (snake_case → Title Case). Removed hardcoded `value.slice(0, 3)` truncation from `AdvancedChartImpl.tsx` XAxis tick formatters — desktop now shows full labels with angle rotation for long text, mobile truncates at 8 characters with "…".

packages/core/src/evaluator/ExpressionCache.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88

99
/**
1010
* @object-ui/core - Expression Cache
11-
*
11+
*
1212
* Caches compiled expressions to avoid re-parsing on every render.
1313
* Provides significant performance improvement for frequently evaluated expressions.
14-
*
14+
*
1515
* @module evaluator
1616
* @packageDocumentation
1717
*/
1818

19+
import { SafeExpressionParser } from './SafeExpressionParser.js';
20+
1921
/**
2022
* A compiled expression function that can be executed with context values
2123
*/
@@ -112,16 +114,25 @@ export class ExpressionCache {
112114
}
113115

114116
/**
115-
* Compile an expression into a function
117+
* Compile an expression into a CSP-safe callable function.
118+
*
119+
* Uses `SafeExpressionParser` — a recursive-descent interpreter — instead of
120+
* `new Function()` so that the expression engine works under strict
121+
* Content Security Policy headers that forbid `'unsafe-eval'`.
122+
*
123+
* The returned function re-creates the evaluation context on every call by
124+
* zipping `varNames` with the positional `args`, matching the contract that
125+
* `ExpressionEvaluator` depends on.
116126
*/
117127
private compileExpression(expression: string, varNames: string[]): CompiledExpression {
118-
// SECURITY NOTE: Using Function constructor for expression evaluation.
119-
// This is a controlled use case with:
120-
// 1. Sanitization check (isDangerous) performed by caller
121-
// 2. Strict mode enabled ("use strict")
122-
// 3. Limited scope (only varNames variables available)
123-
// 4. No access to global objects (process, window, etc.)
124-
return new Function(...varNames, `"use strict"; return (${expression});`) as CompiledExpression;
128+
return (...args: unknown[]) => {
129+
// Reconstruct the named variable context from positional arguments.
130+
const context: Record<string, unknown> = {};
131+
for (let i = 0; i < varNames.length; i++) {
132+
context[varNames[i]] = args[i];
133+
}
134+
return new SafeExpressionParser().evaluate(expression, context);
135+
};
125136
}
126137

127138
/**

0 commit comments

Comments
 (0)