Skip to content

TS SDK - Fall back to closures when Function is forbidden - #6368

Open
JulienLavocat wants to merge 1 commit into
masterfrom
julien/ts-sdk-csp-fallback
Open

JulienLavocat wants to merge 1 commit into
masterfrom
julien/ts-sdk-csp-fallback

Conversation

@JulienLavocat

Copy link
Copy Markdown
Contributor

Description of Changes

Fixes #4966 (and #4669, which was closed without a fix).

The TypeScript SDK builds its serializers and deserializers at runtime with Function(...), in six places in algebraic_type.ts. A Content-Security-Policy without 'unsafe-eval' forbids that, and so do Cloudflare Workers, so the first row decode threw EvalError and the SDK could not be used there.

  • src/lib/runtime_codegen.ts probes once whether Function is allowed and caches the answer, so a page under a CSP sees at most one violation report. It is internal and not re-exported.
  • When Function is allowed, nothing changes: servers and normal browsers keep the generated fast path.
  • When it isn't, ProductType and SumType build their serializers and deserializers from closures that write the same bytes and return the same values. Like the fast path, each one is cached before its fields or variants are built, so recursive types terminate. An unknown sum tag on decode still yields undefined, as the generated switch does.
  • ws.ts loads undici with import(undici) and the webpackIgnore/@vite-ignore comments instead of new Function('m', 'return import(m)').

This doesn't generate serializers when the bindings are generated, as the issue suggests (and as #5003 tried). Types built at runtime, such as reducer arguments, would need this fallback anyway, and generating them in the bindings could follow as a performance change.

API and ABI breaking changes

None. The wire format is unchanged and no public API is added.

Rollback safety impact

n/a

Expected complexity level and risk

  1. The fallback is small and only runs where Function is forbidden, which fails today. The risk is a mismatch between the two paths, which the new test checks byte for byte.

Terser folds the undici specifier back into a literal import("undici") in dist/min/*. Those files are not in the package exports and are only used for the size limits; the bundles consumers import keep the variable and the ignore comments.

Testing

  • tests/serde_without_function.test.ts forbids Function the way a CSP does and checks that both paths write identical bytes and round-trip a fixed-size product, a nested product with strings, arrays and an option, a sum, and recursive products and sums. Removing one fallback branch makes it fail.
  • pnpm vitest run: everything passes except server_test_utils_wasm, server_test_utils_wasm_adapter and consumer_types, which fail the same way on master (missing WASM test artifacts and an unrelated case-conversion client).
  • tsc -p tsconfig.build.json, eslint and prettier are clean, and tsup builds.
  • Loaded dist/index.mjs, dist/index.browser.mjs and dist/min/index.browser.mjs under node --disallow-code-generation-from-strings and round-tripped a product, a sum and a fixed-size product. A master build throws EvalError: Code generation from strings disallowed for this context.
  • Reviewer: Vitest can't run under --disallow-code-generation-from-strings itself (its worker pool uses new Function), so the unit test uses the internal switch. Check you're happy with that.

🤖 Generated with Claude Code

The TypeScript SDK builds its serializers and deserializers with
`Function`, which a Content-Security-Policy without 'unsafe-eval' and
Cloudflare Workers forbid, so the first row decode threw an EvalError.

- Probe once whether `Function` is allowed. If not, build the
  serializers and deserializers from closures that write the same bytes.
- Load `undici` with a variable dynamic import instead of
  `new Function('m', 'return import(m)')`.

Fixes #4966.

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.

TypeScript SDK 2.2.0 still requires 'unsafe-eval' — Function() codegen breaks strict CSP (recurrence of #4669)

1 participant