fix(security): brochures.service UPDATE/DELETE WHERE includes portId

audit-pass-#2 flagged that updateBrochure and archiveBrochure validate
portId in their preceding SELECT but omit it from the subsequent UPDATE
WHERE clause. Currently safe (the SELECT throws NotFoundError first),
but a refactor that drops the SELECT or a TOCTOU race would silently
allow a cross-tenant write.

Defense-in-depth: add and(eq(id), eq(portId)) to both UPDATE WHERE
clauses so the safety property doesn't depend on caller discipline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-06 14:58:47 +02:00
parent 3f6a8aa3b8
commit 64f0e0a1b8

View File

@@ -178,7 +178,7 @@ export async function updateBrochure(
const [row] = await tx
.update(brochures)
.set(updates)
.where(eq(brochures.id, brochureId))
.where(and(eq(brochures.id, brochureId), eq(brochures.portId, portId)))
.returning();
if (!row)
throw new CodedError('INSERT_RETURNING_EMPTY', {
@@ -196,7 +196,7 @@ export async function archiveBrochure(portId: string, brochureId: string): Promi
await db
.update(brochures)
.set({ archivedAt: new Date(), isDefault: false })
.where(eq(brochures.id, brochureId));
.where(and(eq(brochures.id, brochureId), eq(brochures.portId, portId)));
}
// ─── Versions ────────────────────────────────────────────────────────────────