Claude Fable 5.1 & GPT-6 Astra packages are live

Audit Log

Free

Recording security-relevant events so a breach is detectable — what to log, what never to log, and making the record tamper-evident.

252 lines9.2 KB Claude Security
targetModels
Claude Fable 5.1Claude Opus 5Claude Sonnet 5Claude 5 FamilyFuture Claude Models
name
audit-log
category
Security
description
Recording security-relevant events so a breach is detectable — what to log, what never to log, and making the record tamper-evident.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Claude: scripts/model-profiles.json -->

<critical_constraints> FORBIDDEN: Truncating code or writing placeholders such as "// ... existing code ..." or "# rest unchanged". Every edit is complete and applies as written. FORBIDDEN: Reporting a check as passed without showing the command and its output. REQUIRED: Reason through the rules below before the first edit; when two rules conflict, the one stated first wins.

  • Never log only failures. A successful unauthorised action is invisible without the success record, and "who read this record" is the question an incident asks. </critical_constraints>

#Purpose

Rules for the audit trail: the record of who did what, to which object, and when.

This is distinct from application logging. Debug logs exist to fix bugs and may be sampled, truncated or dropped. An audit log is evidence — it must be complete, attributable, immutable and retained.

OWASP lists insufficient logging as a Top 10 category precisely because attacks are typically discovered months late, and the reason is usually that nothing recorded them. → Security/owasp


#What to record

<security_rules>

Every entry answers: who, what, which object, when, from where, and did it succeed.

json
{
  "timestamp": "2026-08-23T14:32:11.482Z",
  "actor": { "id": "usr_9f3", "type": "user", "ip": "203.0.113.9" },
  "action": "invoice.delete",
  "resource": { "type": "invoice", "id": "inv_44c", "tenant": "org_12" },
  "outcome": "denied",
  "reason": "insufficient_permission",
  "requestId": "req_7b2e",
  "userAgent": "Mozilla/5.0 …"
}

Name actions with a stable, hierarchical verb so the trail is queryable: auth.login, auth.mfa_challenge, user.role_change, invoice.delete, apikey.issue, export.download. Avoid free text like "user did a thing" — during an incident you need WHERE action = 'apikey.issue' to return in milliseconds.

Events that must always be recorded:

CategoryExamples
AuthenticationLogin success and failure, logout, MFA enrolment and challenge
AuthorisationDenials especially — a spike is an attack or a broken deploy
Account lifecycleCreation, deletion, password change, email change, role change
PrivilegeGrants, revocations, impersonation, break-glass access
Sensitive dataReads and exports of regulated records
ConfigurationSecurity settings, API key issuance and revocation, webhook changes
AdministrativeAnything an operator does to another user's data

Never log only failures. A successful unauthorised action is invisible without the success record, and "who read this record" is the question an incident asks.


</security_rules>

#What never to appear

<security_rules>

Never logWhy
Passwords, even failed attemptsPlaintext credentials in log storage
Session tokens, API keys, JWTsThe log becomes a credential store
Full card numbers, CVVRegulatory violation
Government identifiers, health dataRegulatory violation; use a reference
Full request bodies on auth routesCaptures the credential
process.envEvery secret at once → Security/secret-management

Redact at the point of writing, with an allow-list of fields to include rather than a deny-list of fields to strip:

js
const AUDIT_FIELDS = ["id", "email", "role", "tenantId"];   // allow-list

function auditSafe(obj) {
  return Object.fromEntries(
    Object.entries(obj).filter(([k]) => AUDIT_FIELDS.includes(k))
  );
}

A deny-list misses the field somebody adds next sprint. An allow-list fails closed.


</security_rules>

#Integrity

<security_rules>

An attacker's first move after gaining access is to remove the evidence.

  • Ship logs off-host immediately. A log that only exists on the compromised machine is not evidence.
  • Write to append-only storage — an S3 bucket with object lock, a write-once-read-many store, or a managed log service with retention locks.
  • The application's own database credentials should be able to insert audit rows and not to UPDATE or DELETE them.
  • For high-value trails, chain the entries so any edit is detectable:
js
// Each entry commits to its predecessor — removing or altering one breaks
// every hash after it.
const entryHash = crypto
  .createHash("sha256")
  .update(previousHash + JSON.stringify(entry))
  .digest("hex");

Storage choices that make deletion hard by design:

StoreMechanism
S3 / GCSObject Lock in COMPLIANCE mode with a retention period
PostgreSQLSeparate role holding INSERT only; no UPDATE/DELETE grant
CloudWatch / StackdriverRetention policy plus a resource policy denying DeleteLogGroup
SIEMIngest-only credential; the shipper cannot read or purge
sql
-- The application role can add to the trail and cannot rewrite it.
GRANT INSERT ON audit_log TO app_writer;
REVOKE UPDATE, DELETE, TRUNCATE ON audit_log FROM app_writer;
  • Use a trusted clock. Timestamps from an unsynchronised host make a timeline unreconstructable; require NTP and record in UTC with an explicit offset.

</security_rules>

#Making it useful

<security_rules>

A log nobody reads is storage, not security.

Fields worth standardising across every service, because they are the ones an incident query filters on: timestamp, actor.id, actor.ip, action, resource.type, resource.id, resource.tenant, outcome, requestId, schemaVersion. Emit them as structured JSON — pino, zerolog, structlog — never as an interpolated string.

  • Assign a request id at the edge and propagate it through every service so a single action can be reconstructed across a distributed call path.
  • Alert on patterns, not on individual lines: a burst of authorisation denials, a first login from a new country, a privilege grant outside change hours, a spike in export volume.
  • Test detection. Perform an unauthorised action in staging and confirm the alert fires. Detection that has never been exercised does not work.
  • Set retention deliberately — often 1 year, longer where regulation requires it — and ensure deletion is automatic once the period lapses.
  • Give the log a schema and a version. Ad-hoc string messages cannot be queried during the incident when queries matter most.

</security_rules>

#Privacy

<security_rules>

Audit logs contain personal data and are subject to the same regulation as any other store.

  • Restrict read access; log the reads of the audit log itself.
  • Store a user reference, not a copy of the user's personal details.
  • Have an answer for erasure requests before one arrives — usually pseudonymising the actor reference while retaining the event record, since the legal basis for keeping security records generally differs from that for the account.

</security_rules>

#Anti-patterns

Anti-patternWhy it failsFix
Logging failures onlySuccessful unauthorised actions are invisibleLog outcome on both paths
console.log(req.body) on loginCaptures the passwordAllow-list audit fields
Deny-list redactionMisses the next field addedAllow-list
Logs kept only on the hostAttacker deletes the evidenceShip off-host immediately
App credentials can DELETE audit rowsTrail is editable by the compromiseInsert-only permissions
No request idCannot reconstruct a distributed actionPropagate a correlation id
Free-text messagesUnqueryable during an incidentStructured, versioned schema
Alerts that have never firedDetection unverifiedExercise it in staging
Local time, unsynchronised clocksTimeline cannot be reconstructedNTP, UTC, explicit offset
Indefinite retentionRegulatory exposure growsAutomatic deletion at term

#Checklist

  • Authentication, authorisation, privilege and account-lifecycle events are recorded
  • Both successful and denied outcomes are logged
  • Entries carry actor, action, resource, tenant, outcome, time and source IP
  • Redaction is an allow-list; no credential or regulated identifier is ever written
  • Logs are shipped off-host and stored append-only
  • Application credentials cannot update or delete audit records
  • High-value trails are hash-chained
  • Timestamps are UTC from an NTP-synchronised clock
  • A request id is propagated across services
  • Alerts exist for denial bursts, privilege grants and export spikes
  • At least one alert has been triggered deliberately and observed to fire
  • Retention is defined, enforced automatically, and privacy-reviewed