Inject the deployment secret into server builds (solidjs/solid#3239) - #343
Merged
Conversation
🦋 Changeset detectedLatest commit: 9e17922 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
This was referenced Sep 4, 2026
…3239) The runtime's no-JS flash cookie is AES-GCM encrypted as of @solidjs/web's #3239 fix — it carries the submitted form input, so without a key the outcome is withheld entirely. This provides the key's secret with zero configuration through the internal globalThis.__SOLID_SECRET__ ??= contract: generated once per plugin instance via node:crypto randomBytes, emitted as the first statement of the generated server-function handler module, which every dispatch surface loads and the generated SSR handler imports at module load — so the secret is in place for both the flash's encode (the form POST) and its decode (the render that follows the redirect). Per plugin instance means a production build bakes one value into the emitted server chunk — every instance of that deployment shares it, which the runtime requires (a per-process value would silently lose flashes behind a load balancer) — and a dev session holds one for its lifetime; a restart invalidates in-flight flashes, which are 60-second one-shot cookies. The handler module is hard-gated server-only, so the secret never reaches the client graph, and ??= keeps an explicit configureServerFunctionsServer({ secret }) — or an outer harness's injected value — authoritative. Co-authored-by: Cursor <cursoragent@cursor.com>
Drives the no-JS server-function convention end to end in the middleware suite (mw-dev and mw-prod): a form navigation POST to a server function's bare address answers 303 back to the referrer with the encrypted flash cookie (`flash=1.…`, Max-Age=60, SameSite=Lax, HttpOnly), the render that follows decrypts it — src/setup.tsx plays the integration's half via decodeFlashCookie and surfaces url/result/input as a marker — and clears the one-shot cookie. Against @solidjs/web 2.0.0-rc.7 without the deployment secret the redirect goes out plain (no Set-Cookie) and the render sees no flash — 8 of the 12 new assertions fail; with the `globalThis.__SOLID_SECRET__ ??=` snippet they pass. The POST goes through node:http rather than fetch: undici treats the Sec-Fetch-* names as forbidden request headers and sends its own `Sec-Fetch-Mode: cors`, which the runtime rightly refuses (400) as a scripted call to the bare address. Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid
force-pushed
the
flash-deployment-secret
branch
from
September 8, 2026 17:28
4479860 to
9e17922
Compare
ryansolid
marked this pull request as ready for review
September 8, 2026 17:31
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.
Summary
Companion to the encrypted no-JS flash cookie landing in
@solidjs/web(solidjs/solid#3239, commit solidjs/solid@fbe5bef4). The flash carries the submitted form input — whatever the user typed — so the runtime now AES-GCM encrypts it under a key derived (domain-separated) from the deployment secret, and withholds the flash entirely when no secret exists.This PR provides that secret with zero configuration through the internal bundler contract:
emitted as the first statement of the generated server-function handler module (
virtual:solid-server-function-handler).Why this shape
node:cryptorandomBytes(32)): a production build bakes one value into the emitted server chunk, so every instance of that deployment shares it — the runtime requires this (a per-process value would silently lose flashes behind a load balancer). A dev session holds one value for its lifetime; a restart invalidates in-flight flashes, which are 60-second one-shot cookies — the next render just reads "no flash".??=keeps explicit config authoritative:configureServerFunctionsServer({ secret })outranks the global in the runtime's resolution order, and an outer harness that injects its own__SOLID_SECRET__earlier wins over this snippet.__SOLID_SECRET__, not a flash-specific name: the secret is the deployment-wide concept; the runtime derives per-purpose keys from it under domain strings (solid.flash.v1today), so future features (and other tooling) share one contract without key-material overlap.Verification
Built
examples/start-ssr:dist/server/server.jsleads with the??=line carrying a 64-hex value; the client output contains no reference.pnpm buildand the example suite pass.Draft until
@solidjs/web2.0.0-rc.7 ships the encrypted flash runtime this pairs with.Made with Cursor
Review against the published rc.7 runtime (
@solidjs/web/server-functions/dist/server*.js):resolveSecret()isconfiguredSecret !== undefined ? configuredSecret : globalThis.__SOLID_SECRET__, read lazily per encode/decode and required to be a non-empty string; the key isSHA-256("solid.flash.v1\0" + secret). The??=snippet matches that contract exactly — explicitconfigureServerFunctionsServer({ secret })and an earlier-injected global both keep precedence. Builtexamples/start-ssr: the snippet opens thevirtual:solid-server-function-handlerregion ofdist/server/server.jswith a 64-hex value;dist/clienthas no reference.e2e (
9e17922): the middleware suite now drives the no-JS convention end to end in dev and prod — form-navigation POST → 303 to the referrer carryingflash=1.…(Max-Age=60, SameSite=Lax, HttpOnly) → the following render decrypts it (src/setup.tsxviadecodeFlashCookie), surfaces url/result/input, and clears the cookie. Red→green: with the plugin built fromnext(no secret) 8 of the 12 new assertions fail (303 goes out plain, noSet-Cookie, no flash on the render); with this branch 67/67. The POST usesnode:httpbecause undici forcesSec-Fetch-Mode: cors, which the runtime rightly refuses as a scripted call.Full gate on the rebased branch: ssr 12/12 + 8/8, css-matrix 87/87 + 19/19, start-ssr 498/498 + http-bridge 10/10 + components-warning 9/9, start-client 45/45, start-env 47/47.