Repository navigation
fix(security): route /v1/search's internal-exception branch through buildErrorBody - #15951
Merged
diegosouzapw merged 1 commit intoOct 9, 2026
Conversation
…uildErrorBody Wave 1.3 of the diegosouzapw#15159 audit. src/app/api/v1/search/route.ts caught any unexpected internal exception and forwarded its raw message to the client via toJsonErrorPayload(err.message, "Internal search error"). toJsonErrorPayload is built for upstream provider bodies and returned an unparseable plain string straight through with zero sanitization. An internal exception's message is our OWN text: it can carry an absolute path, a SQL fragment, a credential echoed from a failed connection, or a stack tail. The sibling alpha/search/route.ts already routed this branch through buildErrorBody and carried a comment explaining why - one file apart, fixed on one side only. This converges the two. The SearchError branch above it keeps its existing toJsonErrorPayload call, because that message is our own controlled provider-failure text, and the log.error stays unsanitized so the operator keeps the full diagnostic on the log path. Guard is deliberately stronger than regex-only: reverting the fix and re-running caught a false-green in my own first draft, where the convergence assertion satisfied itself on a prose comment naming buildErrorBody instead of a call to it. catchTail now strips line comments, so all three source-shape assertions fail on revert. Two behavioural assertions additionally prove the builder still redacts a credential, an absolute path and a stack frame - the gap the alpha search precedent flagged, where source matching "could not have caught" the leak while asserting errors were sanitized. Verified: 3 tests watched red on revert, 7 green after; 59 search tests pass; typecheck:core, lint and check:error-helper clean.
diegosouzapw
merged commit Oct 9, 2026
a0bcbbe
into
diegosouzapw:release/v3.8.52
29 of 51 checks passed
Owner
|
Thanks @jonlwheat2-gif — the /v1/search internal-exception response now goes through buildErrorBody, so the client body is sanitized. This is in the upcoming release. |
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
Wave 1.3 of the #15159 audit.
src/app/api/v1/search/route.tscaught any unexpected internal exception and forwarded its raw message to the client:The leak is that
toJsonErrorPayloadwas handed an internal exception, not an upstream provider body. An internal exception's message is our own text: it can carry an absolute path, a SQL fragment, a credential echoed from a failed connection, or a stack tail.The sibling
alpha/search/route.tsalready routed this branch throughbuildErrorBodyand carried a comment explaining why — one file apart, fixed on one side only. This converges the two.Scope held deliberately
SearchErrorbranch above it keeps its existingtoJsonErrorPayloadcall — that message is our own controlled provider-failure text.log.errorstays unsanitized so the operator keeps the full diagnostic on the log path. Sanitization belongs on the response path, not the log path.Testing
TDD, per Hard Rule #18: 3 tests watched red before the fix, 7 green after. 59 search tests pass.
typecheck:core,lint,check:error-helperclean.Guard is deliberately stronger than regex-only
The alpha/search precedent notes that source-matching tests "could not have caught" this class while falsely asserting errors were sanitized. So:
Reverting the fix and re-running caught a false-green in my own first draft. The convergence assertion passed on a prose comment naming
buildErrorBodyrather than a call to it — the exact false-green this guard must not have.catchTailnow strips line comments, and all three source-shape assertions fail on revert.Two behavioural assertions additionally prove the builder still redacts a credential, an absolute path and a stack frame, so the guard fails if either half regresses: