Skip to content

fix(elysia)!: withhold the raw upstream error message from the SSE error frame#476

Open
rejifald wants to merge 1 commit into
mainfrom
fix/elysia-sse-error-leak
Open

fix(elysia)!: withhold the raw upstream error message from the SSE error frame#476
rejifald wants to merge 1 commit into
mainfrom
fix/elysia-sse-error-leak

Conversation

@rejifald

Copy link
Copy Markdown
Owner

Security: SSE error frame leaked the raw upstream message

@stitchapi/elysia's streamStitchSse wrote the raw event.message (and a caught throw's
message) straight into the terminal event: error SSE frame, disclosing it to the
client:

event: error
data: getaddrinfo ENOTFOUND payments.internal.corp    ← internal topology leaked

A transport failure leaks an internal hostname; an upstream 401 leaks its status. This is
the exact disclosure class the other five hosts already guard against — on main,
express / fastify / hono / nest / next all write a generic data: error token by
default (DEFAULT_ERROR_DATA / SAFE_MESSAGE). elysia was the lone outlier still echoing
the raw message
, and its tests even asserted the leak.

Fix

Bring elysia in line with its siblings:

  • The error frame carries a generic data: error token by default.
  • onError still receives the real failure server-side, for logging.
  • New errorData option opts in to shaping the client-facing payload
    (errorData: (e) => e.message) when upstream messages are known safe.
  • A caught throw is normalised to an error event so errorData sees a consistent shape.

⚠️ Breaking

The SSE error frame's data is now error by default instead of the raw message. Opt
back in with errorData. Pre-GA, no deprecation.

Tests

Flips the two SSE tests that asserted the leak, and adds an internal-hostname
regression + errorData opt-in coverage (error path and throw path). Verified the four
assertions fail on the pre-fix src and pass after. README + integration doc document the
option.

Context

Carved out of the #470 contract-freeze sweep so this security fix lands and is reviewed on
its own. In #470 it's bundled with the streamStitchSse option-shape convergence
(data(chunk, index), event as a function, an id option, the StitchEventSource hoist)
— all omitted here; this keeps elysia's existing signatures and changes only the error
disclosure.

Verification

pnpm --filter @stitchapi/elysia test (24 pass) + check:types clean.

🤖 Generated with Claude Code

…ror frame

`@stitchapi/elysia`'s `streamStitchSse` wrote the raw `event.message` (and a caught throw's
message) straight into the terminal `event: error` SSE frame, disclosing it to the client.
A transport failure leaks internal topology (`getaddrinfo ENOTFOUND payments.internal.corp`)
and an upstream `401` leaks its status — the exact class the other five hosts
(express / fastify / hono / nest / next) already guard against. elysia was the lone outlier.

Bring elysia in line: the error frame now carries a generic `data: error` token by default;
`onError` still receives the real failure server-side for logging, and a new `errorData`
option opts in to shaping the client-facing payload (`errorData: (e) => e.message`) when the
upstream messages are known safe. A caught throw is normalised to an error event so
`errorData` sees a consistent shape.

Flips the two SSE tests that asserted the leak, and adds internal-hostname regression +
`errorData` opt-in coverage; documents the option in the README and integration page.

BREAKING: the SSE `error` frame's `data` is now `error` by default instead of the raw
message — opt back in with `errorData`. Pre-GA.

Carved out of the #470 contract-freeze sweep so this security fix lands and is reviewed on
its own (in #470 it is bundled with the `streamStitchSse` option-shape convergence).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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