Skip to main content

Command Palette

Search for a command to run...

A batch endpoint must authorize every item, not just the first

Updated
•2 min read•View as Markdown
A
Practical lessons on auth, authorization, and access control.

Bulk endpoints are handy: archive 50 documents, delete 20 comments, move 100 files in one call. They are also a quiet place for authorization to slip.

The trap

The handler receives ids: [...], loads the first record, checks that the caller can edit it, then runs UPDATE documents SET archived = true WHERE id = ANY(:ids).

That proves the caller may touch one document. It says nothing about the other 49, which might belong to another project, another team, or another tenant.

Why it happens

  • The single-item route had a check, and the bulk route reused only half of it.
  • Checking every id feels slow, so someone "samples" the first one.
  • The UI only shows the user's own items, so nobody expects a mixed list.
  • A direct API call can send any ids it likes, including ones copied from another account.

A safer shape

  1. Load all requested records in one query.
  2. Authorize the caller on each record (or filter the query by what the caller may access).
  3. If any id is missing or denied, decide on purpose: reject the whole batch, or apply only the allowed ones and return which were skipped.
  4. Run the write only on the ids that passed, inside the same transaction.

Tiny checklist

  • Can I send a sibling tenant's id in the middle of a batch and have it change?
  • Does the bulk route use the same policy check as the single-item route?
  • Do partial failures get reported, instead of silently succeeding?
  • Is there a sane max batch size so the per-item check stays cheap?

Batch for speed, but authorize per object. The database will happily update every row you hand it.

1 views