Skip to content

Field conditions use eval(), so a Content Security Policy without 'unsafe-eval' breaks conditional fields #15552

Closed

Description

@bpmore

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

  1. Build a form with one conditional field, for example if: { referrer: 'equals Yes' }.
  2. Render it on the front end with the frontend helpers loaded, and send Content-Security-Policy: script-src 'self', with no 'unsafe-eval'.
  3. Set referrer to Yes. The conditional field does not appear, and the console shows an EvalError from 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.4

Installation

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 and prepareRhs() returns a string JSON-encoded, so eval('1 == "1"') is true, while comparing the prepared operands directly (1 == '"1"') is false.
  • An undefined LHS. eval('undefined == "x"') is false, while passing both operands through JSON.parse() to undo the encoding throws a SyntaxError on the undefined side.

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions