Skip to content

Audit Log Reference

jitsudo maintains an append-only audit log in PostgreSQL. Every significant action produces an AuditEvent record with a SHA-256 hash of the previous entry, forming a tamper-evident hash chain.

{
"id": 1042,
"timestamp": "2026-03-20T16:00:00Z",
"actor_identity": "[email protected]",
"action": "request.created",
"request_id": "req_01J8KZ4F2EMNQZ3V7XKQYBD4W",
"provider": "aws",
"resource_scope": "123456789012",
"outcome": "success",
"details_json": "{\"duration_seconds\":7200,\"role\":\"prod-infra-admin\"}",
"prev_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"hash": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
FieldTypeDescription
idint64Sequential event ID. Monotonically increasing, never reused.
timestampRFC3339 (UTC)When the event occurred.
actor_identitystringThe user who performed the action — the IdP email. system for automated actions (expiry sweeper, etc.).
actionstringThe action that occurred. See Action Types below.
request_idstringThe associated elevation request ID, or empty if not applicable.
providerstringThe cloud provider involved (aws, gcp, azure, kubernetes), or empty.
resource_scopestringThe provider-specific scope (account ID, project ID, namespace), or empty.
outcomestringsuccess or failure.
details_jsonstringJSON object with additional context. Schema varies by action type.
prev_hashstringSHA-256 of the previous audit entry. "" for the first entry.
hashstringSHA-256 of this entry (see Hash Chain below).
ActionActorDescription
request.createdUserA new elevation request was submitted
request.approvedUserA request was approved by an approver
request.deniedUserA request was denied by an approver
request.revokedUserAn active request was manually revoked
ActionActorDescription
grant.issuedsystemCredentials were issued after approval
grant.expiredsystemA grant reached its natural expiry time
grant.revokedsystemA grant was revoked (triggered by request.revoked)
ActionActorDescription
policy.createdUserA new OPA policy was applied
policy.updatedUserAn existing OPA policy was updated
policy.deletedUserAn OPA policy was deleted
{
"provider": "aws",
"role": "prod-infra-admin",
"resource_scope": "123456789012",
"duration_seconds": 7200,
"reason": "Investigating P1 ECS crash",
"break_glass": false
}
{
"comment": "Approved for INC-4421 response"
}
{
"expires_at": "2026-03-20T18:00:00Z"
}
{
"policy_name": "sre-eligibility",
"policy_type": "eligibility"
}

The audit log uses a SHA-256 hash chain to detect tampering. Each event’s hash field covers the event’s content and links to the previous event.

The hash of each event is computed as:

SHA-256(prev_hash + "|" + id + "|" + timestamp + "|" + actor_identity + "|" + action + "|" + request_id + "|" + outcome + "|" + details_json)

Where prev_hash is the hash of the immediately preceding event (empty string for the first event).

To verify the hash chain is intact, compute the expected hash for each event and compare it to the stored hash field. Any modification to a historical event — including the prev_hash field — will cause all subsequent hashes to mismatch.

Example verification script (Python):

import hashlib, json, sys
def compute_hash(event):
parts = "|".join([
event.get("prev_hash", ""),
str(event["id"]),
event["timestamp"],
event["actor_identity"],
event["action"],
event.get("request_id", ""),
event["outcome"],
event.get("details_json", ""),
])
return hashlib.sha256(parts.encode()).hexdigest()
events = json.load(sys.stdin) # list of AuditEvent objects
for event in events:
expected = compute_hash(event)
if expected != event["hash"]:
print(f"TAMPERED: event id={event['id']} hash mismatch")
print(f" expected: {expected}")
print(f" stored: {event['hash']}")
sys.exit(1)
print(f"Chain intact: {len(events)} events verified")

Usage:

Terminal window
# Export events as JSON
jitsudo audit --output json > audit-export.json
# Verify the chain
python3 verify-chain.py < audit-export.json
Terminal window
# All events for the last 24 hours
jitsudo audit --since 24h
# All events for a specific request
jitsudo audit --request req_01J8KZ4F2EMNQZ3V7XKQYBD4W
# All events by a specific user in JSON
jitsudo audit --user [email protected] --output json
# Export a date range as CSV
jitsudo audit \
--since 2026-01-01T00:00:00Z \
--until 2026-02-01T00:00:00Z \
--output csv > january-2026-audit.csv

See jitsudo audit for the full CLI reference.

Terminal window
curl "https://jitsudo.example.com/api/v1alpha1/audit?\
since=2026-03-01T00:00:00Z" \
-H "Authorization: Bearer $TOKEN"

See the REST API reference for query parameters and response schema.

The audit log is append-only at the database layer. jitsudo uses serializable transactions to:

  1. Read the latest event’s hash field.
  2. Compute the new event’s hash using the latest as prev_hash.
  3. Insert the new event atomically.

No UPDATE or DELETE operations are ever performed on the audit table. Database-level permissions on the jitsudo role should enforce this (REVOKE UPDATE, DELETE ON audit_events FROM jitsudo).