Skip to content

Build1 publisher3 min readPublished

Any path containing a dot skips LibreDB Studio's Next.js middleware

Consolidating every database credential onto one server leaves one process deciding who is asking on each request, which is why LibreDB Studio's authors re-verify the caller inside every route rather than trusting a matcher that exempts any path with a dot.

The Engineer · Build desk

Illustration accompanying Any path containing a dot skips LibreDB Studio's Next.js middleware

What happened

  • A dev.to post by cevheri walks through moving a desktop SQL client onto a server, which collapses sixty scattered credential copies into one and turns per-person VPN grants into a deployment topology.
  • The authors say the result is a multi-tenant network service holding every database connection the team owns, with four problems a desktop app never had, two of which they got wrong on the first attempt.
  • LibreDB Studio's Next.js middleware does verify the session cookie before every route it covers, but the authors state plainly that the middleware is not the authorization boundary.
  • State-changing requests carry a second check beyond the session cookie, comparing the request Origin against the deployment's own host because a browser client brings ambient authority a desktop client never had.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A URL prefix cannot express "this transport, on this run", so any team that admits a machine caller by adding its path to the public list has admitted everything that can reach the port.
  • exposure A token handed to the wrong verifier does not fail closed here: the cast to UserPayload yields a session with no role, which reads as a non-admin user of the entire application.
  • cost The drive credential was designed and paid for before any queue produces traffic that uses it, on the authors' judgement that boundaries are cheap up front and expensive to retrofit.
  • decision Operators behind a TLS-terminating proxy have the tradeoff made for them: scheme-blind comparison keeps them able to log in, at the price of conceding a same-host plaintext page to an attacker already inside transport.

Middleware in Next.js runs where the matcher tells it to run. The config line is a negative lookahead, and one of its terms is `.*\..*`, which excludes any request path containing a dot [6][7]. That term is there so static assets skip the pipeline, and as an optimisation it is fine. As a trust rule it says that requests whose URLs contain a period are exempt. A handler at a path like `/api/export.csv` would be reached with no edge session check performed at all [1].

The response was not a tighter regex. Every route that touches a database or a model provider verifies its caller again through one shared guard, and the edge is kept for making the common case cheap [8].

That decision pays where a legitimate caller cannot present a cookie. The agent runtime's resume callback is invoked by a durable transport rather than a person, so there is no session to check by construction [9]. Adding the callback's path to the exemption list is the cheap fix and the wrong one, because a path exemption is path-shaped: anything that can reach the port gets in [11]. What the caller presents instead is a token the server minted itself, sixty seconds long, naming exactly one run, granting nothing else [12].

The signing key is the part worth copying. It is HMAC-SHA-256 over a fixed label, keyed with `JWT_SECRET`, so the resulting drive key can neither mint nor verify a session [13]. The reason is a cast: `verifyJWT` coerces its payload to `UserPayload` without inspecting it, so a drive token that reached the session verifier would present as a session whose role is `undefined`, which the middleware reads as "not admin" and therefore as an ordinary user of the whole application [14]. Separate keys remove that path instead of leaving a claim check for every future reader to remember [15].

The Origin check is argued on false positives rather than on attackers. The comparison ignores the scheme because of a deployment class: a TLS-terminating proxy that forwards plain HTTP without setting `x-forwarded-proto` makes the browser send `Origin: https://db.example.com` while the app computes `http://db.example.com`, and comparing schemes there locks the operator out of their own login form [17]. The concession is an `http://` page on the same host posting to the `https://` app, which needs an attacker who has already broken transport [18]. A request arriving with neither Origin nor Referer is accepted only when its content type is `application/json` [19]. The post says two independent reasons hold that rule up and lays out the first: an HTML form has three `enctype` values, none of them JSON, so the classic CSRF vector cannot produce that shape [20].

Both Origin decisions assume a browser front end, sessions in a cookie the app mints itself, and a symmetric secret to derive a second key from [12][13][16]. Where session tokens come from an external issuer there is no `JWT_SECRET` in hand, and the same reasoning about unreachable failure modes arrives at a separate signing key by a different route. The arithmetic of the move holds either way: in the post's own example, sixty credential copies become one, and fifty-nine laptops stop needing a VPN grant [2].

What to watch

  • Whether a queue ever produces a drive delivery in production, which would put the sixty-second token on a live traffic path for the first time.
  • Whether the second of the two claimed reasons for accepting Origin-less JSON requests is ever spelled out; only the enctype argument is.
  • Whether the matcher is rewritten to name the paths it admits rather than exempting anything with a dot in it.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories