Twenty-Five Routes, No Shared Gate
Middleware protected the pages. Every API route was trusted to protect itself. Two of them did not.
What
Tink's middleware guards page routes by prefix: /devices, /labs, /flash, /pumps, /scripts. It matches on the start of the path, so /api/devices never matches any of them. That is deliberate, because an API route is expected to authorize itself where it knows what resource is being touched. The problem is that the expectation was the only thing enforcing it. An audit found two routes that had shipped without a check: one returned any device row to an unauthenticated caller, including a legacy plaintext secret sitting in its metadata, and one let an unauthenticated caller reassign a device into a different organization.
The decision that mattered
Keep authorization in the route rather than extending middleware over /api, and hold every route to one canonical ladder: no session is a 401, no device is a 404, no organization is a 409, no membership is a 403. Middleware genuinely cannot do this check, because it does not know which lab a device belongs to until something has queried for it.
Twenty-five API routes and no shared helper between them. Every route hand-rolls its own check, so correctness depends on the author remembering, and nothing fails to compile when they do not. A shared wrapper was rejected earlier for reasons that still hold, and this is the bill for that decision arriving.
Good / Bad / Ugly
One canonical authorization ladder, checked against every route, with the unauthenticated device read and the cross-tenant reassignment both closed.
The reassignment route let an unauthenticated caller move a device into their own organization. That is device theft, not merely a missing check.
Twenty-five API routes and zero of them use a shared authorization helper. The convention holds exactly as long as everyone remembers it, and the codebase gives no signal when someone does not. It will happen again unless the check becomes something you opt out of rather than something you opt in to.
Shipped
Both routes closed and deployed, verified in production by response body rather than status code, because a deleted route and a genuine not-found both answer 404 and only the body tells them apart.
Next
Make the gate impossible to forget: either a shared wrapper, or a lint rule that fails any route handler containing no authorization call.