Security audit finding, commit abf4e87.
File: src/core/render.ts:188-210 (renderPipelineStreaming)
Attack / Failure scenario
In renderPipelineStreaming, if an ancestor layout's load() throws before the skeleton flushes, the code sets skeletonChunk = STREAM_ERROR_MARKER with status 500 and never invokes the nearest error.tsx / not-found.
The non-streaming path (renderPipelineCore) and the deferred paths handle this correctly via the error-boundary renderers. As a result, simply adding a loading.tsx to a subtree silently disables error-boundary rendering for that subtree's ancestor-layout loader failures — a surprising, hard-to-notice regression in behavior.
Suggested fix
Run the ancestor phase through the same nearestRenderer("renderError") / nearestRenderer("renderNotFound") path that renderPipelineCore uses, falling back to STREAM_ERROR_MARKER only when no boundary is available.
Security audit finding, commit
abf4e87.File:
src/core/render.ts:188-210(renderPipelineStreaming)Attack / Failure scenario
In
renderPipelineStreaming, if an ancestor layout'sload()throws before the skeleton flushes, the code setsskeletonChunk = STREAM_ERROR_MARKERwith status 500 and never invokes the nearesterror.tsx/not-found.The non-streaming path (
renderPipelineCore) and the deferred paths handle this correctly via the error-boundary renderers. As a result, simply adding aloading.tsxto a subtree silently disables error-boundary rendering for that subtree's ancestor-layout loader failures — a surprising, hard-to-notice regression in behavior.Suggested fix
Run the ancestor phase through the same
nearestRenderer("renderError")/nearestRenderer("renderNotFound")path thatrenderPipelineCoreuses, falling back toSTREAM_ERROR_MARKERonly when no boundary is available.