Skip to content

Account deletion - security view

The customer-facing walkthrough lives at guides/account-deletion. This page is the security-level companion: how anonymise-in-place actually works, what's preserved, what's deleted, and the auditable boundary between the two.

The architectural decision

GDPR Article 17 (right to erasure) is satisfied by either deletion or anonymisation - recital 26 makes anonymous data outside the regulation. Australian Privacy Principle 11 (Australia) lands at the same place via different language.

We chose anonymise in place rather than cascade-delete. Hard-purge would destroy audit trails, financial-record FK integrity, and provisioning telemetry - none of which is personal data, all of which is operationally needed. Anonymisation preserves the structure while making the bytes that were the customer unreadable.

Timeline

T+0          Deletion request submitted
             → All envs hibernated immediately (active billing stops)
             → Account + company marked as pending deletion
             → 30-day grace begins
             → Confirmation email sent

T+0..T+30d   Customer can sign back in (auto-cancel) or call cancel-deletion.
             BCDock staff can also cancel on the user's behalf via internal tooling.

T+30d        Scheduled anonymisation runs (background job set at T+0).
             → Personal data on the user account overwritten in place
             → If sole-owner Company: company name overwritten
             → Hibernation backup blobs deleted (real delete, not soft)
             → Session and OTP state deleted
             → A salted email hash is retained for trial-abuse suppression
             → A trial-history flag is retained if applicable
             → Status → `deleted`

What's overwritten

Personal contact and identity data on the user account (email address, display name, OAuth subject, last login timestamp, time zone) is overwritten to null.

What's retained on the user account and why

A small set of non-identifying data is kept:

  • Internal account identifier and creation timestamp - retained because audit records, usage history, and environment provisioning history reference the account by ID. Severing the link would break those records.
  • Auth-provider type - same as above.
  • A salted hash of the original email - retained for re-registration detection (see below).
  • A trial-history flag - retained for trial-abuse suppression on re-registration.
  • Account status set to deleted so the row is excluded from customer-facing APIs by default.

For sole-owner companies, the company is anonymised the same way (name and slug overwritten to generic placeholders). The structural identifiers and creation timestamp are retained for the same record-integrity reason.

For co-member companies, the user's membership is removed and ownership transfers to the next-oldest member if the deleting user was the owner; the company itself is untouched.

What's actually deleted

Not anonymised - deleted:

  • Hibernation backup blobs - the customer's BC database snapshots. Hard delete (bypasses the 7-day soft-delete that protects against accidental customer deletes).
  • Refresh tokens - ephemeral session state.
  • Email codes (OTP) - ephemeral auth state.

What's untouched

Not personal data:

  • Provisioning logs - keyed by environment / pool ID, with no user identifier. Operational telemetry.
  • Pool / VM / image records - platform infrastructure, doesn't reference users.

The salted email hash - legitimate-interest retention

After anonymisation, the user account carries a salted hash of the original email (SHA-256 of the lowercased email combined with a server-side salt, hex-encoded). The plaintext email is gone; the hash is kept. We use it for two purposes:

Re-registration detection

When a previously-anonymised email signs up again, the sign-in step computes the hash and finds the matching anonymised account. We restore the account in place (plaintext email re-attached, hash cleared, status active, display name reset). The customer is back; their old company is not (it stayed anonymised - they create a fresh one).

Trial-abuse suppression

If the restored account had previously consumed a trial, the new company it creates does not get a free trial. One trial per human, identified by email.

GDPR Article 17(3)(e) ("processing is necessary for the establishment, exercise or defence of legal claims") and APP 11.2(c) ("the entity is required by or under an Australian law, or a court/tribunal order, to retain the personal information") cover the limited retention. Defending against repeated-signup trial-abuse is a legitimate-interest grounds.

Salt handling

The salt lives in Azure Key Vault, never in the database, never in code or logs. It does not rotate by design - rotating would invalidate every existing hash and lose the suppression check.

If the salt leaks, an attacker with the database can rainbow-table common emails. Same threat model as password hashes; mitigated by Key Vault access controls.

Cross-account isolation

The deletion flow runs under the deleting user's context, with company isolation active. A user cannot trigger deletion of another user's account, even if they share a company.

Staff can also cancel a pending deletion on the user's behalf via internal tooling; the action is captured in the audit trail attributed to the operator.

Audit trail integrity

Audit records reference the user by internal ID. After anonymisation, the records still resolve - they point at an anonymised account whose ID is preserved and whose personal fields are null. The audit trail is continuous: "what happened" is preserved, "who specifically" is anonymised after +30d.

This is the load-bearing argument for anonymise-in-place over hard-purge. A hard purge would either break the link or cascade-delete the audit trail - both unacceptable.

Edge cases

Email recycling at the provider level

If a previously-anonymised email becomes the property of a new human (Gmail recycles abandoned addresses, custom domain ownership changes), that human will be treated as the original owner on first signup - the email hash matches and we restore the anonymised account to them. Whoever controls the inbox controls the account. Same threat model as password-reset-via-email everywhere; worth being explicit because it's the only path where one human can land in another human's BCDock account.

Email already taken by a different active user

If the original email is currently held by a different active user (rare but possible if the original owner anonymised, then someone else signed up with the same email before the original came back), restoration is skipped and the returning user falls through to a normal signup. The restored-account path requires the email to be currently unclaimed.

Threat model

  • Adversarial deletion - the --confirm <email> requirement on bcdock me delete blocks accidental deletion in agent-driven flows. The portal requires typing the email exactly. There's no zero-click path.
  • Anonymisation reversal - once anonymisation runs, the original email is unrecoverable from our side (only the salted hash remains). Restoration requires the customer to sign in with the original email, which only the inbox controller can do.
  • Salt compromise - would expose the hash to rainbow-table attack against common emails. Mitigations: Key Vault RBAC, no rotation (so a single point of leak), separate salt per environment in principle.