Skip to content

[Pro] Backport production RSC diagnostic redaction#4744

Open
justin808 wants to merge 1 commit into
release/17.0.0from
fix/4629-rsc-diagnostics-redaction-release
Open

[Pro] Backport production RSC diagnostic redaction#4744
justin808 wants to merge 1 commit into
release/17.0.0from
fix/4629-rsc-diagnostics-redaction-release

Conversation

@justin808

Copy link
Copy Markdown
Member

Why

Issue #4629 identified that streamed React Server Component diagnostics could serialize a server render error's message and source-mapped stack into browser-visible HTML outside development. The mainline fix landed in #4631; the closed v17 release tracker designated this as lead 17.0.1 patch-train work.

This PR is the smallest release-branch backport that closes the client-visible disclosure without importing #4631's broader node-renderer reporting changes.

What changed

  • Pass railsContext.railsEnv to the internal RSC payload injector.
  • Include full renderingError metadata only for explicit development and test.
  • Fail closed for production, custom environments, and missing environment values by emitting only { hasErrors: true }.
  • Keep the existing server rendering/reporting path unchanged.

Reporter-path preservation

  • reportError, emitError, buildRenderMetadata, streamingUtils.ts, and the node-renderer handlers are unchanged.
  • The existing streamServerRenderedReactComponent regression suite remains green.
  • This deliberately does not backport Redact RSC rendering error details from client DOM in production #4631's separate custom renderingError event, which is outside this security backport's authorized paths and behavior.

Verification

  • pnpm --filter react-on-rails-pro exec jest tests/injectRSCPayload.test.ts --runInBand — 79 tests passed.
  • pnpm --filter react-on-rails-pro exec jest tests/streamServerRenderedReactComponent.test.jsx --runInBand — 39 tests passed.
  • Pro TypeScript type-check — passed.
  • Focused ESLint and Prettier — passed.
  • Mandatory OSS RuboCop — 234 files, no offenses.
  • script/check-pro-license-headers — 851 files passed.
  • git diff --check — passed.
  • Pre-commit hooks — passed.

The pre-push branch RuboCop gate also passed. Its Markdown link phase could not run because release-branch divergence selected unrelated Markdown files and the installed Lychee version cannot parse the current .lychee.toml; no Markdown or configuration file changes in this PR.

Release context

  • Base: release/17.0.0
  • Release tracker Release gate: react_on_rails 17.0.0 #3823: closed as released; repository mode returned to development.
  • Release-branch phase gate: rc.
  • Intended train: 17.0.1 patch work.
  • No tag, package publication, version bump, or release action is included.

Codex Decision Log

Addresses #4629.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2a99f6d-d09c-4627-9f8d-9f21b58fe7ba

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/4629-rsc-diagnostics-redaction-release

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR redacts browser-visible RSC diagnostics outside explicit development and test environments. The main changes are:

  • Pass the Rails environment into the RSC payload injector.
  • Preserve full rendering-error details in development and test.
  • Emit only hasErrors for production, custom, or missing environments.
  • Add focused tests for disclosure, redaction, and first-error behavior.

Confidence Score: 5/5

This looks safe to merge.

  • The production caller passes the required Rails environment.
  • Missing and custom values use the secure redacted behavior.
  • No blocking issues were found in the changed code.

Important Files Changed

Filename Overview
packages/react-on-rails-pro/src/injectRSCPayload.ts Adds environment-aware redaction while preserving the browser-visible error signal.
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts Passes the required Rails environment to the RSC payload injector.
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts Covers full diagnostics in development and test, redaction for production and missing values, and first-error behavior.

Reviews (1): Last reviewed commit: "Prevent RSC diagnostics leaking outside ..." | Re-trigger Greptile

@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review: [Pro] Backport production RSC diagnostic redaction

Overview

Focused security backport for #4629 that redacts renderingError (message/stack) from the RSC diagnostic script tag injected into the client HTML unless railsContext.railsEnv is explicitly development or test. All other values (production, custom envs, undefined) fail closed to { hasErrors: true }.

Correctness

  • The allowlist approach (railsEnv === 'development' || railsEnv === 'test', else redact) is the right shape for a disclosure fix — safer than a denylist that could miss an unanticipated custom/staging env name.
  • Single production call site (streamServerRenderedReactComponent.ts:234) correctly threads railsContext.railsEnv through; RailsContext.railsEnv is a required string (packages/react-on-rails/src/types/index.ts:23), so the undefined branch only triggers defensively when a caller omits the option — matches the "missing" test case.
  • Forcing hasErrors: true in the redacted branch (even when the actual hasErrors was false but renderingError carried a signal) is intentional, not a bug: this function only executes past the hasRenderingErrorSignal guard when an error signal already exists, and the client's rscDiagnostics.ts fallback message logic depends on hasErrors to decide whether to show a generic error. So the redacted payload still correctly signals "an error occurred" without leaking details.
  • No other RSC-payload fields (consoleReplayScript, serializedProps) are exposed in either branch — that redaction predates this PR and is untouched.
  • Diff is cleanly scoped to the 3 files listed; no incidental changes to reportError/emitError/node-renderer paths, matching the PR's stated boundary.

Test coverage

  • Good coverage for development, test, production, and missing (undefined) railsEnv values, using a distinguishable secret marker string to make leakage assertions unambiguous.
  • Minor gap: the PR description explicitly claims "Fail closed for production, custom environments, and missing environment values," but no test exercises an arbitrary non-standard value (e.g. 'staging' or 'uat'). The current ternary trivially handles it (anything not 'development'/'test' redacts), but for a security-sensitive redaction path, an explicit it.each case for a custom string would directly verify the claim made in the PR description rather than relying on the shape of the conditional. Left as an inline suggestion.

Other notes

  • No CHANGELOG.md changes — consistent with the PR's stated intent to leave changelog assembly to the 17.0.1 release-train work, and confirmed by git log/diff — this is a reasonable call for a release-branch security backport in RC phase.
  • Didn't independently re-run the Jest suite in this sandbox (tool execution was blocked), so I'm relying on the PR's stated verification output, but reviewed the logic/tests by hand and didn't find any correctness issues.

Overall: solid, narrowly-scoped fix. No blocking issues found.

expect(resultStr).not.toContain('consoleReplayScript');
},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit/suggestion: this it.each covers production and missing (undefined), but the PR description also claims redaction for "custom environments." Consider adding a third case like ['staging', 'staging'] here to directly verify that claim, since the ternary in injectRSCPayload.ts (railsEnv === 'development' || railsEnv === 'test' ? ... : { hasErrors: true }) redacts anything outside the two allowlisted values — an explicit custom-env case would pin that behavior for this security-sensitive path rather than relying on the shape of the conditional.

@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 63.54 KB (0%)
react-on-rails/client bundled (gzip) (time) 63.54 KB (0%)
react-on-rails/client bundled (brotli) 54.55 KB (0%)
react-on-rails/client bundled (brotli) (time) 54.55 KB (0%)
react-on-rails-pro/client bundled (gzip) 64.55 KB (0%)
react-on-rails-pro/client bundled (gzip) (time) 64.55 KB (0%)
react-on-rails-pro/client bundled (brotli) 55.49 KB (0%)
react-on-rails-pro/client bundled (brotli) (time) 55.49 KB (0%)
registerServerComponent/client bundled (gzip) 135.08 KB (0%)
registerServerComponent/client bundled (gzip) (time) 135.08 KB (0%)
registerServerComponent/client bundled (brotli) 81.47 KB (0%)
registerServerComponent/client bundled (brotli) (time) 81.45 KB (-0.04% 🔽)
wrapServerComponentRenderer/client bundled (gzip) 127.5 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 127.5 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 74.61 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 74.61 KB (0%)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3421c63c9a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +110 to +111
const diagnostic =
railsEnv === 'development' || railsEnv === 'test' ? { hasErrors, renderingError } : { hasErrors: true };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Redact diagnostics on direct RSC payload fetches

This environment gate only protects diagnostics that are embedded by injectRSCPayload, but production clients can still hit the RSC payload route during navigation/refetch: getReactServerComponent.client.ts:417 and prefetchServerComponent.client.ts:82 call fetchRSC, and the Rails helper still frames the response with chunk.except("html").to_json (react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb:1357). When an RSC component errors on that path, the HTTP response body still contains the full renderingError.message/source-mapped stack in production, so the disclosure remains outside the initial streamed-HTML case; please apply the same rails-env redaction before framing the payload response.

Useful? React with 👍 / 👎.

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