Merged
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
react-server-docs | ebf2331 | Mar 01 2026, 08:49 PM |
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.
This PR adds AES-256-GCM encryption for server function IDs, preventing clients from discovering or tampering with internal module paths and export names that are normally embedded in the HTML payload as plain-text action references.
With this change, every server function reference sent to the client is replaced with an opaque, encrypted token. The server decrypts the token on invocation and maps it back to the original function. This is a zero-configuration feature — in development a random ephemeral key is generated automatically, and in production the key is persisted as a build artifact.
Motivation
Without encryption, server function IDs like
src/actions#deleteUserare visible in the client bundle and HTML output. An attacker could:Encrypting these IDs eliminates this entire class of information disclosure.
What Changed
New:
action-crypto.mjs— Core Encryption EngineA new module (
packages/react-server/server/action-crypto.mjs, ~310 lines) providing:encryptActionId(id)— Encrypts a plain-text action ID using AES-256-GCM with a random 12-byte IV, producing a base64url tokendecryptActionId(token)— Decrypts a token back to the original ID, trying the primary key first, then previous keys for rotationinitSecret(secret)/initSecretFromConfig(config)— Key initialization with a clear resolution order:REACT_SERVER_FUNCTIONS_SECRETenv varREACT_SERVER_FUNCTIONS_SECRET_FILEenv var (path to .pem)serverFunctions.secretin configserverFunctions.secretFilein configwrapServerReferenceMap(baseMap)— Proxy wrapper that transparently handles encrypted key lookups by decrypting tokens before delegating to the base mapserverFunctions.previousSecretsandserverFunctions.previousSecretFilesconfig arraysModified:
action-register.mjs— Encrypting$$idGetterReact's
registerServerReferenceis wrapped to replace the plain-text$$idproperty with a lazily-encrypting getter. Key design decisions:$$idis stable across multiple reads of the same reference. This is critical because React'sdecodeFormStatecompares the$ACTION_IDfrom the submitted form with the action's current$$id.$$originalId: The original plain-text ID is stored as a non-enumerable property for server-side identity comparison without decryption.Modified:
action-state.mjs— Dual-formatactionIdComparisonuseActionStatenow compares the context'sactionIdagainst bothaction.$$id(encrypted) andaction.$$originalId(plain text), because the context can receive either format:decodeAction):actionIdis the encrypted token from the form's$ACTION_IDhidden fieldReact-Server-Actionheader):actionIdis the decrypted plain-text ID after server-side resolutionModified:
render-rsc.jsx— Server-side DecryptionThe RSC render function now:
wrapServerReferenceMap()at module level for transparent encrypted lookupsReact-Server-Actionheader value before resolving the functionServerFunctionNotFoundErrorwhen decryption fails and the raw ID is also unknown (tampered/invalid token)Modified:
use-server.mjsPlugin — Build-time Encryption for Client/SSR StubsThe Vite plugin that transforms
"use server"modules now encrypts action IDs at build time when generatingcreateServerReference()calls for client and SSR bundles. This ensures the client never sees plain-text IDs.Build & Runtime Integration
lib/build/action.mjs: Generates a fresh secret at build time, persists it asserver/action-secret.mjsbuild artifactlib/build/edge.mjs: Resolves theaction-secretimport alias for edge buildslib/start/manifest.mjs: Loads the persisted secret from the build artifact at startup, then applies env/config overrideslib/dev/action.mjsandlib/dev/index.mjs: Initialize the secret from config at dev server startupnode-loader.mjs,bun.mjs,deno.mjs): Add import alias for@lazarv/react-server/dist/server/action-secretpointing to the build outputDocumentation
New docs page at
docs/src/pages/en/(pages)/framework/server-function-encryption.mdxcovering:previousSecrets/previousSecretFilesConfiguration
Environment variables:
REACT_SERVER_FUNCTIONS_SECRET=your-secret-key # OR REACT_SERVER_FUNCTIONS_SECRET_FILE=./secrets/action-key.pem