Security model#
This page states what Sigilbase does to protect the ledger, as claims you can test rather than assurances you must take on trust. Where a claim is testable from outside, the test is given.
API keys#
A key is issued per tenant, optionally scoped to specific streams, and presented as a bearer token:
Authorization: Bearer sgb_k7f2m9qwx4njp8ce.4kJq...
The part before the final dot is the public key id; the part after it is the secret.
- The secret is stored only as a one-way hash. The full token is displayed once at creation and cannot be retrieved afterwards, by you or by Sigilbase staff, because it does not exist anywhere at rest. Test: create a key, close the dialog, and look for any way to see it again.
- Revocation is immediate. A revoked key fails on the next request with
401 {"message": "Invalid or revoked API key."}. Test: revoke a key and replay a previously working request. - Expiry is optional and immediate. A key created with an expiry date fails from that instant with
401 {"message": "API key expired."}; rotation issues a replacement with the same scope and revokes the old key in the same moment. Test: create a key expiring tomorrow and replay a request the day after. - Authentication failures do not leak key state. An unknown key id, a revoked key, and a wrong secret all return the same message, so the API cannot be used to enumerate which key ids exist. (Only a caller presenting the correct secret is told its key expired.)
- Scoping is enforced per stream. A key scoped to stream A receives
403on stream B, even inside the same tenant. Test: create a scoped key and request another stream with it. - Keys are write and read credentials, not verification credentials. Anything needed to verify (signing keys, evidence bundles, the verifier) works without any API key at all.
Immutability: three independent layers#
An event that has been accepted can only be changed by defeating all three of the following at once.
1. The application refuses. The models for events and checkpoints are append-only in code: no silent update or delete path exists anywhere in the application. Two narrow transitions are permitted, each one-way and each loud: stamping an event with its checkpoint id at sealing (once, from empty to set), and destroying an event's stored payload through the redaction ceremony described below — which preserves every hash and always appends its own ledger record.
2. The database refuses. Postgres triggers on the event and checkpoint tables reject UPDATE and DELETE statements at the database layer, regardless of what any application code (or anyone with the application's database credentials) attempts, permitting only the same two one-way transitions byte-for-byte. In production the application's database role additionally holds no UPDATE or DELETE right on these tables at all: sealing is a single-column grant, and redaction is reachable only through one SECURITY DEFINER function whose body is the allowlist. This is not a permissions setting that a misconfigured role bypasses; it is enforced by the tables themselves. Test, if you hold database access on your own instance's data via support arrangement: attempt an update on a sealed event and read the error.
3. The mathematics tells on you. Suppose both layers above were defeated, say by someone with raw disk access. Every event is hash-chained and every sealed range is signed, so the alteration is detectable by anyone holding an evidence bundle exported before the change, and by the nightly verification described below. Rewriting history consistently would require Sigilbase's private signing key, and even then would break the checkpoint chain against copies already exported. How verification works covers this layer in full.
The layers fail independently: a bug in one leaves two; the first two protect the live database while the third protects everything that has ever been exported.
Erasure without silence#
Append-only is a defence against silent rewriting, not a refusal to ever destroy content — data-protection law sometimes requires destruction. Sigilbase resolves the tension with one rule: no silent deletion exists. Content erasure exists: tenant-initiated only, hash-preserving, itself recorded in the ledger, structurally impossible to hide, and incapable of touching sequence, metadata, hashes, checkpoints, or anchors.
Concretely, a stream owner can redact the stored payload of named events. The payload is destroyed; everything else — sequence, actor, action, resource, timestamps, the payload hash, the entry hash, chain links, checkpoint membership, and anchor tokens — is preserved, so the chain verifies identically afterwards and the preserved payload hash can still convict a purported "original" that resurfaces. The act itself appends a payload.redacted event to the ledger it edited, evidence bundles declare every redaction (and the verifier fails any bundle where a payload is absent without a declaration), and redacted events render as tombstones, never as gaps. Platform operators — who cannot read customer payloads — are also structurally unable to destroy them: no operator-side path to redaction exists. GDPR and erasure covers when to use it and what auditors see.
Signing key custody#
- Checkpoint signatures use Ed25519. Private keys are held outside the application database; compromising the database does not yield the ability to sign.
- Private keys never appear in API responses, exports, or the UI. Public keys are published without authentication at
GET /api/v1/keys, including retired ones with their retirement timestamps, so bundles sealed under an old key remain verifiable forever. - Key rotation is append-only: keys are retired, never deleted, and every checkpoint records which key signed it.
Tenant isolation#
Every stream, event, checkpoint, and API key belongs to exactly one tenant, and tenancy is applied as a query-level scope on every access path. A request for another tenant's stream returns 404, not 403: the API does not confirm that a slug exists outside your tenancy. Test: take a valid key from one account and request a stream slug you know exists in another.
Security logging and privacy#
To keep accounts safe, Sigilbase keeps a security log separate from your tamper-evident ledger: sign-ins and failed sign-ins, sign-ins from a new device, lockouts, API-key authentication failures, and blocked attempts to reach another account. This is done under our legitimate interest in the security of the service.
We minimise what we keep. IP addresses are kept in full for up to 30 days for investigation, then reduced to a coarse network prefix (a /24 for IPv4, /48 for IPv6) for up to 12 months, then deleted. User agents are never stored in full — only a salted hash and a coarse family label (for example "Chrome" or "Bot"). This data is used for security only: it never feeds product analytics, never feeds the AI support assistant, and is never shared between tenants. Workspace owners can see their own account's sign-in activity, with addresses shown as a prefix, on the Security page.
Continuous self-checking#
Sealing is not the only scheduled job. Every night, Sigilbase re-verifies every stream end to end: the full hash chain, every checkpoint's Merkle root, the checkpoint chain, and every signature. Results are recorded and shown in the Verification page of the UI. A failed run is treated as the loudest possible state in the product, and it is deliberately impossible to dismiss without resolution. Test: the verification history for your streams is visible in your account, with timestamps and outcomes.
What to do if verification ever fails#
A failed verification means the stored data no longer matches its own seals. Export an evidence bundle immediately (bundles are verified at export, and earlier exports you already hold remain independently checkable), preserve the failure output, and contact Sigilbase. The design intent is that you never need to wonder whether a failure is real: the verifier's arithmetic either closes or it does not.