ZKRINET Docs
Building blocks

Audit log

On Zkrinet: add the block under Building blocks in the panel — one click, your own code is untouched and a snapshot is taken first. The installation steps below apply if you run PocketBase yourself, for example after an export.

Who changed what, and when — for every record created, updated or deleted via the API (your app, the admin panel and batch requests). The log is tamper-evident: each row carries a SHA-256 hash of its own content plus the previous row's hash. If a row is changed or deleted afterwards, it shows immediately.

Tested against PocketBase 0.40.4.

Installation

Copy pb_hooks/ and pb_migrations/ to your PocketBase and restart. Logging starts immediately.

What is logged

One row in zk_granskning per change:

FieldContent
nrSequence number, without gaps
samling, post_id, handlingWhat: create / update / delete
vem_typ, vem_id, vem_samlingWho: superuser, anvandare or gast
andringarCreated: all values. Updated: only fields that changed, with before and after. Deleted: the values it had.
tidWhen (UTC)
ipOnly if turned on (see below)
foregaende, hashThe chain

Secrets are never logged. Hidden fields are shown as (dolt fält) and password changes as (lösenord) — neither the value nor the hash ends up in the log.

A change that changes nothing, and requests that fail, produce no row. A batch request that fails is rolled back completely — the log rows too — so the log never claims a change that did not happen.

Verify the chain

GET /api/zk/granskning/verifiera     (superuser)
→ { "ok": true, "rader": 1532, "sista_nr": 1532, "fel": [] }

If something is wrong, it states which row and what: "Raden har ändrats efter att den skrevs." ("The row has been changed after it was written.") or "Rad 40–41 saknas." ("Rows 40–41 are missing.")

The log cannot be changed or deleted via the API — not even by superusers.

Settings (environment variables)

VariableDefaultWhat
ZK_GRANSKNING_SAMLINGARallComma-separated list if only certain collections should be logged
ZK_GRANSKNING_IPoff1 = store the sender's IP. IP addresses are personal data — only turn this on if you have a reason, and mention it in your privacy policy.
ZK_GRANSKNING_DAGARoff (keep forever)Delete rows older than that many days (every night at 03:17, or POST /api/zk/granskning/rensa). Only the oldest consecutive rows are removed, so the chain stays intact.

The system's own collections (_superusers etc.) and plugin collections (zk_…) are not logged.

Limitations — read before you promise a customer anything

Test

./bank.py granskningslogg

12 tests: who/what/when, guest and superuser, that secrets are never logged, empty changes and errors, selection, IP, that the log is locked, that the chain reveals both changed and deleted rows, batch (successful and failed), and cleanup. The security tests are verified with mutants.