Skip to content

The direct-mount package door ships 5xx messages verbatim — no leak heuristic, unlike the dispatcher twin and unlike rest-server's own /data exit #8086

Description

@hotlong

Found while implementing #8016 (coded-error mapping on the same door). Recorded rather than acted on: #8016's dispatch explicitly scoped this out, and it is an independent decision about message disclosure, not about status/code mapping. Filed unassigned; nobody is on it.

The gap

packages/rest/src/package-routes.ts writes every error body through the shared sendError (packages/types/src/response-envelope.ts), and neither the module nor the shared writer applies any leak heuristic:

Both siblings on this surface DO sanitize:

So one door of /api/v1/packages withholds a leaky 5xx message and the other does not, on the same deployment.

Reachability

packageService.publish / .delete and protocol.deletePackage execute inside these handlers, and packages/metadata-protocol/src/protocol.ts interpolates raw driver text into client-facing messages on status: 500 throws — the two producers #5437 named by line (Failed to persist customization overlay to sys_metadata: ${dbError.message}..., Failed to delete customization overlay: ${err.message}). A SQLite/Postgres error naming physical tables is well under any length threshold, so it travels whole.

Not a regression from #8016, and not new

Before #8016 this door's catch-alls shipped (error as Error).message verbatim on a hard-coded 500; after it they ship the resolved status's message verbatim. The disclosure behaviour is unchanged in both directions — #8016 only stopped mislabelling refusals as faults. What changed is that a coded 5xx now arrives here as a 5xx with its own code, which makes the missing filter easier to reach deliberately.

Sibling, closed

#5437 / PR #5464 fixed exactly this class one seam over (rest-server.ts's resolveErrorResponse passthrough band). That fix did not reach this registrar, which does not go through resolveErrorResponse at all. #3867 is the sanitizer itself.

Directions (not a ruling)

  • A — sanitize in the shared writer. Apply the 5xx rule inside sendError, so every module that writes the declared envelope inherits it. Widest blast radius (7+ route modules, several services and plugins), and it makes a disclosure rule a property of the envelope writer, which its own module note currently disclaims.
  • B — sanitize at this door. Apply it in sendThrownError (package-routes.ts). Smallest change, closes the door named here, leaves the other sendError callers unaudited.
  • C — fix at the producer.metadata-protocol stops interpolating driver text into client-facing messages; the raw text goes to logs only. Contract-first and the real cure, but it needs a producer-by-producer sweep — the same option C sendError 的显式状态直通覆盖 400–599,5xx 的原始驱动报错绕过全部泄漏启发式直达客户端(metadata-protocol 有活体产出方) #5437 recorded.

A + C, or B + C, are the combinations that leave nothing hanging. Whoever takes it should decide whether "the envelope writer sanitizes" is a rule this repo wants, because that answer generalises and B does not.

Unverified

Read from code and confirmed by grep (looksLikeInternalErrorLeak( has zero call sites in package-routes.ts and in response-envelope.ts). No end-to-end reproduction: I did not boot a REST server and force a sys_metadata write failure through POST /packages/publish.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions