Skip to content

Support JSON.parse() with a string in the CSP evaluator - #4913

Open
m-develops wants to merge 1 commit into
alpinejs:mainfrom
m-develops:csp-json-parse-intrinsic
Open

m-develops wants to merge 1 commit into
alpinejs:mainfrom
m-develops:csp-json-parse-intrinsic

Conversation

@m-develops

Copy link
Copy Markdown

Server-side helpers emit JSON.parse('…') for arrays and objects; Laravel's @js() and Js::from() do it for every non-scalar value. The CSP evaluator resolves JSON like any other global, so every one of those expressions fails with Undefined variable: JSON, and any array that reaches an Alpine attribute through such a helper breaks under the CSP build.

Why now. Livewire's csp_safe mode uses this build, and Filament is working on supporting it: a scan of Filament's templates found 57 @js() arrays inside Alpine attributes, the largest single category of what breaks. Dan Harrin asked for this to be solved here rather than with a Filament-specific replacement for @js(): filamentphp/filament#7032 (reply in thread)

What this does. JSON.parse() with a single string argument becomes an intrinsic of the evaluator. Parsing JSON runs no code, so it does not weaken the sandbox. The rest of the JSON global stays unreachable: JSON.stringify(), a bare JSON, and JSON['parse'] all still throw. A JSON defined in scope takes precedence, the argument may come from scope as well as from a literal, and each evaluation returns a fresh value, as native code would.

Unit tests for all of the above, a Cypress test with the exact output Js::from() produces (including its " escapes), and a docs entry.

Server-side helpers emit JSON.parse('…') for arrays and objects; Laravel's
@js() and Js::from() do it for every non-scalar value. The CSP evaluator
resolved JSON like any other global, so those expressions failed with
"Undefined variable: JSON" and every array reaching an Alpine attribute
through such a helper broke under the CSP build.

Parsing JSON runs no code, so the evaluator now treats JSON.parse() with a
single string argument as an intrinsic: the argument is evaluated, must be
a string, and is parsed with the native JSON.parse(). The JSON global
itself stays unreachable (JSON.stringify(), JSON['parse'], a bare JSON
all still throw), a JSON defined in scope still takes precedence, and
each evaluation returns a fresh value, as native code would.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant