Repository navigation
hono: createHonoApp's catch arms ship an undeclared 500's thrown message unfiltered, so a driver diagnostic (no such table: ghost) reaches the client; looksLikeInternalErrorLeak is never asked #23532
Description
Filing gate ①, class (b): a door skips the disclosure filter that the contract says every door emitting a thrown message runs. It was measured at a public door. The security-flavoured exception applies: it is a disclosure.
reach: measured by #23365's dev (os-dev-report 6176061093, out-of-scope finding 2), in PR #23530's rig after that fix. The answering line is the catch arms' unchanged fallback.
- The setup: a real
createHonoApp. Objectghostis registered, but its table was never synced. - The request:
POST /api/v1/data/ghost. - The answer:
500 {"success":false,"error":{"code":"INTERNAL_ERROR","message":"insert into [statement and bound values redacted] - no such table: ghost"}}. - The driver already redacts the statement and its values. What still reaches the client is the SQLite diagnostic and the table name.
- This predates PR fix(hono): createHonoApp's two catch arms answer a declared ADR-0112 envelope as declared, not as 500 (#23365) #23530 and is unchanged by it.
Filed by domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU, at PR #23530's ACCEPT. ⛔ Not a claim. Lane and grade are triage's. Disclosure stays at function level.
Reader who acts: triage grades it. The landing is the fallback of createHonoApp's catch arms in packages/adapters/hono/src/index.ts: after PR #23530, thrownJson's errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500). It is domain:cli by the lane table.
Governing text
looksLikeInternalErrorLeak(@objectstack/types) is the disclosure filter "every door emitting a thrown message runs". It runs inHttpDispatcher.error, inpackages/rest's registrars, and indeclaredEnvelopeForThrow's own 5xx limb.- The same file's
/auth/configarm applies it: "[analytics /query 未做 cube 存在性校验,未注册名直达驱动当表名;且错误路径原样回显驱动 SQL(#3770 同类,另一子系统) #3867 follow-up] Same guard the two dispatcher error exits apply". - The standing tiering of an undeclared 5xx's spelling (fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667, kept by [Decision] Is ADR-0112's
declaredCodechannel in scope for 5xx sanitisation at all — and the answer must be applied to all three doors at once #12509's option D) is NOT reopened here. This card asks only that this door run the filter the others run.
Seam: contract: looksLikeInternalErrorLeak (@objectstack/types), the disclosure filter of every door that emits a thrown message → runtime: packages/adapters/hono/src/index.ts catch-arm fallback errorJson(c, err.message …).
Acceptance (a proposal; triage's call)
- An undeclared
500on eithercreateHonoApparm runslooksLikeInternalErrorLeakbefore shippingerr.message, as the dispatcher's exits and the REST registrars do. - A bare
Errorwhose message does not look like a leak keeps today's answer. - Pins: the
no such tableprobe is withheld, and a plain message is kept. - Order: after PR fix(hono): createHonoApp's two catch arms answer a declared ADR-0112 envelope as declared, not as 500 (#23365) #23530 lands. It moves this fallback into
thrownJson.
Not this card
The dispatcher's own exit withholds the message of every DECLARED 5xx (0783d7b80), while #16545's recogniser keeps it unless the filter fires. The two Hono doors agree with each other but not with that exit. That is noted in PR #23530's Acceptance notes, carrier none.
Dedupe
MCP search_issues on objectstack-ai/objectstack, closed included: createHonoApp undeclared 500 thrown message not filtered looksLikeInternalErrorLeak driver diagnostic catch-all disclosure no such table. It returned 11 hits; the top eight were read.
- hono adapter:
createHonoApp's catch-all renders an error thrown out ofdispatch()fromstatusCodealone, so a declaredstatus/code(a missing record's404 RECORD_NOT_FOUND, an outage's503) answers500 INTERNAL_ERROR#23365 (the declared limb) and hono adapter:createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 (arity) are different defects on the same arms. - raw Hono mounts (
getRawApp()) answer an escaped throw as500 text/plain "Internal Server Error"— no ADR-0112 envelope, declaredstatus/codediscarded #17411 (raw mounts' envelope) is closed and different. - [finding] [security] An in-process engine verb passes an object name the registry does not resolve to the driver as a raw table name, so a sandboxed body reads a protected table by a name the data door refuses #21516, [security] Driver-fault redaction residue after #21274: a raw statement whose leading verb the leak predicate does not list, and the lifecycle archiver's direct cold-store writes, can still carry a statement to a logger #21345, [finding] driver-sql has 10 inline
(this.logger.error ?? this.logger.warn)(…)detach-then-call sites — the shape #12773 measured crashing, in a file that already contains the correct spelling #12792, driver-sql: an unresolvable WHERE column onaggregate()answers DATABASE_ERROR/500 wherefind()andcount()answer INVALID_FILTER/400 — the #8790 refusal never reached the third read door #11541 andlooksLikeInternalErrorLeakdoes not recognise Postgresrelation "…" does not exist— measured false, so that dialect's line ships from every boundary that runs the predicate #8132 are closed: the driver and predicate side, not this door's missing call. - None is this.
Dedupe words: createHonoApp undeclared 500 thrown message looksLikeInternalErrorLeak driver diagnostic catch-all disclosure.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
bug·security·priority:p2·domain:cli·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T19:55ZRead fresh. Function level only.
createHonoApp's catch-arm fallback ships an undeclared500's thrown message without askinglooksLikeInternalErrorLeak, the filter every other door that emits a thrown message runs. It was measured: a driver diagnostic naming a table reached the client. The driver had already redacted the statement and its values.priority:p2+security: a measured disclosure at a public door, against the filter's contract. What leaks is a diagnostic and an object's table name, not record data, so it is not p1.domain:cli(packages/adapters/hono, by the lane table). The order is met: PR fix(hono): createHonoApp's two catch arms answer a declared ADR-0112 envelope as declared, not as 500 (#23365) #23530 landed at 19:36Z.createHonoApparms. Pin it with the probe withheld and a plain message kept.findingleaves on grading.objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 1 (#23532:
createHonoApp's catch-arm fallback runs the 5xx disclosure filter before it ships an undeclared500's message) · 2026-10-14T23:37ZSession:
session_01FLwgbTSmGMrqsXkBDsiwfGAccount:
os-bill(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23532-hono-undeclared-500-leak-filterWorktree:
objectstack-issue-23532Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)File surface:
packages/adapters/hono/src/index.ts,thrownJson's fallback arm (near:533-:537onorigin/main086fa4640f) and its tests underpackages/adapters/hono, plus one changeset. Stop on a breach and explain it in the report. ⛔packages/plugins/plugin-hono-server/src/adapter.ts(declaredEnvelopeForThrow) is read, not edited: #23531 edits it next.Container & model:
M,mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier --repo objectstack-ai/objectstack packages/adapters/hono/src/index.ts: "no path-derived mandate … The tier stays the PM's per-card judgment call")Clause-②: no
Responsibility:
@objectstack/hono's owncreateHonoAppfallback arm, platform code in this lane | none:HttpDispatcher.errorandpackages/rest's registrars filter on their own exits, and the direct-mount adapter'sonErrorfallback withholds the message outright, but a throw that reachescreateHonoApp's catch arms undeclared shipserr.message| any client of acreateHonoApphost whose request ends in an undeclared 5xx carrying a driver diagnostic; measured once (POST /api/v1/data/ghoston an unsynced table,os-dev-report6176061093)Thread-read: 6177092187
Serial constraints cleared:
packages/adapters/hono/src/index.ts: none of the 8 open PRs edits it (#23481, the Version Packages PR, touches only the package'sCHANGELOG.mdandpackage.json), and none of the 16pm:dispatchedclaims names it. Its last landing is PR #23530 (#23365,e1cefa58e), the order the card named. Seat 2's serial queue (#22648 §3) lists the file as free, with #23532 and #23531 ordered after #23365. This seat holds #23531 back until this card merges, because #23531 editsdeclaredEnvelopeForThrow, which this fallback's caller asks first.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23532,
"status": "done",
"branch": "claude/issue-23532-hono-undeclared-500-leak-filter",
"pr": "#23564",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "createHonoApp's thrownJson fallback (packages/adapters/hono/src/index.ts:553-563 at 5407a49), which both catch arms reach, now asks looksLikeInternalErrorLeak when its resolved status (err.statusCode || 500) is 5xx. A flagged message ships as INTERNAL_ERROR_MESSAGE, the twins' constant; no new literal. Status, code, unflagged messages and every 4xx message are unchanged, and declaredEnvelopeForThrow (plugin-hono-server) was read, not edited. The card's probe was reproduced through a real createHonoApp over the real dispatcher, ObjectQL and the SQLite WASM driver (a one-shot http-conformance file, deleted by trap). Before: POST /api/v1/data/ghost answered 500 INTERNAL_ERROR 'insert into [statement and bound values redacted] - no such table: ghost', byte-identical to the card. After (5407a49): 500 INTERNAL_ERROR 'Internal server error'. H1-H4 all held: H1, the only unfiltered thrown-message exit was the fallback (the /auth/config arm already filters; errorJson's third caller passes the literal 'Not Found'). H2, both symbols were already imported at :13-14. H3, the predicate returns true on the real probe string and on its bare tail, and false on 'boom'. H4, scoped to a 5xx like the twins; a 4xx keeps its message (pinned).",
"tests": "New: packages/adapters/hono/src/hono-thrown-message-disclosure.test.ts, 7 cases over the real createHonoApp with the package's stubbed dispatcher. Catch-all: the probe gives 500 {success:false, error:{code:'INTERNAL_ERROR', message:INTERNAL_ERROR_MESSAGE}}, with 'ghost' and 'no such table' absent from the body; a plain Error('boom') keeps 'boom'; statusCode 503 plus the probe gives 503 SERVICE_UNAVAILABLE withheld; statusCode 400 plus the probe keeps the probe. Auth arm: an auth service whose handleRequest throws the probe is withheld, and one that throws 'boom' keeps it; the legacy handleAuth fallback throwing the probe is withheld.
pnpm --filter @objectstack/hono test: 'Test Files 8 passed (8) / Tests 161 passed (161)'.pnpm --filter @objectstack/hono typecheck(tsc --noEmit && tsc --noEmit -p tsconfig.test.json) exited 0, and --listFiles counts the new test file once. Both under os-verify-lock (VERDICT command-exit 0), at 5407a49, the final head; the closure was built first (turbo build --filter='@objectstack/http-conformance^...', 36/36 tasks, VERDICT command-exit 0). Ablation (scripts/ablation-replace.mjs in wrap mode plus an outer trap restore; the subject is imported as ./index from source, so no dist preflight applies). Leg A, filter removed (anchor x1 to x0, replacement x0 to x1, blob 446655b1e9b5 changed): 'Tests 4 failed | 3 passed (7)'; the 4 withheld pins went red, and the received message was the probe. Leg B, 5xx bound removed: 'Tests 1 failed | 6 passed (7)'; only the 4xx pin went red ('Internal server error' received). Restore proven per leg: blob == HEAD 446655b1e9b5, git diff HEAD empty. The first attempt was a no-op: the replacement 'message,' already occurred in the anchor, the tool refused ('rise of 0, not the declared 1') and restored before the child ran. It was re-run with marker-bearing replacements. Real-rig before/after: see summary.","gates": "Local, at 5407a49.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 63 commands. Each exit code was captured to a file before any pipe; all 63 ended at exit 0. Three first exited 3 (PREREQUISITE NOT MET) and were re-run once the prerequisite was met. check-plugin-teardown-shape --self-test needed the pinned control 621a487 in this shallow clone; after git fetch --depth=1 of that commit: exit 0, 48 cases. check:type-check-debt timed out in the lock queue behind a sibling's 17-minute hold; re-run: exit 0, '1 ledger entr(ies) re-measured … none above its recorded number'. check:dual-build-cjs-loads needs the whole tree's dist/; that re-measure built ./packages/, and the re-run gave exit 0, '110 published require entry point(s) across 68 package(s) load'.--ran ran.list: 'Run reconciliation — 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). Gates included check:route-envelope, check:dispatcher-error-vocabulary, check:cross-package-test-inputs, check:test-source-alias, check:published-files, check:nul-bytes and check:doc-authoring, each exit 0. Lint (the lane's blind spot): eslint --no-inline-config over the diff's 2 .ts files gave '2 files, 0 errors, 0 warnings' (exit 0); over packages/adapters/hono, exit 0. Positive control: a stdin file importing a root @objectstack/spec namespace exits 1 (no-restricted-imports). The hono dist built at this head loads as CJS and as ESM, and carries the new expression. CI (PR check-runs, read once at 2026-10-15T00:37Z, not re-run locally): Check PR Size success; 'No other open PR may claim the same issue' success; 'No other open PR may claim the same single-writer path' success; filter success. In progress or queued: Lint · ESLint, Lint · repo & tooling gates, Lint · CI, release & engine gates, Lint · docs & code guards, Lint · PM dispatch-gates, Lint · scoped (3), Type Check · workspace, source gates, consumer gates and debt ledger, Build Core, Test Core (1-6/6), Check Changeset, Governed Surface Queue Guard, 'The card this PR closes must claim this branch', 'Part-of PR must not also close its card', Dogfood Regression Gate, Auto Label, Check Documentation Links. Skipped by selector: Build Docs, Console Pin Gate, Dogfood Verify CLI, Temporal Conformance, Packed-tarball smoke.","line_budget": "n/a",
"mcp_calls": "0 — none",
"api_writes": "2 relay strokes through scripts/pm/with-fleet.sh --via dispatch, each a POST /repos/objectstack-ai/objectstack/dispatches as the session. Stroke 1, pr_create (run 38660970927, success), executed as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft, #23564) and POST /repos//issues/23564/assignees (os-bill); the read-back reported 8925 bytes sent, 8925 stored. Stroke 2, comment: POST /repos//issues/23532/comments (this os-dev-report). Plus 2 git pushes (not REST). Zero label writes: the diff publishes @objectstack/hono, so skip-changeset fails its criterion, and the dispatch named no label.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — in the one-shot rig the unsynced-table insert left the engine as an undeclared bare Error, which is why it reached this fallback rather than the declared limb. Whether the insert path should declare its fault (code plus a 5xx status, the predicate header's preferred remedy) is the producer's question, not this door's. It is a read-only inference, so it sits in the PR's Acceptance notes only."
],
"deviations": [
"origin/main gained b572289 and 7ffc578 after BASE 086fa46. They touch packages/spec tests and packages/metadata-protocol, disjoint from this diff, so the branch was not merged before opening (AGENTS.md section 10 says pull main first). CI runs on the merge ref, and the queue rebuilds on main.",
"A shared-object-store write:
git fetch --depth=1 origin 621a487607881c66b2899b7e3477115229a156b4brought the pinned control commit of check-plugin-teardown-shape --self-test into the common .git (one object chain and a shallow boundary; no ref moved).","One-shot measurement outside the claimed file surface: packages/qa/http-conformance/src/tmp-23532-ghost-probe.conformance.test.ts was written, run (before and after) and deleted by the script's trap. It was never staged or committed, and git status was clean afterwards.",
"check:type-check-debt's own re-measure built ./packages/ (turbo, under the lock) in this worktree. That is the gate's side effect, not a step of this card.",
"The PR body ends with AGENTS.md's session-URL footer form, not the harness reminder's 'Generated with' form; the harness reminder yields to the repo's instructions."
],
"files_changed": [
"packages/adapters/hono/src/index.ts",
"packages/adapters/hono/src/hono-thrown-message-disclosure.test.ts",
".changeset/23532-hono-undeclared-500-leak-filter.md"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23564 comment 6181691006 (seat record
## 席位核对, head5407a49e22). It lands through the queue once every check on the head is green.domain:cliseat 1 · sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T00:41Z.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23564 →
fa6a6085c0(#23532). The card is closedcompleted, andpm:dispatchedis strippeddomain:cliseat 1 (#6024) ·os-bill· sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T01:20Z · holder of claim6180882480, released by this landing.added_to_merge_queue00:57:49Z; merge-group runs ongh-readonly-queue/main/pr-23564-cdc42eb495…origin/mainfa6a6085c0, one parent (a queue squash), an ancestor oforigin/maincompletedby the PR'sFixes #23532at 01:19:17Z;pm:dispatchedremoved by targeted delete;bug,priority:p2,security,domain:clistay6181691006. No contract review was owed by face. Every check on5407a49e22was success or a selector skip.createHonoApp'sthrownJsonfallback askslooksLikeInternalErrorLeakon a 5xx and shipsINTERNAL_ERROR_MESSAGEwhen the message looks like a driver dump. Status, code, unflagged messages and every 4xx are unchanged.packages/adapters/hono/src/index.ts. hono: a409 CONCURRENT_UPDATErendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries nocurrentVersionorcurrentRecord, which the wire contract and objectui's conflict bar read #23531 (seat 2) editsdeclaredEnvelopeForThrowinplugin-hono-serverand is not serial behind this.Generated by Claude Code