Delete is just a put
We found a real hole. On Vibes, a published app can accept writes from any signed-in visitor — that's the point of the default: your app's own access.js is the write gate, and the platform trusts it to say who may do what. But deletes never ran access.js. They took a different door, and that door's rule was roughly "anyone who can write may delete." Put together: every signed-in visitor could hard-delete any document in any default-published vibe.
The careful fix
The first fix was careful, reviewed, and tested. It added provenance: track whether a member's access came from an explicit grant row or was merely derived from the app-wide auto-accept setting, and let only explicitly-granted members fall back to "writers may delete." One bit of "how did you get here" instead of "who are you." It shipped.
And it immediately hurt the app it was supposed to protect. Our family-grocery vibe — a shared shopping list where the whole design is that family members are auto-accepted — had four destructive buttons (✕ an item, clear the cart, leave a group, delete a group), and under the new rule every future family member would get working add buttons and dead delete buttons, with the client-side permission hint promising otherwise. So the app grew a workaround the same day: soft deletes. Flip removed: true with a put instead of calling del, because puts run through access.js and the access function can scope those per group.
Get posts like this in your inbox
One email field. Real updates. No algorithm required.
Notice what just happened: the platform shipped a mitigation, and the very first real app responded by routing around delete entirely — re-implementing it as a put, in userland, to get its permissions back.
The insight that killed it
That workaround was the design review. If an app can faithfully reproduce "delete" as a put of a flag, then delete never was a separate operation — it's sugar for writing the tombstone {_id, _deleted: true}. And the security version of the same argument: anyone who can write a document could always destroy it by overwriting its contents with garbage. A delete permission that's stricter than the write permission protects nothing; it just makes the destruction messier.
So a few hours after the careful fix landed, we deleted it and shipped the simplification instead. A delete is now the gated put of its tombstone. Same front door as every other write, same access.js invocation — with one crucial detail: the doc handed to your access function as oldDoc is the persisted head, not whatever the caller claims, because the stored document is the only trustworthy evidence of who owns what. The separate delete action is gone from the ACL vocabulary entirely.
Everything downstream got simpler. Per-doc rules like "only the author may delete this" fall out of the same access-function line that already gates edits — a tombstone put on someone else's doc fails exactly the way a hostile edit fails. One less concept, one less asymmetry, one less door.
The decoration became the enforcement
Here's our favorite detail. Several of our shared-list apps had carefully hardened _deleted branches in their access functions — written by diligent authors who assumed deletes went through the gate. Before this change, those branches were decoration wearing enforcement's clothes: the platform never invoked them. After it, they are the enforcement, verbatim, without their authors touching a line. And family-grocery reverted its soft-delete workaround the next morning — plain database.del again, now properly gated. Both platform pivots shipped between two visits by the same shopper.
The lesson we keep relearning: when a mitigation makes you track a new kind of state (provenance bits, grant-origin ledgers), first ask whether the operation you're guarding is real. Delete wasn't. The right simplification didn't just close the hole the clever mitigation closed — it closed the holes we hadn't found yet, because the whole class of "this path skips access.js" stopped existing for destruction. If you're writing access rules for your own vibe, the model is now one sentence: everything that changes a document is a write, and your access function sees all of it.
Your app, your rules — one gate
Every vibe ships with an access function that governs every write, deletes included.
Start building →