Skip to content

CLOUD-4771: reject an empty id list in the bulk delete statics - #245

Merged
buggyhunter merged 1 commit into
mainfrom
CLOUD-4771-reject-empty-bulk-delete
Sep 20, 2026
Merged

buggyhunter merged 1 commit into
mainfrom
CLOUD-4771-reject-empty-bulk-delete

Conversation

@burak-upstash

Copy link
Copy Markdown
Contributor

Linear: CLOUD-4771

Why

Found while investigating an incident where a console bug deleted every box on a customer's account. The root cause there was the console sending an unscoped DELETE /v2/box, which the coordinator reads as "delete everything". The SDK can produce the same request:

await Box.delete({ apiKey, boxIds: [] }); // sends {"ids": []} -> every box on the account is deleted

The coordinator treats an empty ids as "no filter". A script that computes its id list and comes up empty wipes the account instead of doing nothing. Box.deleteSnapshots({ snapshotIds: [] }) has the same shape, and snapshots are the only way to recover a deleted box's workspace.

What changes

  • Box.delete and Box.deleteSnapshots throw a BoxError before any request when the id list is empty or contains a blank id (shared requireIds helper). EphemeralBox.delete / EphemeralBox.deleteSnapshots are aliases and are covered.
  • Box.deleteSnapshots() with no snapshotIds still deletes every snapshot, as documented. It now sends ?all=true, so "everything" is stated rather than inferred from a missing list.
  • The Python SDK mirrors both: delete_boxes / delete_snapshots raise BoxError via common.require_ids, and delete_snapshots() sends all=true. _sync is regenerated.

Nothing in this repo passed an empty list: the only callers of the static Box.delete are in packages/box-pi, one with a single id and one behind a length > 0 check.

Compatibility

upstash/box-backend#268 makes the coordinator reject empty and malformed ids and accept ?all=true. This PR works against the coordinator both before and after that change: the current coordinator ignores the unknown all param and still deletes all snapshots for a request with no ids. #268 deliberately keeps accepting the old bare deleteSnapshots() shape so published SDK versions do not break; once this release is adopted, that fallback can be removed.

Testing

  • JS: new cases in box-delete.test.ts and box-delete-snapshots.test.ts (empty array, empty string, blank id in a list, missing boxIds; each asserts fetch was never called), plus the ?all=true URL. pnpm ci:lint, pnpm build, and pnpm test pass.
  • Python: parametrized rejection tests and the all=true assertion. ruff, mypy, pytest tests/_async tests/_sync (264 passed), check_parity.py, and sync generation are clean.
  • Integration tests were not run (they need UPSTASH_BOX_API_KEY).
  • Changeset: patch for @upstash/box.

Box.delete({ boxIds: [] }) sent {"ids": []}, and the API read an empty list as
"no filter", so it deleted every box on the account. Box.deleteSnapshots had
the same shape for snapshotIds: [].

Both now throw a BoxError before any request when the list is empty or holds a
blank id. deleteSnapshots() with no ids still deletes every snapshot, as
documented, and now sends ?all=true so the API does not have to infer
"everything" from a missing list.

The Python SDK mirrors both changes.
@linear-code

linear-code Bot commented Sep 20, 2026

Copy link
Copy Markdown

CLOUD-4771

@buggyhunter
buggyhunter merged commit 8fed971 into main Sep 20, 2026
10 of 12 checks passed
@buggyhunter
buggyhunter deleted the CLOUD-4771-reject-empty-bulk-delete branch September 20, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants