Repository navigation
[6.x] Compare field conditions without eval() - #15564
Merged
Merged
Conversation
Validator::passesCondition() decided every plain operator comparison by building a source string and passing it to eval(), so conditional fields stop working under a Content Security Policy that omits 'unsafe-eval'. Compare through a table of the eight operators instead. prepareLhs() and prepareRhs() JSON-encode string operands, so those are decoded back to the values eval() read before comparing; everything else is compared as is. That keeps eval()'s results, including a number field against a string condition value and an undefined operand. Fixes statamic#15552. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BunmG83RzfEoLnXCnQZ9Kq
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
joshdaugherty
added a commit
to UAMS-Web/aether-alpine-csp-forms
that referenced
this pull request
Oct 27, 2026
Closes #18. The `$showField` magic now forwards to `window.Statamic.$conditions.showField`, which Statamic's front-end `helpers.js` defines, and the add-on's own copy of the field-conditions evaluator is removed. Public forms and the Control Panel then evaluate conditions with the same code. `statamic/cms` `v6.35.1` is the first release whose evaluator runs without `eval()` (`statamic/cms#15564`), so the add-on now requires `^6.35.1`. ## Approach - [`resources/js/conditions/showFieldMagic.js`](https://github.com/UAMS-Web/statamic-alpine-csp-forms/blob/18-forward-showfield-to-statamic/resources/js/conditions/showFieldMagic.js) reads `window.Statamic.$conditions` on every call, not at registration, so a host that loads `helpers.js` after the add-on's script still works once it has loaded. - **Custom conditions**: Statamic's front-end `$conditions` defines `showField()` only, and its evaluator looks a `custom` condition up with `get()`, which exists in the Control Panel alone. Where `get()` is missing, the magic supplies one that reads `window.StatamicCustomConditions`, so a registered condition decides the field and an unregistered one hides it with Statamic's "was not properly defined" error. An existing `get()` is left in place. - **Missing global**: the magic shows the field and logs one console error per registration naming `/vendor/statamic/frontend/js/helpers.js`. A throwing evaluator still fails open, as before. - **Removed**: `validator.js`, `constants.js`, `converter.js`, `parentResolver.js`, `support.js`, `showField.js`, their tests, and `tests/Unit/FieldConditionsConstantsParityTest.php`. - **Tests**: [`showFieldMagic.test.js`](https://github.com/UAMS-Web/statamic-alpine-csp-forms/blob/18-forward-showfield-to-statamic/resources/js/conditions/showFieldMagic.test.js) loads the installed `vendor/statamic/cms/resources/dist-frontend/js/helpers.js`, so the real-interpreter cases (non-ASCII values, live `$data`, the field path, the rendered form) run against Statamic's evaluator. A stubbed global covers forwarding, and a missing one covers the console error. - **Docs**: the README and the wiki's Installation, Driver reference and Troubleshooting pages say to load `helpers.js` before `alpine-csp.js`, and give the `6.35.1` floor. `docs/DECISIONS.md` records the decision. - `resources/dist/frontend/alpine-csp.js` is rebuilt: 859 bytes, down from 7,357. ## Breaking change A host must load Statamic's `helpers.js` ahead of `alpine-csp.js`. Without it every conditional field is shown and the console names the missing script. In `uams-statamic` that is UAMS-Web/uams-statamic#1931. ## Test plan - [x] `composer ci` on Windows 11: Pint, Rector, PHPStan, 57 Pest tests and 125 Vitest tests passed - [x] `npx vitest run resources/js`: 15 passed on Windows 11 - [x] Negative controls: replacing the forward with `return true` turns 8 of the 12 forwarding and evaluation tests red, including the stubbed-global case. Removing the once-only guard turns the missing-global case red (3 calls, not 1). Removing the custom-condition lookup turns both lookup tests red, and overwriting an existing `get()` turns the keep-it test red. All restored to green. - [x] `git grep` at the head finds no reference to the deleted files outside `docs/DECISIONS.md` (the same expression finds them on the parent commit) ## After merging A release is tagged so UAMS-Web/uams-statamic#1931 can require it. The tag is left for the release owner. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15552.
Validator::passesCondition()decides every plain comparison (==,!=,===,!==,>,>=,<and<=, sois,equals,not,isntand the shorthand too) by building a source string and passing it toeval(). Under a Content Security Policy that omits'unsafe-eval', that call throws and conditional fields stop working.This replaces the
eval()with a table of those eight comparisons.Keeping
eval()'s resultsprepareLhs()andprepareRhs()JSON-encode string operands, andeval()read them back as string literals. Comparing the prepared operands directly would change results, so:JSON.parse()before comparing, which giveseval()'s values. Without that, a number field compared with a condition value (age: '13'against13) is compared with the encoded"13", quotes included, and stops matching.undefinedoperand is not valid JSON, so decoding every operand would throw whereeval()returned a result.Error, aseval()threw on a source string it could not parse.prepareLhs(),prepareRhs(), and theincludes,includes_any,contains_any,emptyand custom paths are unchanged. Only the three comments that referred toeval()are updated. No compiled files are included.The narrower change was chosen over returning native values from
prepareLhs()/prepareRhs(). That would also remove the encoding, butcontains_anymatches against the encoded string, so its results for values containing a quote character would change.Tests
Added to
resources/js/tests/FieldConditionsValidator.test.js:eval(): every plain operator,null,trueandemptyevaluate withglobalThis.evalreplaced by a function that throws.==,!=,===and the shorthand.null.undefinedoperand:passesCondition()with anundefinedoperand returnseval()'s results rather than throwing.npx vitest run --project unit: 70 of 71 files pass.LivePreview.test.jsfails identically on6.xwithout this change (localStorageis undefined in that environment).Against the unchanged
eval()implementation, all of the new tests except the no-eval()one pass. That shows the results are unchanged. Two naive rewrites each fail at least one test:undefined-operand test.This was prepared with AI assistance and reviewed before submitting.