From 22aa069b35de500e51dbe0f4e14664bc0a3d505b Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 31 Jul 2026 09:20:41 +0200 Subject: [PATCH] public: reject non-boolean crew, closing a paid-sale-books-as-free hole MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit crew ? 0 : total (and crew ? 1 : 0 in the insert) only work correctly if crew is actually a boolean. Any other truthy value — the string "false" is the obvious one, but any stray object/number works too — silently books a paid sale as a free crew drink. Once total_cents is 0 there's nothing left in the row to tell a genuine crew drink apart from a mis-typed paid one; unrecoverable after the fact. Adds the same typeof check the other fields on this endpoint already get (isValidCents, qty bounds, etc.) — reject with 400 instead of silently mis-booking. --- server/src/routes/public.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/routes/public.ts b/server/src/routes/public.ts index 85e0b46..5e70de6 100644 --- a/server/src/routes/public.ts +++ b/server/src/routes/public.ts @@ -55,6 +55,13 @@ export function registerPublicRoutes(app: FastifyInstance, db: DB) { if (!Number.isInteger(bar_id)) { return sendProblem(req, reply, 400, 'Bad Request', 'bar_id required'); } + // `crew ? 0 : total` below only works if `crew` is actually a boolean — + // any other truthy value (e.g. the string "false", or a stray object) + // would silently book a paid sale as a free crew drink, with nothing in + // the row afterwards to tell that apart from a genuine crew transaction. + if (crew !== undefined && typeof crew !== 'boolean') { + return sendProblem(req, reply, 400, 'Bad Request', 'crew must be a boolean'); + } if (!Array.isArray(items)) { return sendProblem(req, reply, 400, 'Bad Request', 'items required'); }