go-auditGo Audit

Changelog

Release notes for Go Audit.

mcp v1.1.2

Patch release for the MCP server (mcp/). Depends on core v1.1.1.

Bug fixes

  • mcp: date_from / date_to on query_data_logs and query_api_logs, and at on snapshot_entity, are now compared as instants on SQLite. The server parses these RFC3339 parameters and passes them to the core date filters, which on mcp/v1.1.1 and earlier bound them as zone-tagged strings; a value carrying a non-UTC offset could therefore match the wrong rows. Picking up core v1.1.1 fixes this (see below). PostgreSQL and MySQL were unaffected.

No tool changes: names, parameters and response shapes are identical to mcp/v1.1.1.

Upgrade

go install github.com/gopackx/go-audit/mcp@v1.1.2

v1.1.1

Core patch release. Adapter users should also pick up the matching adapters/bun/v1.1.1 and adapters/ent/v1.1.1 below; adapters/gorm is unchanged at v1.1.3.

Bug fixes (correctness — data loss on SQLite)

  • core / store: Purge, DataFilter.DateFrom/DateTo, APIFilter.DateFrom/DateTo and therefore Snapshot's at now compare instants on SQLite. created_at is written in UTC, but SQLite has no time type: the driver binds a time.Time as text including its zone offset and SQLite compares the strings lexicographically. A cutoff built in a local zone (... 20:00:00+07:00) sorted after every UTC row written in the same hour (... 14:00:00+00:00), so Purge(time.Now().Add(-1*time.Hour)) on a UTC+7 machine deleted every fresh row, and date filters missed or included rows by the zone offset. The SQL store now normalises every time parameter to UTC before binding. PostgreSQL (TIMESTAMPTZ) and MySQL (TIMESTAMP) already compared instants, so nothing changes there.

    Contract: created_at is stored in UTC; callers may pass a time.Time in any zone to Purge, Snapshot, Restore and the date filters. See Querying → Retention.

Upgrade

go get github.com/gopackx/go-audit@v1.1.1

No schema change. SQLite deployments in non-UTC zones should audit their retention jobs: rows newer than the intended cutoff may already have been purged on v1.1.0 and earlier.

adapters/bun v1.1.1

Patch release for the Bun adapter. Requires core v1.1.0 or later.

Behaviour changes

  • adapters/bun: ErrorFailLoud (the default) is now honoured as far as Bun allows. bun.QueryHook.AfterQuery runs after the statement and cannot change the error Bun returns, so the query cannot be aborted; previously the error was silently discarded. The hook now passes it to an error handler whose default logs via Config.Logger (log.Printf when nil). See Bun → Error Handling.
  • Under ErrorFailSilent nothing changed: the auditor logs via Config.Logger and swallows the error.

New

  • auditbun.Register(db, auditor, opts ...Option) accepts functional options. auditbun.WithErrorHandler(func(ctx context.Context, err error)) replaces the default handler so you can raise a metric, trip a health check, or mark the surrounding transaction for rollback. The error is go-audit: bun: record <action> <table>/<id>: <store error>.

Upgrade

go get github.com/gopackx/go-audit/adapters/bun@v1.1.1

adapters/ent v1.1.1

Patch release for the Ent adapter. Requires core v1.1.0 or later.

Behaviour changes

  • adapters/ent: ErrorFailLoud (the default) is now honoured. Previously the hook discarded audit-store errors, so a failed audit write under fail-loud went unnoticed. Mutate now returns go-audit: record <action>: <store error> (unwrap with errors.Is / errors.As) together with the already-applied mutation result, mirroring the GORM adapter's AddError. Bulk mutations still offer every row to the store and join the failures into one error. If your code relied on Ent mutations never failing because of the audit store, wrap them in a transaction or switch that table to ErrorFailSilent. See Ent → Error Handling.
  • Under ErrorFailSilent nothing changed.

Upgrade

go get github.com/gopackx/go-audit/adapters/ent@v1.1.1

Examples

  • _examples/gorm-basic and _examples/full-example: gorm.io/gorm v1.25.12 → v1.31.1, golang.org/x/text v0.14.0 → v0.20.0. All examples require go-audit v1.1.0 (still replaced by the local checkout) instead of v0.0.0. No library change.

adapters/gorm v1.1.3

Patch release for the GORM adapter only — core v1.1.0 is unchanged.

Bug fixes (correctness / performance)

  • adapters/gorm: the pre-write snapshot for primary-key-driven writes (db.Model(&p).Update(...), db.Save(&p), db.Delete(&p), db.Delete(&rows)) no longer scans the whole table. GORM adds the primary-key predicate only inside its own gorm:update / gorm:delete callbacks, after the snapshot has run, so the snapshot executed SELECT * FROM <table> with no WHERE (a 1-row update on a large table loaded every row). The adapter now builds the same primary-key predicate itself and ANDs it with any user WHERE, so the snapshot loads only the targeted rows.
  • adapters/gorm: WHERE-driven bulk updates and deletes (db.Model(&T{}).Where(...).Update(...), db.Where(...).Delete(&T{})) no longer produce a single bogus entry with entity_id "0" and empty old_values. They now emit one entry per row found by the snapshot, with that row's real entity_id, old_values from the snapshot, and new_values = the snapshot with the assigned columns applied (null for a hard delete, {"deleted_at": ...} for a soft delete).
  • adapters/gorm: db.Unscoped().Delete(&row) on a row that was already soft-deleted is now recorded as delete (with its old values) rather than soft_delete. The snapshot also respects Unscoped, so it finds the soft-deleted row.

Behaviour changes

  • With DataAudit.SkipOldValues: true, WHERE-driven bulk updates and deletes are no longer audited at all — without a snapshot there is no way to attribute them to rows. Previously they produced one misleading entity_id "0" entry. Primary-key-driven writes are still audited per row.
  • Writes run with AllowGlobalUpdate (no WHERE, no primary key) snapshot the whole table by design, since the write itself touches every row; each row gets its own entry.

Upgrade

go get github.com/gopackx/go-audit/adapters/gorm@v1.1.3

The adapters/gorm/v1.1.3 tag is pending; until it is pushed, this command will not resolve.

adapters/gorm v1.1.2

Patch release for the GORM adapter only — core v1.1.0 is unchanged.

Bug fixes (correctness)

  • adapters/gorm (issue #1, "A2b"): a parent Update that still carries its loaded HasMany associations no longer double-audits the children as create. Calling db.Model(&order).Update(...) while order.Items is still attached makes GORM re-upsert the children (ON CONFLICT DO UPDATE), which runs the create-callback chain a second time — so each child was recorded as create again (order_items/create == 4 instead of 2). Because this happens in the Update's transaction (separate from the original Create), the v1.1.1 transaction-scoped dedup could not see it.

    The adapter now distinguishes a genuine insert from an association re-save: a re-saved child already has a non-zero primary key before the statement runs, and the operation's root is an update/delete rather than a create. Such rows are skipped for create auditing. Genuinely new children added during an update (PK still zero) and all top-level / nested creates remain fully audited.

    Workaround on v1.1.1 and earlier: db.Model(&order).Omit("Items").Update(...).

Upgrade

go get github.com/gopackx/go-audit/adapters/gorm@v1.1.2

adapters/gorm v1.1.1

Patch release for the GORM adapter only — core v1.1.0 is unchanged.

Bug fixes (correctness)

  • adapters/gorm: nested Create no longer double-audits child rows on GORM ≥ v1.31 (the "2 items → 4 rows" report). The v1.1.0 dedup was scoped to a single GORM statement, but on GORM ≥ v1.31 one db.Create(&parent) runs the child create-callback chain twice within its auto-transaction — an initial INSERT and an FK-backfill upsert, in two separate statements — so the per-statement set never caught the repeat. Dedup is now scoped to the transaction, shared across every statement of one logical write, with a depth counter so it is freed when the outermost operation completes (no leak) and two explicit sequential writes of the same row are each still audited.

Upgrade

go get github.com/gopackx/go-audit/adapters/gorm@v1.1.1

If you installed adapters/gorm@v1.1.0, upgrade to pick up this fix. No schema change and no core upgrade required.

v1.1.0

Bug fixes (correctness)

  • adapters/gorm: db.Where("…").Update/Delete no longer emits WHERE WHERE … (PostgreSQL 42601, which previously cancelled the user's write). The pre-write snapshot SELECT now unwraps the clause.Where expression so the SQL builder writes exactly one WHERE.
  • adapters/gorm: Create with nested associations no longer duplicates child audit rows (2 items → was 4 rows). The callback keeps a per-statement (entity_type, entity_id) dedup set.
  • core: old_values on create is now persisted as SQL NULL instead of the literal JSON token "null". Affected callers that passed a typed-nil map[string]any.

New

  • APIEntry.ResponseHeaders (map[string]string) — redacted using the same RedactHeaders list as RequestHeaders, persisted into a new response_headers column on audit_api_logs.
  • RedactBodyFields now redacts struct and pointer payloads, not only map[string]any. The auditor JSON-round-trips the value first; matching is on JSON tag names.

Docs

  • README pulled to parity with the docs site: ExcludeEntities, SkipOldValues, per-table OnError, gorm.Session{SkipHooks: true}, adapter pseudo-version install, and a dedicated *sql.DB production tip.

AI integration

  • Claude Skill (skills/go-audit/SKILL.md) — drop into ~/.claude/skills/go-audit/ and Claude Code knows how to integrate go-audit from a cold prompt. See Claude Skill.
  • MCP server (mcp/) — read-only Model Context Protocol server exposing query_data_logs, query_api_logs, query_by_transaction, snapshot_entity, and recent_changes so AI hosts can investigate audit history conversationally. See MCP Server.

⚠️ Migration

AutoMigrate uses CREATE TABLE IF NOT EXISTS, so the new response_headers column is only created on fresh installs. Existing deployments must run a one-shot ALTER TABLE per dialect — see audit_api_logs → Upgrading from v1.0.x.

v1.0.0

  • Initial stable release
  • Core auditor backed by database/sql; zero network dependencies
  • Auto-diff on UPDATE — only changed fields stored
  • Manual entry point (RecordDataChange) for raw-SQL code paths
  • API call logging with header and JSON body redaction
  • Body truncation marker for oversized payloads
  • Transaction correlation via transaction_id and context helpers
  • Snapshot and Restore — reconstruct entity state at a past time
  • Retention via Purge(ctx, before)
  • Configurable error modes (ErrorFailLoud / ErrorFailSilent)
  • Custom table names for both audit tables
  • Field and entity exclusion
  • Dialects: PostgreSQL, MySQL, SQLite (with RegisterDialect for custom)
  • GORM adapter — full auto-hooks, soft-delete detection
  • Bun adapter — QueryHook with snapshot via WHERE-clause parsing
  • Ent adapter — mutation hook with single-row old-values
  • Query builder with typed filters (DataFilter, APIFilter)
  • In-memory Store for tests

On this page