Skip to content

fix(rate-limit): DB error during bearer-identity classification crashes the request with a non-JSON 500 #9223

Description

@JSONbored

Summary

validateBearerForRateLimit (src/auth/rate-limit.ts) is called from the app.use("*", ...) rate-limit middleware, ahead of every route handler, to decide whether an incoming bearer token should be treated as an authenticated identity (its own rate-limit bucket) or fall back to IP-keying. It calls authenticatePrivateTokenauthenticateSessionTokengetAuthSessionByTokenHash, which does a real D1/Drizzle read against auth_sessions.

Unlike the Durable Object rate-limit-bucket call just below it in checkRateLimitBucket (explicitly fail-open per #5000, since no app.onError is registered anywhere in the app), this DB read is unguarded. If it throws (a transient D1 hiccup, a driver error, or — as hit in tests — a DB binding that doesn't fully implement the D1 contract), the error propagates out of the middleware uncaught. With no global error handler, Hono falls back to a bare, non-JSON Internal Server Error response for whatever route happened to be hit — indistinguishable from an application bug in that route's own handler.

This is purely an identity-classification path (which bucket to count a request against), not the actual authorization decision for the route — those checks happen later in dedicated middleware and re-validate independently. A hiccup here should never be able to take down an otherwise-healthy, otherwise-correctly-authenticated route.

Reproduction

test/integration/orb-ingest.test.ts — "tolerates a list query that omits results (rows.results ?? [])"

Swap env.DB for a minimal stub implementing only .prepare().all() (no .bind()), then hit GET /v1/internal/orb/instances with Authorization: Bearer dev-internal-token. Response body is the literal string Internal Server Error, not JSON — TypeError: this.stmt.bind is not a function thrown from drizzle-orm/d1/session.js inside getAuthSessionByTokenHash, invoked via the rate-limit middleware's validateBearerForRateLimit, before the route's own /v1/internal/* bearer check (which only does a static-token compare and never touches the DB) even runs.

Fix

Wrap validateBearerForRateLimit's body in try/catch, matching the existing fail-open convention in the same file (checkRateLimitBucket's DO-fetch catch, and isGlobalAgentFrozen in src/db/repositories.ts): on error, log and fall back to treating the token as unrecognized (IP-keyed identity), rather than letting the error escape the middleware.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions