fix(svc): mask internal error text on the wire #31

Merged
nalum merged 1 commit from fix/internal-error-mask into main 2026-08-13 11:39:12 +00:00
Owner

Driver/DB error strings rode CodeInternal to clients ("database is locked", pgx constraint names) — including to unauthenticated callers via the auth interceptor's DB-failure branches.

New MaskInterceptor, outermost in the chain (mask → auth → sanitize → hydrate → otel), so it sees the FINAL error including ones the auth interceptor raises. Any CodeInternal error is logged in full server-side (error_id, procedure, original text) and replaced on the wire with internal error [id=<16-hex>] — code preserved, details and metadata dropped. Unary and streaming paths both covered.

Strictly scoped to CodeInternal: validation/domain/auth/not-found errors pass by identity, so the "failures name their reason" rule holds where it matters. No call-site churn — handlers keep rich %w wrapping for the server log. Otel stays innermost and traces the unmasked error.

AGENTS.md gains the seam as service-pattern item 8. Full suite + integration pass.

Fixes #17

🤖 Generated with Claude Code

Driver/DB error strings rode `CodeInternal` to clients ("database is locked", pgx constraint names) — including to unauthenticated callers via the auth interceptor's DB-failure branches. New `MaskInterceptor`, outermost in the chain (`mask → auth → sanitize → hydrate → otel`), so it sees the FINAL error including ones the auth interceptor raises. Any `CodeInternal` error is logged in full server-side (`error_id`, procedure, original text) and replaced on the wire with `internal error [id=<16-hex>]` — code preserved, details and metadata dropped. Unary and streaming paths both covered. Strictly scoped to `CodeInternal`: validation/domain/auth/not-found errors pass by identity, so the "failures name their reason" rule holds where it matters. No call-site churn — handlers keep rich `%w` wrapping for the server log. Otel stays innermost and traces the unmasked error. AGENTS.md gains the seam as service-pattern item 8. Full suite + integration pass. Fixes #17 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(svc): mask internal error text on the wire
All checks were successful
check / web (push) Successful in 1m50s
check / go (push) Successful in 2m7s
8763edf69f
CodeInternal errors wrapped whatever the failing layer said, so driver
strings ("database is locked", pgx constraint names) crossed the wire —
including to unauthenticated callers via the auth interceptor's
DB-failure branches. A structural MaskInterceptor, outermost in the
chain so it sees the final form of every error (auth's included),
replaces the text with "internal error [id=…]" and logs the original
under the correlation id: operator logs stay rich, the wire stays
generic, and details/metadata are dropped with the text since a fresh
error carries none. Handlers keep their %w wrapping untouched — the
mask is one seam, like sanitize, with no call-site churn.

Scoped strictly to CodeInternal so the failures-name-their-reason rule
holds: domain, validation, auth and not-found errors stay verbatim, and
"internal error" is an internal fault's honest reason — the id makes
it findable.

Fixes #17
nalum force-pushed fix/internal-error-mask from 8763edf69f
All checks were successful
check / web (push) Successful in 1m50s
check / go (push) Successful in 2m7s
to 901a9fcbde
Some checks failed
tag / tag (push) Has been cancelled
check / web (push) Successful in 1m36s
check / go (push) Successful in 1m53s
2026-08-13 11:33:33 +00:00
Compare
nalum scheduled this pull request to auto merge when all checks succeed 2026-08-13 11:34:02 +00:00
nalum merged commit 901a9fcbde into main 2026-08-13 11:39:12 +00:00
nalum deleted branch fix/internal-error-mask 2026-08-13 11:39:12 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!31
No description provided.