{"record":{"id":"6f94aa454a2dc406","repo":"clockworklabs/SpacetimeDB","slug":"database-ownership-changed-before-deletion","errorCode":null,"errorMessage":"database ownership changed before deletion","messagePattern":"database ownership changed before deletion","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/standalone/src/lib.rs","lineNumber":433,"sourceCode":"                        spec.host_type,\n                        host.replica_id,\n                        spec.program_bytes.to_vec().into(),\n                        style,\n                    )\n                    .await\n            }\n            None => anyhow::bail!(\n                \"Database `{}` does not exist\",\n                spec.database_identity.to_abbreviated_hex()\n            ),\n        }\n    }\n\n    async fn delete_database(&self, caller_identity: &Identity, database_identity: &Identity) -> anyhow::Result<()> {\n        let Some(database) = self.control_db.get_database_by_identity(database_identity)? else {\n            return Ok(());\n        };\n        anyhow::ensure!(\n            database.owner_identity == *caller_identity,\n            \"database ownership changed before deletion\"\n        );\n        self.control_db.delete_database(database.id)?;\n\n        for instance in self.control_db.get_replicas_by_database(database.id)? {\n            self.delete_replica(instance.id).await?;\n        }\n\n        Ok(())\n    }\n\n    async fn reset_database(&self, caller_identity: &Identity, spec: DatabaseResetDef) -> anyhow::Result<()> {\n        let previous = self\n            .control_db\n            .get_database_by_identity(&spec.database_identity)?\n            .with_context(|| format!(\"Database `{}` does not exist\", spec.database_identity))?;\n        anyhow::ensure!(","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/standalone/src/lib.rs#L415-L451","documentation":"`delete_database` verifies that the identity requesting deletion still matches the database's stored `owner_identity` before removing it from the control DB. This TOCTOU guard prevents a caller from deleting a database whose ownership was transferred (or re-created under a different owner) between lookup and deletion. If ownership changed, deletion is refused instead of letting a non-owner destroy the database.","triggerScenarios":"Calling `delete_database(caller_identity, database_identity)` when `database.owner_identity != caller_identity` — i.e. the database was transferred to another owner, re-published by a different identity, or the caller is passing the wrong caller identity / wrong database identity.","commonSituations":"A developer publishes a database under identity A, transfers or re-publishes it under identity B, then an automation/CI job still holding A's credentials tries to delete it; or a shared test harness (e.g. `finish_module_test`) deletes databases with a stale or different identity than the one used at publish time.","solutions":["Check `database.owner_identity` via the CLI/API (`spacetime sql` on the database metadata or inspect the publish response) and delete using the identity that currently owns the database.","Re-authenticate with the credentials of the current owner instead of the original publisher.","If ownership transfer is intended, complete/verify the transfer first, then retry deletion with the new owner identity.","If the database identity is ambiguous, confirm you are targeting the correct database identity rather than another owner's database."],"exampleFix":"// before\nclient.delete_database(caller_identity = identityA, database_identity)\n// after (ownership moved to identityB)\nclient.delete_database(caller_identity = database.owner_identity /* identityB */, database_identity)","handlingStrategy":"validation","validationCode":"// before deleting\nlet database = control_db.get_database_by_identity(&database_identity)?;\nif let Some(db) = database {\n    if db.owner_identity != caller_identity {\n        // skip or re-authenticate as the current owner\n        return Err(anyhow!(\"cannot delete: owned by {}\", db.owner_identity));\n    }\n}","typeGuard":"fn can_delete(db: &Database, caller: &Identity) -> bool { db.owner_identity == *caller }","tryCatchPattern":null,"preventionTips":["Always delete databases with the same identity used to publish them","Track ownership changes and update automation credentials after transfers","Cache the owner identity alongside the database identity in test harnesses"],"tags":["rust","ownership","authorization","database"],"backgroundTag":"permission-denied","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-09-20T12:15:59.611Z","contentChangedAt":"2026-09-20T12:15:59.611Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}