carlos / platform Public

A deleted instance's replica is inherited by whoever next creates the same host, in any account Issue open

Found by Astra reviewing feat/carlos-db-read, and confirmed in code on main.

  • A hibernating instance's replica lives at activator.HibernatePrefix(prefix, host), which is the host only. No account or app is part of the key.
  • DeleteRecord (provision.go ~1067) and handleAPIInstanceDelete leave the parked database and blobs in place on purpose (spec §3.2).
  • Nothing in the activator's restore checks who the replica belongs to.

So: account A deletes shop.example.com. Later, account B gets that custom-domain namespace and creates the same host. On its first wake, B's instance restores A's database, and B's code can read all of it. The new carlos db pull door on feat/carlos-db-read reaches the same bytes without running any code. It adds no new exposure, but it makes the hole easier to hit.

This is the same family as the "bucket records are not a tenancy boundary" lesson: the key is shared, but the authority over it is not.

Ways to fix it, pending a decision:

  1. Write an ownership stamp (account, app, record creation id) under the prefix when the instance is created. The restore and the db door both refuse a replica whose stamp doesn't match. Leftover prefixes without a stamp need migrating once.
  2. When a record is deleted, move its replica to a per-account tombstone prefix. A purge clears it, and a create never finds old data.
  3. Make create refuse a host whose prefix still holds a replica from another record, until an operator purges it.

Option 1 is probably the cheapest one that works. Options 2 and 3 change what delete means.

Paul's rulings, 2026-10-02: archive an earlier incarnation's lineage in-region and start fresh (refuse if its record still exists); adopt unstamped data by the current record; keep push uploads and backups in-region. The design is on design-replica-ownership (docs/superpowers/specs/2026-10-02-replica-ownership-and-db-push-design.md, Part A). The local warm database is host-keyed too and never deleted, so the fix covers it as well as the replica.

Status, 2026-10-02. Three design revisions on design-replica-ownership failed Astra review:

  • rev 1, stamps on host-keyed storage: 16 P1s;
  • rev 2, storage rekeyed by incarnation and generation: 13 P1s;
  • rev 3, a delete-time tombstone and park: 11 P1s.

Almost every rev 3 finding asks for the same missing primitive. Delete, park, admitting a recreated host, move rounds and staging promotion, the db pull door and wake all act on a host without serialising against each other.

Paul's ruling:

  1. Stopgap now. On branch fix/199-create-refuses-leftovers, the instance and sidecar create doors refuse a host whose instances/<host>/ or moves/<host>/ in the console's own region holds replica data. It is read-only, and a listing error refuses rather than creates. It does not cover other regions or local-only leftovers.
  2. The real fix: one per-host lifecycle claim, designed jointly with the move-any-app spec (design-moves-any-region). #199 parking, db push and move fencing then all build on it. Rev 3's findings are the requirements list.

Stopgap merged as fe2088e3 (2026-10-02). The instance and sidecar create doors refuse a host whose instances/<host> or moves/<host> in the console's own region holds replica data; a failed check also refuses. It went through two Astra rounds, Paul approved the copy unchanged, and CI passed. It ships with the next release. The real fix is the host lifecycle claim (design-replica-ownership, rev 3 at 01385bce), under joint review with move-any-app and its executable model.