Skip to content

fix(types): withhold the Postgres and bare-SQLite phrasings of a driver failure (#8132) - #8263

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-8132-error-leak-pg-phrasings
Aug 13, 2026
Merged

fix(types): withhold the Postgres and bare-SQLite phrasings of a driver failure (#8132)#8263
os-zhuang merged 2 commits into
mainfrom
claude/issue-8132-error-leak-pg-phrasings

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #8132

looksLikeInternalErrorLeak recognised SQLite's SQLITE_ERROR: no such table: sys_metadata
but not the Postgres phrasing of the same condition, relation "sys_metadata" does not exist.
One failure therefore disclosed a physical table name, or not, depending purely on which engine
was underneath — from every boundary that applies the predicate.

Implements option A as ruled in triage. Option C (the producer-side fix, so no driver text is
interpolated into client-facing messages) is tracked separately as #8136 and is deliberately out
of scope here.

What changed

packages/types/src/error-leak.ts gains a DIALECT_LEAK_PHRASINGS list covering the two engines
this repo actually runs (driver-sql, driver-turso, driver-sqlite-wasm):

phrasing dialect
relation "…" does not exist / column "…" does not exist Postgres 42P01 / 42703
permission denied for table|relation|sequence|database … Postgres 42501
bare no such table: / no such column: SQLite / libsql, message-only form

The bare SQLite forms are not speculative: packages/metadata/src/utils/schema-sync-errors.ts
already documents both as shapes libsql and better-sqlite3 raise without a SQLITE_ prefix, which
is exactly what the existing sqlite_ limb needs in order to fire.

No dialect this repo does not run gets an entry. The unbounded-list trap the module note warns
about is real, and declaresServerFault remains the answer that does not depend on phrasing.

The judgment: anchoring, not keywords

A bare includes('does not exist') would have matched "user does not exist" and started replacing
legitimate business answers with Internal server error. Every phrasing is anchored on what the
driver always emits and prose usually does not — a quoted identifier, or the trailing colon.

The negative half is pinned as tests, including two near-misses that prove the anchor is real
rather than incidental: This relation does not exist in the diagram and The column layout does not exist both stay false. The looser includes('relation') && includes('does not exist')
reading used elsewhere would match the first of those.

permission denied for is restricted to physical object kinds. schema, view, function and
column are all ObjectStack authoring vocabulary, so a product message could legitimately use
them; a miss is the cheap direction, since the outcome is already a 5xx.

The residual pin, inverted

package-door-5xx-message-sanitization.test.ts carried a deliberately-red-in-future case from
PR #8130 asserting the predicate returned false for the Postgres line. It is inverted into the
positive assertion rather than deleted, and now asserts the full withheld envelope — so the pair
of cases proves that door answers the same envelope for both dialects of one failure. The
option-C pointer survives in the comment, since a phrasing test can only know the dialects
someone has met.

That is the only packages/rest touch, keeping clear of #8039's landing in rest-server.ts.

Verification

  • packages/types — 246/246 pass. New cases measured red first against the unfixed predicate
    (10 positives failing, negatives already green), then green after the change.
  • packages/runtime — 2301/2301 pass, no flips.
  • packages/rest — 1756/1756 pass. Before the pin flip: exactly one failure, the named
    residual pin and nothing else.
  • typecheck green on all three packages.
  • Gates: check:authz-resolver, check:cross-package-test-inputs, check:filter-alias-parity,
    check:meta-type-normalized, check:query-options-erasure, check:type-check-coverage,
    check:nul-bytes, plus five changeset-triggered families surfaced by re-deriving against the
    actual diff — all green.

Verified at every call site that the predicate still only ever runs where the outcome is already a
5xx. Seven call sites: four guard on httpStatus >= 500, two build a hard-coded 500, and
rest-server.ts's classifier chooses between DATA_STORE_FAULT and UNCLASSIFIED_FAULT, both of
which are status 500. One nuance worth recording: at that classifier the predicate selects which
sanitised 500 envelope is returned, so a false positive there shifts INTERNAL_ERROR to
DATABASE_ERROR — a code change, never a disclosure.

Generated by Claude Code


Generated by Claude Code

@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 13, 2026 1:23am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/types.

1 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/plugins/packages.mdx (via @objectstack/types)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants