Skip to content

Redact RSC rendering error details from client DOM in production#4631

Open
AbanoubGhadban wants to merge 8 commits into
mainfrom
4629-rsc-render-errors-leak-server-stack
Open

Redact RSC rendering error details from client DOM in production#4631
AbanoubGhadban wants to merge 8 commits into
mainfrom
4629-rsc-render-errors-leak-server-stack

Conversation

@AbanoubGhadban

@AbanoubGhadban AbanoubGhadban commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #4629 — Two issues with RSC render errors in production:

  1. Security: RSC render errors leaked full server message and source-mapped stack into client DOM via REACT_ON_RAILS_RSC_ERRORS inline <script> tags
  2. Observability: With default config (throwJsErrors: false), rendering errors never reached error reporters (Sentry/Honeybadger)

Production error redaction

  • Gate at the DOM injection boundary (createRSCDiagnosticScript): outside development/test, emit only { hasErrors: true } — no message, no stack, no file paths
  • Fail-closed allowlist: only explicit development or test envs show full diagnostics; all other envs (production, staging, uat, undefined) redact by default — safe for a security gate
  • Wire protocol unchanged: buildRenderMetadata still sends the full renderingError to Ruby so raise_prerender_error and server-side error handling work as before

Error reporting via custom stream event

  • Problem: emitError() emits standard Node.js 'error' events which text() from stream/consumers rejects on — can't just remove the throwJsErrors gate
  • Solution: Custom 'renderingError' stream event that standard consumers (text(), pipe(), async iterators) ignore, but the node renderer listens for
  • Deduplication: WeakSet<Error> in vm.ts ensures same error is reported exactly once, even when throwJsErrors: true causes both events to fire
  • Consumer abort safety: notifyRenderingError mirrors emitError's abort guard — no reporting after client disconnect

Changes

File What
streamingUtils.ts Add notifyRenderingError to bufferStream() and transformRenderStreamChunksToResultObject() — buffers custom events before reading starts, wraps with consumer-abort guard
streamServerRenderedReactComponent.ts Thread railsContext.railsEnv into injectRSCPayload options; call notifyRenderingError in reportError when !throwJsErrors
proRSC.ts Call notifyRenderingError in reportError when !throwJsErrors; remove misleading console.error (Sentry doesn't capture console)
vm.ts (node renderer) Listen for 'renderingError' on raw stream; deduplicate with WeakSet<Error> against handleStreamError's 'error' handler
injectRSCPayload.ts Add railsEnv param; fail-closed allowlist (development/test show full diagnostics, everything else redacts)
injectRSCPayload.test.ts 4 tests: production redaction, development full diagnostics, staging redaction, hasErrors:false edge case
streamServerRenderedReactComponent.test.jsx 3 tests: renderingError emission on shell/async errors with throwJsErrors:false, no emission when throwJsErrors:true
CHANGELOG.md Entry under [Unreleased] > Fixed

Test plan

  • All 141 streaming tests pass (5 suites)
  • All 23 RSC tests pass (7 suites)
  • TypeScript clean on both packages
  • Prettier + ESLint clean
  • CI passes

Release / cherry-pick

This is a security fix (production RSC render errors leaking the server error
message and source-mapped stack — including file paths and potentially user data —
into the client DOM). Candidate for cherry-pick into the 17.0.1 patch release.
The behavior change is intentional security hardening: setups that never set
railsEnv now redact by default (fail-closed allowlist — full diagnostics only for
explicit development/test).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Prevented RSC rendering error details (message/stack/file paths) from leaking into client output in production, staging, and when environment is unspecified.
    • Kept full rendering error diagnostics for development and test.
    • Improved rendering-error observability when JavaScript errors are not thrown, ensuring proper error notification via the streaming pipeline.
    • Avoided duplicate stream error reports and standardized error handling.
  • Documentation
    • Updated the changelog to reflect the improved RSC error privacy and reporting behavior.
  • Tests
    • Expanded coverage for environment-specific RSC error redaction and rendering-error event routing.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR redacts RSC render error details from the client DOM in production. The main changes are:

  • Adds a railsEnv option to RSC payload injection.
  • Emits only hasErrors in production diagnostic scripts.
  • Threads railsContext.railsEnv through the streaming render path.
  • Adds tests for production, development, and missing-env behavior.

Confidence Score: 4/5

The standard streaming path looks correct, but the exported injection helper can still expose production error details when the new option is omitted.

  • streamServerRenderedReactComponent now passes the Rails environment.
  • injectRSCPayload keeps full diagnostics when railsEnv is missing.
  • Direct production callers can still write server messages and stacks into the client DOM.

packages/react-on-rails-pro/src/injectRSCPayload.ts

Security Review

Production redaction works for the standard streaming path, but direct callers that omit railsEnv can still expose server error messages and stacks in the client DOM.

Important Files Changed

Filename Overview
packages/react-on-rails-pro/src/injectRSCPayload.ts Adds production redaction at the diagnostic script boundary, but leaves omitted railsEnv on the verbose path.
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts Passes railsContext.railsEnv into RSC payload injection for the standard streaming render path.
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts Adds coverage for production redaction, development diagnostics, and legacy missing-env behavior.

Reviews (1): Last reviewed commit: "Redact RSC rendering error details from ..." | Re-trigger Greptile

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

RSC stream errors now propagate through a buffered renderingError channel and reach error reporters without requiring JavaScript throws. RSC client diagnostics receive railsEnv; production and other non-development/test payloads retain only hasErrors, while development and test payloads include rendering details.

Changes

RSC error propagation and reporting

Layer / File(s) Summary
Rendering-error notification channel
packages/react-on-rails-pro/src/streamingUtils.ts, packages/react-on-rails-pro/src/capabilities/proRSC.ts, packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts, packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx, packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js
Buffers and replays renderingError events, exposes notifyRenderingError, routes non-throwing stream errors through the notification channel, and tests event routing and reporting.
Deduplicated stream reporting
packages/react-on-rails-pro-node-renderer/src/worker/vm.ts, packages/react-on-rails-pro-node-renderer/tests/vm.test.ts
Centralizes stream error formatting, normalizes thrown values, preserves VM-realm errors, and prevents duplicate error reports.
Environment-aware diagnostic injection
packages/react-on-rails-pro/src/injectRSCPayload.ts, packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts, packages/react-on-rails-pro/tests/injectRSCPayload.test.ts, CHANGELOG.md
Threads railsEnv into RSC payload injection, redacts rendering details outside development and test, validates environment-specific behavior, and documents the change.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant streamServerRenderedComponent
  participant StreamingUtils
  participant runInVM
  participant ErrorReporter
  participant injectRSCPayload
  streamServerRenderedComponent->>StreamingUtils: notifyRenderingError(error)
  StreamingUtils->>runInVM: emit renderingError
  runInVM->>ErrorReporter: report formatted error once
  streamServerRenderedComponent->>injectRSCPayload: pass railsContext.railsEnv
  injectRSCPayload->>injectRSCPayload: redact renderingError outside development and test
Loading

Possibly related issues

  • #4736 — Extends the same rendering-error redaction protection to fetched RSC payloads.
  • #4737 — Requests tests for the VM renderingError listener and deduplication behavior.

Possibly related PRs

Suggested labels: ready-for-hosted-ci

Suggested reviewers: justin808

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: redacting RSC rendering errors from the client DOM in production.
Linked Issues check ✅ Passed The PR redacts client-visible RSC diagnostics outside dev/test and routes rendering errors through reporting, matching #4629's goals.
Out of Scope Changes check ✅ Passed The added stream, VM, and test changes support the same redaction/reporting work and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 4629-rsc-render-errors-leak-server-stack

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.

@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: baf9c60644

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated
Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated
@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

Fixes #4629 by redacting the RSC diagnostic script's renderingError (message + source-mapped stack) from the client-visible DOM when railsEnv === 'production', while keeping the wire protocol to Ruby and the onDiagnosticError server-side hook unchanged. Change is scoped to createRSCDiagnosticScript in injectRSCPayload.ts, with railsEnv threaded through from streamServerRenderedReactComponent.ts's railsContext.railsEnv.

Strengths

  • Correct fix location: gates at the DOM-injection boundary rather than trying to scrub upstream, so raise_prerender_error and other server-side error handling paths are untouched.
  • Backwards compatible: railsEnv is optional and defaults to full diagnostics when omitted, matching prior behavior for any direct callers of injectRSCPayload.
  • Client-side consumer (buildRSCStreamDiagnosticError in rscDiagnostics.ts) already tolerates a missing renderingError and falls back to a generic message keyed off hasErrors, so no crash/regression there.
  • New tests clearly cover production redaction, development full-diagnostics, and the undefined-backwards-compat case.

Main concern: denylist env check (see inline comment)

The redaction gate is railsEnv === 'production' — a denylist. Any RAILS_ENV that isn't spelled exactly "production" (e.g. staging, uat, demo, or other custom env names many teams use for internet-facing/customer-facing tiers) will still leak the full server error message and stack trace (including file paths) to the client DOM. Since this PR exists specifically to close a leak, I'd suggest flipping to a fail-safe allowlist (e.g., only emit full diagnostics when railsEnv is explicitly development/test), consistent with the allowlist pattern already used in rootErrorHandlers.ts's inDevelopmentEnv(). Left a detailed inline comment with a suggested test case (staging-like custom env) to make the intended behavior explicit.

Minor

  • No CHANGELOG.md entry. Per this repo's changelog guidelines, user-visible fixes (especially security-relevant ones) should get a **[Pro]**-tagged entry under #### Fixed.

Other

  • No security/performance issues beyond the above; the diff is small, well-isolated, and the wire-protocol/backwards-compat reasoning in the PR description checks out against the code.

@github-actions

github-actions Bot commented Jul 13, 2026

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.92 KB (0%)
react-on-rails-pro/client bundled (gzip) (time) 64.92 KB (0%)
react-on-rails-pro/client bundled (brotli) 55.82 KB (0%)
react-on-rails-pro/client bundled (brotli) (time) 55.82 KB (0%)
registerServerComponent/client bundled (gzip) 135.42 KB (0%)
registerServerComponent/client bundled (gzip) (time) 135.42 KB (0%)
registerServerComponent/client bundled (brotli) 81.72 KB (0%)
registerServerComponent/client bundled (brotli) (time) 81.72 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) 127.87 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 127.87 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 74.88 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 74.88 KB (0%)

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts (1)

471-493: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test for the edge case where hasErrors is not true but renderingError has a signal.

The production test only covers hasErrors: true. The early return in createRSCDiagnosticScript (line 108) also proceeds when hasRenderingErrorSignal(renderingError) is true even if hasErrors is not true. A test for that path would verify the client still receives hasErrors: true in production and that renderingError details are redacted.

🧪 Suggested additional test
  it('redacts renderingError and signals hasErrors:true in production even when hasErrors is not true', async () => {
    const mockRSC = createMockRSCStreamWithMetadata('{"test": "data"}', {
      hasErrors: false,
      renderingError: {
        message: 'Internal server failure',
        stack: 'Error: Internal server failure\n    at Server (/app/lib/server.ts:42:7)',
      },
    });
    const mockHTML = createMockHTMLStream(['<html><body><div>Hello, world!</div></body></html>']);
    const { rscRequestTracker, domNodeId } = setupTest(mockRSC);

    const result = injectRSCPayload(mockHTML, rscRequestTracker, domNodeId, undefined, {
      railsEnv: 'production',
    });
    const resultStr = await collectStreamData(result);

    expect(resultStr).toContain('"hasErrors":true');
    expect(resultStr).not.toContain('Internal server failure');
    expect(resultStr).not.toContain('server.ts');
    expect(resultStr).not.toContain('renderingError');
  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-on-rails-pro/tests/injectRSCPayload.test.ts` around lines 471
- 493, Add a production test alongside the existing injectRSCPayload diagnostic
tests using hasErrors: false with a populated renderingError. Verify the
collected result still reports hasErrors as true while redacting the rendering
error message, stack details, and renderingError field.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-on-rails-pro/src/injectRSCPayload.ts`:
- Line 111: Update the production branch of the clientPayload construction in
injectRSCPayload so it always emits hasErrors: true whenever the early-return
path is reached, including renderingError signal cases; preserve the
non-production payload’s existing hasErrors and renderingError values.

---

Nitpick comments:
In `@packages/react-on-rails-pro/tests/injectRSCPayload.test.ts`:
- Around line 471-493: Add a production test alongside the existing
injectRSCPayload diagnostic tests using hasErrors: false with a populated
renderingError. Verify the collected result still reports hasErrors as true
while redacting the rendering error message, stack details, and renderingError
field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05958d85-254a-4e62-b870-8444eafd6601

📥 Commits

Reviewing files that changed from the base of the PR and between 2ade196 and baf9c60.

📒 Files selected for processing (3)
  • packages/react-on-rails-pro/src/injectRSCPayload.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated
Comment thread packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated
@AbanoubGhadban

Copy link
Copy Markdown
Collaborator Author

All review feedback addressed in af56d88:

  1. Allowlist instead of denylist — flipped from === 'production' to an allowlist: only development/test show full diagnostics. Custom envs like staging/uat now redact by default.
  2. Force hasErrors: true — production payload hardcodes true instead of passing through the metadata value.
  3. CHANGELOG entry — added under #### Fixed in Unreleased.
  4. New tests — staging env redaction + hasErrors: false with renderingError signal edge case.

undefined railsEnv preserves full diagnostics for backwards compatibility (only matters for direct callers; the standard render path always provides it).

@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review: Redact RSC rendering error details from client DOM in production

Overview
Fixes two real problems with RSC render errors: (1) Security — createRSCDiagnosticScript in injectRSCPayload.ts no longer emits the full server message/source-mapped stack into the client DOM when railsEnv === 'production'; only { hasErrors: true } is sent. Full diagnostics still go to Rails via buildRenderMetadata unchanged, so server-side handling (raise_prerender_error, etc.) is unaffected. (2) Observability — adds a renderingError custom stream event (streamingUtils.ts, bufferStream/transformRenderStreamChunksToResultObject) that the node renderer (vm.ts) listens for and reports via errorReporter.message(...), so errors reach Sentry/Honeybadger even when throwJsErrors: false (the default). A WeakSet<Error> in vm.ts deduplicates in case both the standard error and the new renderingError fire for the same error.

Code quality
Clean, well-scoped diff. The custom event name (rather than reusing error) is well-motivated since stream/consumers's text() rejects on error, so a distinct event avoids breaking existing consumers while still allowing the render to continue. bufferStream's buffering/replay logic for the new event is symmetric with the existing error handling — verified it doesn't rely on the source stream itself ever emitting renderingError (it never does; the event only travels through the manually-invoked notifyRenderingError closure), so there's no dead code path here. Removing the console.error('Error in RSC stream', ...) in proRSC.ts is safe: errorReporter.message() already calls log.error(...) internally regardless of whether a Sentry/Honeybadger notifier is registered, so no logging visibility is lost. Also verified railsContext.railsEnv is a required string field (not optional), always populated by react_on_rails/lib/react_on_rails/helper.rb (Rails.env), and injectRSCPayload is only used from the HTML-embedding path — the direct RSC-only path (proRSC.ts) never calls it, so there's no gap where a caller could forget to thread railsEnv through and silently leak in production.

Issues found (posted inline)
Test coverage gap on vm.ts: no test exercises the new renderingErrorerrorReporter.message() path, nor the WeakSet dedup. Existing tests (streamServerRenderedReactComponent.test.jsx) only assert the standard error event is not emitted when throwJsErrors: false — they don't assert the new custom event is emitted, so the actual observability fix this PR claims isn't verified by CI. Minor: redaction is gated purely on railsEnv === 'production'; other internet-facing non-dev environments (staging/uat) still get full stack traces in the DOM, same as before — likely intentional but worth a short comment noting the scope.

Nits (not blocking)
PR description says it deletes 1 redundant backwards-compat test, but the diff to injectRSCPayload.test.ts shows only additions — no test removal is present. Also, no react_on_rails_pro/CHANGELOG.md entry for this security-relevant fix.

Security
The core fix is correct and effective: production DOM payloads no longer include message/stack. escapeScript's </script and <!-- escaping (pre-existing, unmodified) still protects the dev/staging path where full diagnostics are intentionally emitted.

Overall: solid, well-motivated fix. Main ask is closing the test-coverage gap on the error-reporting side before merge.

@AbanoubGhadban

Copy link
Copy Markdown
Collaborator Author

All issues from this review are addressed in later commits:

  1. Test coverage gap — addressed in aa46ce4: 3 new tests verify 'renderingError' emission for shell errors (throwJsErrors:false), async errors (throwJsErrors:false), and mutual exclusivity with standard 'error' event (throwJsErrors:true).

  2. Staging/uat env scope — addressed in af56d88: flipped from denylist (=== 'production') to allowlist (only development/test show full diagnostics). Staging, uat, and all other envs now redact by default.

  3. PR description mentions test deletion but not visible — the test deletion is in the second commit (d801748), which removes the redundant backwards-compat test from injectRSCPayload.test.ts.

  4. CHANGELOG entry — added in af56d88 under #### Fixed in Unreleased.

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated
Comment thread packages/react-on-rails-pro/src/streamingUtils.ts
@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review

Reviewed the diff (redaction gate in injectRSCPayload.ts, the new renderingError stream event plumbing in streamingUtils.ts/proRSC.ts/streamServerRenderedReactComponent.ts, and the node-renderer dedup in vm.ts).

Design is sound overall:

  • Gating redaction at the single DOM injection boundary (createRSCDiagnosticScript) rather than scattering checks is the right call — one place to audit.
  • Allowlist (development/test show full diagnostics, everything else redacts) is the safer choice vs. a denylist on 'production', correctly catching staging/uat/etc.
  • Forcing hasErrors: true in the redacted payload (rather than passing through a possibly-false value) closes a real edge case, and there's a test for it.
  • The WeakSet<Error> dedup in vm.ts is well-motivated: streamServerRenderedReactComponent.ts's .catch handler can call reportError a second time with the same Error instance that onError already reported (since convertToError returns the identical object for real Errors), so without dedup the same failure could get double-reported to Sentry/Honeybadger. Scoped correctly per-request (inside runInVM), no cross-request leakage.
  • Redaction correctly drops the entire renderingError object rather than trying to scrub specific fields, so it can't be defeated by unexpected extra fields on that object.

Two things worth a look (posted as inline comments):

  1. injectRSCPayload.ts:114 — the redaction gate fails open when railsEnv is undefined/unrecognized (falls back to full diagnostics). For a security-hardening PR, defaulting to redact-unless-explicitly-safe would be more robust than reveal-unless-explicitly-unsafe, in case a future caller ever fails to thread railsContext.railsEnv.
  2. streamingUtils.ts:180 (and the corresponding vm.ts listener) — the new notifyRenderingError/'renderingError' event path, which is the entire mechanism for the "errors now reach Sentry/Honeybadger with throwJsErrors: false" fix, has no test coverage anywhere in the repo. Existing tests only assert the standard 'error' event does not fire when throwJsErrors: false; none assert 'renderingError' does fire or that it reaches errorReporter.message.

No other correctness, performance, or security issues found in the diff.

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts
Comment thread packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review: #4631 — Redact RSC rendering error details from client DOM in production

Overview

This PR closes two real gaps around RSC render errors:

  1. Redacts the full server error message/stack/file paths from the REACT_ON_RAILS_RSC_ERRORS inline diagnostic script when railsEnv isn't development/test (allowlist, so staging/uat default to redacted).
  2. Adds a 'renderingError' custom stream event (notifyRenderingError) so rendering errors reach the configured error reporter (Sentry/Honeybadger) even with the default throwJsErrors: false, without breaking standard stream consumers (text(), pipe()) that would otherwise reject on a plain 'error' event.

The streamingUtils.ts plumbing (buffering 'renderingError' alongside 'data'/'error'/'end', mirroring emitError's consumer-abort guard) is well done, and the vm.ts WeakSet<Error> dedup is a sensible defensive measure against double-reporting the same error via both the 'error' and 'renderingError' paths. Test coverage for the injectRSCPayload redaction behavior (prod/staging/dev, plus the "force hasErrors: true" case) is thorough.

Main concern: the fix doesn't cover the other place the same data leaks

createRSCDiagnosticScript is only one of (at least) two places where a server render error's full message/stack reaches the client. The other is the shell-error HTML fallback (<pre>Exception in rendering!...Message: ...\n\n{stack}</pre>), built by generateRenderingErrorMessage.ts and used via handleError.ts / handleErrorRSC.ts. This is invoked directly from sendErrorHtml in streamServerRenderedReactComponent.ts (onShellError / the outer .catch) and piped straight into the HTTP response stream — no railsEnv gating at all.

Concretely, in production:

  • raise_on_prerender_error defaults to Rails.env.development? (react_on_rails/lib/react_on_rails/configuration.rb), i.e. false in production.
  • With that false, ReactOnRails::Helper#server_rendered_react_component (helper.rb ~line 1014) swallows the render error and returns the raw result["html"] — which is exactly the <pre> block containing the full message and stack — marked .html_safe and injected into the page (helper.rb ~679/704).
  • For the Pro streaming path, the HTML chunk from handleError/handleErrorRSC is piped directly into the response as it's generated, before any Ruby-side rescue could intercept it.

So today, a production render error still lands the full server error message + stack trace directly in the page's HTML source (not just JS-readable via a script tag) — arguably a more severe version of the exact issue this PR sets out to fix (#4629), just via a different code path that predates this PR and wasn't touched by it.

Given the PR title/description explicitly frames this as closing the "leak full server message and stack into client DOM" issue, I'd flag this as a gap worth addressing (either in this PR or as an immediate fast-follow) — e.g. apply the same railsEnv allowlist gating to generateRenderingErrorMessage/handleError/handleErrorRSC so production shows a generic fallback instead of the raw exception text.

Other notes (also left inline)

  • injectRSCPayload.ts: showFullDiagnostics fails open when railsEnv is undefined (shows full diagnostics). For a security-redaction gate, failing closed (redact by default, opt into full diagnostics) is safer against future callers that forget to thread railsEnv.
  • vm.ts: the new 'renderingError' listener + WeakSet dedup + errorReporter.message() call (the piece that actually fixes the Sentry/Honeybadger observability gap) has no direct test coverage — existing errorReporter.message assertions all go through the standard 'error' event path, not the new custom event.

Nice-to-have but not blocking: the CHANGELOG/PR description says "delete 1 redundant backwards-compat test" — confirmed in the diff, no concerns there.

Comment thread packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts
@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review: Redact RSC rendering error details from client DOM in production (#4631)

Overview

Two fixes bundled together:

  1. Security: createRSCDiagnosticScript now redacts message/stack/file paths from the REACT_ON_RAILS_RSC_ERRORS inline script outside development/test railsEnv, while still sending full diagnostics to Rails over the wire protocol.
  2. Observability: a new renderingError custom stream event (notifyRenderingError/bufferStream) lets throwJsErrors: false renders still reach the error reporter (Sentry/Honeybadger) via the node renderer's vm.ts, without breaking text()-based stream consumers the way a standard error event would.

What's solid

  • Fail-closed redaction gate: showFullDiagnostics = railsEnv === 'development' || railsEnv === 'test' is an allowlist, not a !== 'production' denylist -- an unset/unknown/custom (e.g. staging) env redacts by default, which is the right default for a security-sensitive gate.
  • Wire protocol left untouched: full renderingError still reaches Ruby (raise_prerender_error etc.), so server-side error handling is unaffected -- only the client-visible DOM payload changes.
  • Custom event choice is well-reasoned: using renderingError instead of piggybacking on error avoids stream/consumers' text() rejecting on non-throwing renders, and the notifyRenderingError guard mirrors emitError's consumer-abort check so nothing gets reported after a client disconnect.
  • Good new unit-test coverage in injectRSCPayload.test.ts for the redaction matrix (dev/test/staging/production, including the hasErrors:false + signal edge case).

Issues found (see inline comments)

  • Test coverage gap on the observability fix (vm.ts): the new WeakSet dedup + result.on('renderingError', ...) listener in the node renderer has no direct unit test, and the existing e2e assertions in tests/htmlStreaming.test.js (lines 232-238 and 291-297, "should not notify error reporter when throwJsErrors is false...") still assert the old, pre-fix behavior (errorReporter.message not called). Those tests run against a prebuilt fixture bundle (tests/fixtures/projects/spec-dummy/220f7a3/server-bundle-web-target.js) that predates this PR and contains no notifyRenderingError reference, so they currently pass because they exercise stale compiled code, not because the new behavior was verified. As it stands, the PR's core "errors now reach Sentry/Honeybadger with default config" claim isn't actually proven by CI. Worth adding a focused unit test for the vm.ts dedup/listener and/or regenerating the fixture bundle so htmlStreaming.test.js flips to asserting the reporter is called.
  • Minor PR-description inaccuracy: the description states that when railsEnv is not provided (undefined), full diagnostics are emitted (matching previous behavior), but the implementation does the opposite -- undefined redacts, per the code's own "fail-closed" comment. The code's choice is the safer one; just flagging the description so it doesn't mislead future readers/integrators.

Other notes

  • The WeakSet<Error> dedup in vm.ts is a reasonable safety net for cases where both error and renderingError could theoretically fire for the same error object, though I couldn't find a concrete code path in this diff where that happens for the same Error instance today -- worth double-checking it's not defending against a scenario that can't currently occur.
  • No security concerns beyond the one already being fixed; railsEnv is server-controlled (Rails.env), not attacker-influenced.

Comment thread packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
Comment thread packages/react-on-rails-pro/src/streamingUtils.ts

@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: 534a919ef7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts
@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review: Redact RSC rendering error details from client DOM in production

Overview
Good, well-scoped security fix for issue 4629. Two changes:

  1. Redaction: createRSCDiagnosticScript now emits only { hasErrors: true } to the client outside development/test (fail-closed allowlist - good default, since it protects unnamed/staging/custom Rails envs too).
  2. Observability: introduces a 'renderingError' custom stream event (distinct from the standard 'error' event that stream/consumers APIs reject on) so render errors reach Sentry/Honeybadger even with the default throwJsErrors: false.

The redaction logic (injectRSCPayload.ts) is clean, the fail-closed default is the right call for a security gate, and the client-side fallback (buildRSCStreamDiagnosticError in rscDiagnostics.ts) already degrades gracefully when message/stack are absent, so nothing crashes when the redacted {hasErrors: true} payload reaches the client. New unit tests in injectRSCPayload.test.ts and streamServerRenderedReactComponent.test.jsx cover the new behavior well.

Likely test-suite regression (please verify before merge)
packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js (not touched by this PR) has two pre-existing assertions that look like they now contradict the intended behavior:

it("shouldn't notify error reporter when throwJsErrors is false and shell error happens", ...)
  expect(errorReporter.message).not.toHaveBeenCalled();

it('should not notify error reporter when throwJsErrors is false and async error happens', ...)
  expect(errorReporter.message).not.toHaveBeenCalled();

Both use the AsyncComponentsTreeForTesting fixture, which calls ReactOnRails.streamServerRenderedReactComponent with throwJsErrors: false - exactly the path modified in streamServerRenderedReactComponent.ts's reportError. After this PR, that now calls notifyRenderingError, which fires the 'renderingError' stream event, which vm.ts's new unconditional listener forwards to errorReporter.message(). That's the intended fix (errors should reach Sentry/Honeybadger even when throwJsErrors: false), but it directly contradicts these two not.toHaveBeenCalled() assertions.

The PR's test-plan checklist references '141 streaming tests / 5 suites' and '23 RSC tests / 7 suites' in packages/react-on-rails-pro, but doesn't appear to include the react-on-rails-pro-node-renderer integration suite where htmlStreaming.test.js lives. Worth running that suite (pnpm test tests/htmlStreaming.test.js in packages/react-on-rails-pro-node-renderer) to confirm and update these two tests to assert the new behavior (mirroring the positive assertions already added elsewhere in this PR). Left as an inline comment on vm.ts with more detail.

Minor

  • CHANGELOG wording ('RSC render errors ... RSC rendering errors are now also reported') undersells scope - the streamServerRenderedReactComponent.ts change affects all streaming component renders, not just RSC ones (that's exactly why the non-RSC htmlStreaming.test.js fixture above is affected).
  • Small DRY nit on the duplicated consumerAborted guard between emitError/notifyRenderingError in streamingUtils.ts - left inline, not blocking.
  • The WeakSet<Error> dedup in vm.ts is identity-based, so it only collapses duplicate reports when the exact same Error object triggers both paths; unrelated Error instances wrapping the same underlying failure (e.g. via addRSCClientHookDiagnostic) won't dedupe. Likely fine in practice, just noting the limitation.

Security
The core fix is sound: fail-closed allowlist, wire protocol to Rails unchanged (server-side error handling / raise_prerender_error unaffected), and no full error detail reaches the client DOM outside dev/test. This is a real improvement.

AbanoubGhadban and others added 6 commits July 17, 2026 17:25
In production, the `REACT_ON_RAILS_RSC_ERRORS` inline script now emits
only `{ hasErrors: true }` instead of the full `renderingError` object
(message + stack trace with source-mapped file paths). Full diagnostics
remain available server-side via the `onDiagnosticError` callback
(Sentry/Honeybadger), and the wire protocol to Ruby is unchanged so
`raise_prerender_error` still receives the complete error.

The gate is at `createRSCDiagnosticScript` — the single DOM injection
boundary — keyed on `railsEnv === 'production'`. All other environments
(development, test, staging, undefined) continue to receive full error
details for debugging. The client-side `buildRSCStreamDiagnosticError`
already handles the absence of `renderingError` gracefully, producing a
generic fallback message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
With default config (throwJsErrors: false), RSC rendering errors were
silently swallowed — never reaching error reporters like Sentry. Add a
custom 'renderingError' stream event that the node renderer listens for,
avoiding the standard 'error' event which would break stream consumers
like text(). Uses WeakSet dedup so errors are reported exactly once
even when throwJsErrors is true.

Also removes redundant backwards-compat test and fixes misleading
onDiagnosticError comment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Flip from denylist (=== 'production') to allowlist: only development/test
  show full diagnostics; all other explicit envs (staging, uat, etc.) redact.
  Undefined railsEnv preserves full diagnostics for backwards compatibility.
- Force hasErrors:true in redacted payload (was passing through the
  destructured value, which could be false when renderingError signal exists)
- Add tests for staging env redaction and hasErrors:false edge case
- Add CHANGELOG entry for the fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test that the custom 'renderingError' stream event is emitted when
throwJsErrors is false (both shell and async error paths), and that
the standard 'error' event is used instead when throwJsErrors is true.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
For a security gate, unknown/missing railsEnv should default to
redacted (safe) rather than full diagnostics (leak risk). Only
explicitly development/test envs now show full error details.

Updated pre-existing tests that relied on undefined railsEnv to
pass railsEnv: 'test' explicitly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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: daff2b5789

ℹ️ 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 thread packages/react-on-rails-pro-node-renderer/src/worker/vm.ts Outdated
Comment thread packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review summary

Solid, well-scoped fix for a real security issue (production RSC render errors leaking server message/stack into client DOM) plus a genuine observability gap (silent errors when throwJsErrors: false). Reviewed the full diff (gh pr diff 4631) and the surrounding call sites in this checkout.

What's good

  • Fail-closed allowlist (railsEnv === 'development' || 'test') in createRSCDiagnosticScript is the right default for a security gate — unset/unknown envs (staging, undefined) redact rather than leak.
  • Wire protocol left intact: buildRenderMetadata still sends the full error server-to-server (Rails raise_prerender_error etc. unaffected) — only the DOM injection boundary is redacted. Correctly scoped.
  • Single call site for injectRSCPayload (streamServerRenderedReactComponent.ts) is updated consistently; no other caller was missed.
  • notifyRenderingError mirrors emitError's consumer-abort guard in streamingUtils.ts, so no error reporting fires after client disconnect — consistent with the existing React 19.2 cacheSignal: settle RSC cache cleanup on render complete/abort (child of #3865) #3885 abort-handling pattern.
  • The convertToError fix in streamServerRenderedComponent's catch block (converting e to an Error before branching on throwJsErrors, instead of after) is a nice incidental correctness fix.
  • Good test coverage on the redaction side: injectRSCPayload.test.ts covers production, staging, undefined-env, and the hasErrors:false-with-signal edge case, plus a development "full diagnostics" control test.

Suggestions (posted inline)

  1. Test coverage gap: the node-renderer side of the fix (vm.ts's new 'renderingError' listener and the WeakSet dedup that prevents double-reporting to Sentry/Honeybadger) has no direct test in this PR. streamErrorHang.test.ts only exercises the pre-existing 'error' path.
  2. Minor asymmetry: createRSCDiagnosticScript forces hasErrors: true in the redacted branch but passes the raw (possibly false) hasErrors through in the full-diagnostics branch. Likely intentional (preserves the historical dev payload shape) but worth a one-line comment since this function is the security gate.

Risk assessment

No correctness bugs or security regressions found. The behavior change (redact-by-default for any non-development/test railsEnv, including unset) is called out clearly in the PR description and CHANGELOG as intentional hardening — reasonable given it's described as a 17.0.1 cherry-pick candidate.

justin808 and others added 2 commits July 17, 2026 18:18
…observability

Addresses the observability half of #4629 (PR #4631) that CI can't cover
(node-renderer jobs are skipped on this PR).

- vm.ts: report stream errors with a realm-agnostic native-Error check
  (util.types.isNativeError + duck-type) instead of `instanceof Error`. A stream
  error thrown inside the sandboxed VM realm fails the worker-realm instanceof
  check, so it was wrapped in `new Error(String(error))`, discarding the original
  message and stack before reaching the error reporter. Genuine Errors (including
  VM-realm ones) now pass through with their message+stack intact; only true
  non-Error throwables are wrapped. (codex P2)

- tests/vm.test.ts: add reception-side coverage for the 'renderingError' listener
  + WeakSet dedup — the reporter fires on a 'renderingError' stream event, a
  genuine VM-realm Error keeps its message/stack (not wrapped), the same Error
  instance is deduped across 'renderingError' and 'error' (reported once), and two
  distinct Errors are both reported.

- tests/htmlStreaming.test.js: the two throwJsErrors:false cases previously
  asserted the reporter was NOT called. Built the dummy server bundle from current
  source and confirmed empirically that the reporter is now called with the
  genuine render error (the intended fix). Updated both to assert the reporter IS
  called with the real error, with a comment explaining the behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sErrors differently

Review nit: the redacted (production) branch forces `hasErrors: true` while the
development/test branch passes the raw metadata through. Add a comment explaining
the intent — production emits a positive generic signal even when only
`hasRenderingErrorSignal` tripped; dev/test preserves the exact historical payload
shape for debugging — since this function is the security gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
@justin808

Copy link
Copy Markdown
Member

+ci-force-full

Requesting the full hosted suite: the standard optimized run skips the node-renderer jobs (package-js-tests, dummy-app-node-renderer-e2e-tests, rspec-dummy-app-node-renderer) even though this PR changes packages/react-on-rails-pro-node-renderer/src/worker/vm.ts and the Pro streaming path. This is a security fix + 17.0.1 cherry-pick candidate, so we want those suites to run on head 59d6268.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Force-Full Hosted CI Requested

Triggered 9 workflow(s) for 59d6268cd8d6.
Skipped 0 workflow(s) with equivalent exact-head coverage.
Mode: force-full hosted CI (bypasses optimized change selection).
Added ready-for-hosted-ci and force-full-hosted-ci, so future commits will bypass optimized hosted CI selection until +ci-stop-full is used.

View progress in the Actions tab.

@github-actions github-actions Bot added force-full-hosted-ci Bypass optimized hosted CI selection and run all hosted suites ready-for-hosted-ci Run optimized hosted GitHub CI for this PR labels Jul 18, 2026
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review: Redact RSC rendering error details from client DOM in production

Overview

This PR fixes two real issues around RSC render errors:

  1. Security: production RSC render errors were leaking full server message + source-mapped stack (including file paths, and potentially user data) into the client DOM via the REACT_ON_RAILS_RSC_ERRORS inline script.
  2. Observability: with the default throwJsErrors: false, render errors never reached error reporters (Sentry/Honeybadger).

Both fixes are well-targeted and the changes are consistent across the touched files.

Correctness

  • createRSCDiagnosticScript in injectRSCPayload.ts is a solid fail-closed allowlist: showFullDiagnostics = railsEnv === development || railsEnv === test. Any unset/unknown env (production, staging, custom envs) redacts by default -- the right posture for a security gate. Confirmed there is only one call site (streamServerRenderedReactComponent.ts), and it correctly threads railsContext.railsEnv through, so there is no gap where diagnostics leak unredacted from a code path that forgot to pass railsEnv.
  • The hasErrors/renderingError normalization is asymmetric by design (redacted branch forces hasErrors: true; dev/test branch passes metadata through unchanged) and is well explained by the inline comment.
  • The renderingError custom stream event (instead of reusing error) is a good solution to the problem that text() from stream/consumers rejects on standard error events. The bufferStream/transformRenderStreamChunksToResultObject plumbing correctly buffers pre-read events and applies the same consumer-abort guard as emitError.
  • The WeakSet-based dedup in vm.ts for the same Error object reported via both renderingError and error is justified -- confirmed via vm.test.ts dedup test that this dual-report scenario is real, not hypothetical, and the realm-crossing util.types.isNativeError check is the right tool for VM-sandboxed errors that fail instanceof Error in the worker realm.
  • Nice incidental fix in streamServerRenderedComponent catch block: error = convertToError(e) is now computed once and used consistently for both emitError/notifyRenderingError and handleError, instead of the previous inconsistency where emitError(e) used the raw exception while handleError used the converted one.
  • Left one inline nitpick on vm.ts: the WeakSet dedup cannot dedupe the same primitive value reported twice (each call wraps it in a fresh Error), but this looks unreachable given current call sites always pass genuine Error objects to notifyRenderingError. Not blocking.

Security

  • The redaction is applied at the right boundary (DOM injection), not just at logging -- good defense-in-depth, since the wire protocol between Ruby and Node still carries the full error (needed for raise_prerender_error etc.), and only the client-facing script is redacted.
  • Fail-closed default (redact unless explicitly development/test) is the correct posture for a security-sensitive gate -- an app that never sets railsEnv, or sets it to something unexpected (e.g. staging, uat), redacts by default rather than leaking.

Test Coverage

  • Good coverage: injectRSCPayload.test.ts covers production/staging/undefined redaction, the hasErrors:false-with-signal edge case, and full dev diagnostics. streamServerRenderedReactComponent.test.jsx and htmlStreaming.test.js/vm.test.ts cover the observability side (event emission, dedup, VM-realm error preservation).
  • One small gap: no test for isErrorLike's duck-typed branch (a plain object with .message/.stack but not a native Error) -- only the native-Error and pure-primitive paths are exercised. Minor, not blocking.

Minor / Nitpick

  • vm.ts's two report labels -- 'Rendering error in stream' vs 'Error in a rendering stream' -- are similarly worded; could make log scanning slightly harder when trying to distinguish which path reported. Purely cosmetic.

Overall

Solid, well-scoped security + observability fix with good test coverage. No blocking issues found.

@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review: Redact RSC rendering error details from client DOM in production

Overall this is a well-structured, well-tested security fix. The redaction gate lives exactly at the DOM-injection boundary (createRSCDiagnosticScript), the wire protocol to Ruby is left untouched (so server-side raise_prerender_error still works), and the fail-closed allowlist (development/test only) is the right default for a security control. I traced the data flow end-to-end and confirmed:

  • createRSCDiagnosticScript has exactly one call site (injectRSCPayload.ts:2026), and it's the only place that builds the REACT_ON_RAILS_RSC_ERRORS script — so there's no other leak path left unredacted.
  • railsEnv is a required field on RailsContext (always Rails.env.to_s/Rails.env from the Ruby side), so the "undefined → redacted" fallback is a true belt-and-suspenders case, not the common path.
  • The new 'renderingError' custom stream event is correctly plumbed through bufferStream/transformRenderStreamChunksToResultObjectproRSC.ts/streamServerRenderedReactComponent.tsvm.ts's dedup + report logic, and pre-existing tests that asserted the old "error reporter NOT called" behavior were correctly flipped to match the new intentional behavior.
  • All pre-existing injectRSCPayload tests that assert on renderingError content were updated to pass railsEnv: 'test' so they keep exercising the "full diagnostics" path rather than silently starting to test the redacted path.

Nice touches

  • The WeakSet<Error> dedup in vm.ts correctly prevents double-reporting when throwJsErrors: true causes both the custom renderingError event and the standard error event to fire for the same error object.
  • isErrorLike/util.types.isNativeError correctly handles VM-realm Error instances that fail a same-realm instanceof Error check, preserving the original stack instead of collapsing it via new Error(String(error)).
  • Removing the console.error('Error in RSC stream', ...) in proRSC.ts is justified (Sentry doesn't capture plain console output) now that notifyRenderingError provides real observability — though see note below on dev-time visibility.

Minor observations (non-blocking)

  1. Dev-time console visibility regression: proRSC.ts's reportError used to console.error on every RSC stream error regardless of throwJsErrors. That's now removed entirely, and notifyRenderingError/emitError route to the error reporter (Sentry/Honeybadger) or the thrown-error path — but a local dev setup without an error reporter configured could now lose console visibility into !throwJsErrors RSC errors that isn't offset by anything else in this file. Worth double-checking that some other layer (e.g. Rails logs via the wire-protocol renderingError, or the node-renderer's own logging) still surfaces this in practice for developers who don't have Sentry wired up locally.
  2. Dedup is reference-based only: the WeakSet<Error> in vm.ts dedups the exact same object instance appearing on both events. If two independent code paths ever produce distinct Error objects for what is logically the same failure (not currently happening per the traced call sites, but worth keeping in mind for future changes), they'd still be reported twice. Not a bug today, just a sharp edge for future maintainers.
  3. Dev/test payload asymmetry: in the redacted branch, hasErrors is always forced to true (even if the server metadata said hasErrors: false but included a non-blank message/stack). In the development/test branch, the raw metadata is passed through unchanged, so it's possible to see { hasErrors: false, renderingError: {...} } in dev — i.e., the two branches don't normalize hasErrors the same way. This is called out in the code comment as intentional (preserving historical dev payload shape), so just flagging for awareness rather than as a defect.

Test coverage

Coverage looks solid — production/staging/undefined redaction, development/test full diagnostics, the hasErrors:false-with-signal edge case, renderingError event emission for shell/async errors under throwJsErrors:false, non-emission under throwJsErrors:true, and dedup across renderingError/error events including the VM-realm-instanceof edge case, are all directly tested. Good adversarial coverage (staging as a "custom env" case, VM-realm errors, distinct-error-not-deduped case).

No correctness bugs or security gaps found in the diff as submitted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

force-full-hosted-ci Bypass optimized hosted CI selection and run all hosted suites ready-for-hosted-ci Run optimized hosted GitHub CI for this PR

Projects

None yet

2 participants