Chronicle
1.x
Docs/Chronicle/GDPR Erasure
Open

Reading3 min
Updated31 Jul 2026
Sourcev1/subsystems/erasure.mdx

Chronicle implements GDPR Article 17 (right to erasure) through crypto-erasure: each data subject's sensitive fields are encrypted with a per-subject AES-256-GCM key. On erasure request, the key is destroyed — making the encrypted payload irrecoverable — while the hash chain remains structurally valid.

Enable crypto-erasure01

c, err := chronicle.New(
    chronicle.WithStore(adapter),
    chronicle.WithCryptoErasure(true),
)

Or via the extension:

ext := extension.New(
    extension.WithStore(s),
    extension.WithCryptoErasure(true),
)

Record events with a subject ID02

When recording events, set SubjectID to associate the event with a data subject:

c.Info(ctx, "profile_update", "user", "user-42").
    Category("account").
    SubjectID("user-42").
    Meta("email", "[email protected]").
    Record()

Perform erasure03

Via the erasure service

import "github.com/xraph/chronicle/erasure"

svc := erasure.NewService(store, keyStore)

result, err := svc.Erase(ctx, &erasure.Input{
    SubjectID:   "user-42",
    Reason:      "GDPR Article 17",
    RequestedBy: "[email protected]",
}, appID, tenantID)

fmt.Printf("keyDestroyed=%v eventsAffected=%d\n",
    result.KeyDestroyed, result.EventsAffected)

Via the HTTP API

POST /v1/erasures

Request body:

{
  "subject_id": "user-42",
  "reason": "GDPR Article 17",
  "requested_by": "[email protected]"
}

Response: erasure.Result JSON.

What erasure does04

  1. Looks up all events with SubjectID = "user-42" in the given app+tenant scope.

  2. For each event, sets Erased = true, ErasedAt, and ErasureID.

  3. Destroys the per-subject encryption key from the KeyStore.

  4. Saves an erasure.Erasure record to the store.

  5. Returns an erasure.Result with KeyDestroyed = true and EventsAffected.

After erasure, attempting ks.Get("user-42") returns chronicle.ErrErasureKeyNotFound. The ciphertext in the store cannot be decrypted.

Hash chain integrity after erasure05

Hashes are computed over event metadata — not the encrypted payload. Destroying the key does not change any hash value, so VerifyChain still returns Valid = true for streams that contain erased events.

erasure types06

erasure.Input

FieldTypeDescription
SubjectIDstringThe data subject to erase
ReasonstringErasure justification (e.g. "GDPR Article 17")
RequestedBystringIdentity of the requestor

erasure.Result

FieldTypeDescription
IDid.IDTypeID of the erasure record
SubjectIDstringThe erased subject
EventsAffectedint64Number of events marked as erased
KeyDestroyedboolWhether the encryption key was destroyed

erasure.Erasure (stored record)

FieldTypeDescription
IDid.IDerasure_ TypeID
SubjectIDstringThe erased subject
ReasonstringErasure reason
RequestedBystringRequestor identity
EventsAffectedint64Events affected
KeyDestroyedboolKey destruction status
AppID / TenantIDstringScope

Crypto primitives07

The crypto package provides Encryptor (AES-256-GCM with random nonces) and KeyStore:

import "github.com/xraph/chronicle/crypto"

enc := crypto.NewEncryptor()
ks  := crypto.NewInMemoryKeyStore()

// Get or create the key for a subject
key, keyID, err := ks.GetOrCreate("user-42")

// Encrypt
ciphertext, err := enc.Encrypt(key, []byte("sensitive data"))

// Decrypt
plaintext, err := enc.Decrypt(key, ciphertext)

// Erase: destroy the key
err = ks.Delete("user-42")
// Any subsequent Decrypt with this key is impossible

For production, replace InMemoryKeyStore with a backend that delegates to AWS KMS, HashiCorp Vault, or similar.

HTTP endpoints08

MethodPathDescription
POST/v1/erasuresRequest erasure for a subject
GET/v1/erasuresList erasure records
GET/v1/erasures/:idGet a specific erasure record