feat(app): default Zod to jitless; add allowUnsafeEval opt-out (#618)

Zod v4's object parser compiles a fast path with `new Function()`, which
violates CSP without `unsafe-eval`. Hosts that enforce the spec's default
CSP (e.g. VS Code) cannot load any View that parses an incoming message —
the SDK's generated schemas trip this on the very first ui/initialize
roundtrip.

The App constructor now sets `z.config({ jitless: true })` by default so
Views work out of the box under strict CSP. New `AppOptions.allowUnsafeEval`
(default false) skips this for Views that know their host permits
`unsafe-eval` and want Zod's faster JIT path.

Addresses #374. Also relevant to #199 (threejs-server has a separate
`new Function()` use in its own shader compiler that this does not fix).
This commit is contained in:
Olivier Chafik 2026-04-21 14:49:09 +01:00 • committed by GitHub
commit 9d68315720
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -74,6 +74,7 @@ import {
standardSchemaToJsonSchema,
validateStandardSchema,
} from "./standard-schema";
import { z } from "zod/v4";
export type {
StandardSchemaV1,
@ -187,6 +188,20 @@ export type AppOptions = ProtocolOptions & {
* @default false
*/
strict?: boolean;
/**
* Allow code paths that require CSP `unsafe-eval` (e.g. `new Function()`).
*
* Views typically run under a strict CSP without `unsafe-eval`. Zod's JIT
* object parser uses `new Function()` and throws on the first message parse
* under such a policy. By default (`allowUnsafeEval: false`) the
* {@link App `App`} constructor sets `z.config({ jitless: true })` so the
* SDK works out of the box under the spec's default CSP. Set
* `allowUnsafeEval: true` to skip that and keep the faster JIT path when
* the host's CSP permits `unsafe-eval`.
*
* @default false
*/
allowUnsafeEval?: boolean;
};
type RequestHandlerExtra = Parameters<
@ -403,6 +418,10 @@ export class App extends ProtocolWithEvents<
) {
super(options);
if (!options.allowUnsafeEval) {
z.config({ jitless: true });
}
this.setRequestHandler(PingRequestSchema, (request) => {
console.log("Received ping:", request.params);
return {};