Repository navigation
Description
Bug description
Standard field conditions are evaluated with eval(), so a site that sends a Content Security Policy without 'unsafe-eval' in script-src cannot use conditional fields on public forms.
Validator.passesCondition() in resources/js/components/field-conditions/Validator.js ends with:
return eval(`${condition.lhs} ${condition.operator} ${condition.rhs}`);It is on the 6.x branch today (line 269). The frontend helpers.js bundle ships the same code, and public forms reach it through Statamic.$conditions.showField(). Every operator except includes, includes_any and custom conditions takes this path: is, equals and ==; isnt, not and !=; and >, >=, < and <=. It is the normal path, not a corner case.
Under an enforcing CSP the eval() throws. When a form renders its conditions in an Alpine x-if, the throw escapes the expression, Alpine logs it, the expression yields undefined, and the conditional field is never shown.
How to reproduce
- Build a form with one conditional field, for example
if: { referrer: 'equals Yes' }. - Render it on the front end with the frontend helpers loaded, and send
Content-Security-Policy: script-src 'self', with no'unsafe-eval'. - Set
referrertoYes. The conditional field does not appear, and the console shows anEvalErrorfrom the CSP.
The same thing can be shown without a browser policy by loading helpers.js in jsdom with eval shadowed:
const calls = [];
const run = new Function('window', 'self', 'globalThis', 'eval', src);
run(window, window, window, (code) => { calls.push(code); return globalThis.eval(code); });
Statamic.$conditions.showField({ if: { referrer: 'equals Yes' } }, { referrer: 'Yes' }, 'referrer_note');
// calls: ['"Yes" == "Yes"']With the shadowed eval replaced by one that throws, which is what a CSP does, showField() throws instead of returning a boolean.
Logs
Nothing server-side. The failure is in the browser: the CSP blocks the eval() call in the frontend helpers and reports an unsafe-eval violation.
Environment
Statamic 6.26.0 (the eval() is unchanged on the 6.x branch as of 2026-09-30)
Laravel 13
PHP 8.4Installation
Existing Laravel app
Additional details
A fix is a comparator over the operator set, but a naive one changes behavior in two measurable cases, because prepareLhs() and prepareRhs() prepare operands as JavaScript source for eval() rather than as values:
- A number LHS against a string RHS.
prepareLhs()returns a number as a number andprepareRhs()returns a string JSON-encoded, soeval('1 == "1"')istrue, while comparing the prepared operands directly (1 == '"1"') isfalse. - An
undefinedLHS.eval('undefined == "x"')isfalse, while passing both operands throughJSON.parse()to undo the encoding throws aSyntaxErroron theundefinedside.
One shape that avoids both is to stop JSON-encoding in prepareLhs()/prepareRhs() and compare native values, keeping the loose == semantics eval() gives today. A narrower alternative leaves those methods alone and decodes inside passesCondition(). A pull request with tests for every operator, the empty right-hand side and both cases above can follow; please say which of the two shapes you would prefer.
Activity