Audit and evidence
A record you can hand over: hash-chained, exportable, and clear about what it proves. Query it, verify it, export it for your SIEM, and score the host's live configuration against ISO 27001, SOC 2 and NIST 800-53 controls.
$ bin/audit-log export --format cef --stream git --day 2026-09-28 --decision deny --out audit-git-2026-09-28.cefThe same over HTTP, operator session onlyGET /api/audit/export?format=cef&stream=git&day=2026-09-28&decision=deny
CEF:0|vendor|product|0.1.0|git:push|push deny|8|rt=2026-09-28T09:14:02+00:00 act=deny duser=dev@example.test dvchost=product cs1Label=workspace cs1=acme/dev01 cs2Label=actorKind cs2=human cs3Label=repo cs3=acme/api cs4Label=reason cs4=validation-missing suid=7
Pick a format, streams, a day and a decision; the command is the real one. LEEF's tab delimiters are shown as <TAB>. The header's vendor and product fields carry the platform's own names on your host, shown here as vendor and product. Nothing is sent anywhere: export writes a file, and you ship it.
What it is, and what it is not
Kodela writes what happens on the host to an append-only, hash-chained audit trail: git, egress, model use, other provider keys, policy changes, detections, workspace lifecycle and identity. Each stream is a file of JSON lines. Credentials are redacted before a line is written. You can query it, verify it, export it in the formats a SIEM reads, and produce an evidence report from the live configuration.
It is tamper-evident, not tamper-proof: an edit breaks the chain and verify names the line, but the host's root user could rewrite a whole file. Nothing leaves the host unless you ship it. The evidence report is evidence for your audit, not a certificate.
Inside the console, each organisation reads its own share of the trail on its Audit tab, a dashboard for owners, admins and auditors, and its AI use on the AI usage tab.
How it works
- Eight streams
git,egress,claude(the Claude proxy),keys(the key broker),policy,detect,workspaceandidentity. Separate files, so a chatty stream cannot bury a quiet one.- The chain
- Each record carries the SHA-256 of the previous record with its own contents. Editing or deleting a line breaks every hash after it.
- Every record
ts,stream,action,decision(allow, deny, observe, info or error),workspace,user,actor,actor_user_id,actor_kind(human, agent, system or unattributed),repo,reason, and any details.- Authorship blocks
- A
push.authorshiprecord's block is also signed with HMAC-SHA256 under a key kept in the audit directory, mode 0600, named by id so a rotated key still verifies old records. - Rotation
bin/audit-log rotatedrops records older than the retention setting, restarts each chain from the first record kept, and writes arotatedrecord naming what it dropped. It keeps anything an organisation's longer retention still needs.
Defaults
| Setting | Scope | Default | Allowed |
|---|---|---|---|
audit_enabled | platform | true | true, false. Off also stops all identity mail. |
audit_retention_days | platform, organisation (raise only) | 90 days | 1 to 3650 |
bulk_read_threshold | platform | 500 files | 10 to 100000 |
bulk_read_window | platform | 60 seconds | 5 to 3600 |
bulk_read_detection in the profile | per profile | on under strict only | true, false |
ai_chat_retention_days | platform, organisation | 0: no time limit; chats are capped at 200 turns | 0 to 3650 |
| Export, host-wide | operator | on demand | cef, leef, syslog, json, csv |
| Export from an organisation's Audit tab | owner, admin, auditor | on demand: exactly the current filter | CSV, JSON; formula-defused for spreadsheets; recorded as org-audit-export |
| Sign-in records | platform | always on | password and shared-credential sign-ins, from orxy V1.7.2.2 |
| Evidence report | operator | on demand | iso27001, soc2, nist80053 |
Configure it
Retention
$ bin/policy set --key audit_retention_days --value 180
# one organisation keeps its own records longer
$ bin/policy set --scope tenant --target acme --key audit_retention_days --value 2555
# see what rotation would drop, then apply it
$ bin/audit-log rotate --dry-run
$ bin/audit-log rotateNothing runs rotation for you. Add a timer that runs bin/audit-log rotate, or the trail grows until you do. bin/backup includes the audit directory and its signing keys.
SIEM field map
Every format carries the same fields. Severity is set from the decision so an analyst can alert on denials: deny 8, error 7, observe 4, allow 2, info 1. CEF drops any custom field that is empty, together with its label, so parsers do not pair the wrong ones.
| Record | CEF | LEEF 2.0 |
|---|---|---|
| stream:action | signature id in the header | cat and action |
| ts | rt | devTime |
| decision | act, and severity | sev |
| user | duser | usrName |
| actor | suser | actorName |
| actor_user_id | suid | actorUserId |
| workspace | cs1 (label workspace) | workspace |
| actor_kind | cs2 (label actorKind) | actorKind |
| repo | cs3 (label repo) | repo |
| reason | cs4 (label reason) | reason |
Use CEF for Splunk, Microsoft Sentinel or ArcSight; LEEF for QRadar; JSON for an HTTP event collector or Elastic; syslog for RFC 5424 framing of CEF, facility local0; CSV for a spreadsheet. Records are exported oldest first.
The evidence report
bin/compliance-report runs a check for each technical control against the running host: it reads the firewall, re-walks the audit chain, reads a real password record. It scores 13 technical controls MET, PARTIAL or NOT MET, and lists 6 organisational controls (risk, people, suppliers, physical, continuity, incident response) as ORG: yours, never counted as a pass.
$ bin/compliance-report # text, all three frameworks
$ bin/compliance-report --framework soc2 # text, SOC 2 references only
$ bin/compliance-report --failures-only # text, only PARTIAL and NOT MET
$ bin/compliance-report --json > evidence.json
$ bin/compliance-report --html evidence.htmlThe technical controls are identification, authenticator strength, account management, access enforcement, session revocation, logging, audit protection, audit review and export, network boundary, outbound traffic, secrets, encryption in transit and change management. --framework and --failures-only shape the text output; JSON and HTML always carry every control. The command exits 1 when any control is NOT MET, so a script can gate on it.
The organisation's Audit tab
The organisation's Audit tab (owners, admins and auditors) is a dashboard: a timeline by day, refused counts, sections by question, an integrity panel, and an export of the current filter that is itself audited. Up to 90 days, never past your retention; it shows only records provably the organisation's own.
- Timeline and counts
- It opens on recent activity, grouped by day. Counts of refused and would-be-refused actions filter the view with one click. Every record is written as a sentence.
- Sections
- People & access, AI activity, Code & provenance, Network, and Workspaces & settings. Approvals decided in the organisation's plans appear here too.
- One record
- Opening a record shows how it happened: authorship, agent run, approver and model. You can copy a link to it, or show it exactly as stored. No secret value is shown, because records are redacted on write.
- Integrity panel
- Says whether the audit log's hash chain verifies and how many of the organisation's pushes carry a verified signature, or that the check did not report. It names streams only, never a line.
- Export
- Exactly the current filter, as CSV or JSON. The file is formula-defused for spreadsheets, and the export is itself recorded in the audit.
- Withheld records
- Records that merely carry the organisation's name, from workspaces with no record of belonging to it, are withheld; the screen says how many and why.
Sign-in records
Password and shared-credential sign-ins are recorded from orxy V1.7.2.2 on (single sign-on sign-ins are recorded as “signed in with SSO”); earlier sign-ins are not in the trail. Password sign-ins at the portal and the operator console are recorded under People & access, allowed and refused. The password is never recorded, nor a token or session. An address that is not an account gets one host-level record with no address in it, never shown to an organisation, so the audit cannot be used to test which addresses are accounts. A flood becomes at most one record a minute per source, account, outcome and method, carrying the count. Recording never slows or blocks a sign-in: if its queue is full the record is dropped and counted.
The AI usage tab
An AI usage tab for organisation admins and auditors: requests, tokens and cost by member, workspace, surface, model and account. There is no spend cap; cost shows only where the cost meter is on; AI tools that hold their own login inside a workspace are not measured. Each download of its usage report is audited as ai-usage-report. See AI agents.
Tasks
Send a day of denials to your SIEM
- Export.
bin/audit-log export --format cef --stream egress --stream git --day 2026-09-28 --decision deny --out denials.cef. Result:N records -> denials.cef (cef)on stderr. - Ship the file with your collector: a forwarder watching a directory, or a scheduled copy. Kodela does not push.
- Map the fields once, from the table above. Alert on severity 8.
Prove the record to an auditor
- Verify the chain.
bin/audit-log verify. Result:every chain verifies - no record has been edited or removedand exit code 0. - Keep the output with the export you hand over, or run it in front of the auditor on the host.
- If it fails, the output names the stream and line, for example
git.jsonl:1042 hash does not match - this line or an earlier one was edited or removed, and the exit code is 2.
Prepare for a SOC 2 evidence request
- List what fails.
bin/compliance-report --framework soc2 --failures-only. Result: only the PARTIAL and NOT MET rows, each with the evidence the check saw. - Fix the NOT MET controls the evidence names, then run it again.
- Write the report.
bin/compliance-report --html evidence.html, and attach it with theverifyoutput.
Review an organisation's week without the host
- Open the Audit tab as an owner, admin or auditor: OrganisationsacmeAudit Result: a timeline of recent activity, grouped by day.
- Press the refused count. Result: only refused and would-be-refused records, each as a sentence.
- Check the integrity panel. Result: whether the chain verifies and how many pushes carry a verified signature, or that the check did not report.
- Export the view as CSV or JSON. Result: a file with exactly the current filter; the export itself appears in the audit.
Answer the four questions for one day
- Ask them.
bin/audit-log questions --day 2026-09-28 --user dev@example.test. Result: who cloned what, who pushed where, what was denied, what left the host, and signals raised, each with its records. - Narrow it.
bin/audit-log query --workspace acme/dev01 --decision deny --jsonfor the rows behind an answer.
Find organisations looser than the default
- Run the posture audit.
bin/tenant-audit, or--json. Result: a row per organisation and a verdict, naming any that are handed real credentials in their containers.
Verify it works
$ bin/audit-log query --day 2026-09-28 --stream policy
$ bin/audit-log verify
every chain verifies - no record has been edited or removed
authorship: 12 signed and verified, 0 unsigned (written before signing, or with no key available)Good looks like: recent records in every stream you use, verify exiting 0, and the organisation's Audit tab showing that organisation's records on its timeline, with an integrity panel that says the chain verifies. Organisation owners, admins and auditors can read that tab and export its current filter; a plain member is not offered it. The host-wide HTTP export is for operators only.
Troubleshooting
hash does not match - this line or an earlier one was edited or removed
The chain is broken at that line. Treat it as an incident: keep the file, compare it with the latest backup, and find who had write access.
authorship signature does not match - the authorship block was edited
The chain may be intact while the signed block was changed and re-hashed. Only something holding the host key can do that; treat it as a root-level event.
audit-log: cannot read stream(s): …
A query exits 3 when a stream file is unreadable, so an empty answer is never mistaken for none. Check the file's owner and mode.
audit is disabled or the write failed
audit_enabled is off, or the audit directory is not writable. Turn it on; with it off, identity mail stops too.
audit_retention_days may only be raised above the platform's 90, never lowered
An organisation cannot keep less than the platform. Lower the platform value if you want less for everyone.
HTTP export answers 403 {"error": "admin only"}
The host-wide export route needs an operator session. Organisation owners, admins and auditors export their own records from the Audit tab instead.
The Audit tab says records were withheld
They carry the organisation's name but come from workspaces with no record of belonging to it, so the tab does not show them.
What it does not do yet
- Built-in forwarding to a SIEM. Export writes a file; shipping it is yours.
- Protection against the host's root user. The chain and the authorship key live on the host. Ship exports off the host if that matters.
- Scheduled rotation. Nothing runs
rotatefor you. - Mappings for the EU AI Act, GDPR, HIPAA or DORA. The report covers ISO 27001:2022, SOC 2 and NIST 800-53 r5 only.
- Certification. Kodela holds none. The report is evidence you use in your own audit.
- The Audit tab's window is 1 to 90 days and never past the audit retention; it is paged and bounded by size and time, and it shows only records provably the organisation's own.
- Sign-ins before orxy V1.7.2.2 are not in the trail.
- No spend cap. The AI usage tab measures; cost shows only with the cost meter on, and AI tools that hold their own login inside a workspace are not measured.
Related
Verified against the main branch, 4 Oct 2026.