Flagsmith / Flagsmith/flagsmith

Audit log shows a blank user for API key, system, and deleted-user changes

Open
#8,261 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
6.6k
Forks
567
Avg merge
1d 13h
Merged PRs (30d)
121

Description

### Is your feature request related to a problem?

Audit log entries frequently show an empty **User** column, with no indication of who or what made the change. A customer raised this on SaaS: "Sometimes no user is listed in the user section in the audit log. Do you know why this happens?"

There are three ways an entry ends up with `author = null`:

1. **The change was made with a Master API Key** (Terraform provider, CI scripts, direct Admin API calls). `AuditLog` has a separate `master_api_key` FK alongside `author`, and it is populated correctly - we just never return it. `AuditLogListSerializer` and `AuditLogRetrieveSerializer` only expose `author`, so the API response has no attribution at all for these entries.
2. **System events** (`is_system_event=True`), e.g. a scheduled Change Request going live via `create_feature_state_went_live_audit_log`. These legitimately have no human author, but the UI gives no signal that this is why the field is blank.
3. **The user was deleted.** `author` is `on_delete=models.SET_NULL`, so removing a member from the organisation retroactively blanks their name on every historical entry they created.

In the UI this is worse than in the API, because `AuditLog.tsx` renders `{author?.first_name} {author?.last_name}` with no fallback - so all three cases render as an empty cell that looks like missing data rather than a known state.

The impact is that the audit log stops being a reliable answer to "who changed this flag", which is its primary job. For customers using it for compliance or for tracing an unexpected production change, a blank cell is indistinguishable from a bug. Note that `is_system_event` *is* already returned by the API and unused by the row rendering, and `master_api_key` attribution already exists in the database - so in both cases the information is present and simply not surfaced.

### Describe the solution you'd like.

Never render an empty User cell. Specifically:

1. Add the Master API Key to the audit log serializers (at minimum `{id, name}` from `MasterAPIKey`) so API consumers can attribute API-driven changes.
2. In `AuditLog.tsx`, fall back through `author` -> master API key name (badged as an API key, e.g. `🔑 terraform-prod`) -> `System` when `is_system_event` is true.
3. For deleted users, show something explicit such as `Deleted user` rather than nothing. This one needs a data decision - see alternatives.

Item 1 and 2 are small and independent of item 3, and together cover the majority of reported blanks.

### Describe alternatives you've considered

- **Denormalise the author's email onto `AuditLog` at write time.** This solves case 3 properly (the historical record keeps the identity of whoever made the change, even after the user is deleted) but is a larger change and has GDPR implications for erasure requests, so it deserves its own discussion. Rendering `Deleted user` is the cheaper option and at least distinguishes "we don't know" from "no human did this".
- **Leave the API as-is and fix only the frontend.** Rejected: customers query the audit log API directly for SIEM and compliance pipelines, and the attribution gap exists there too.
- **Document the behaviour instead of changing it.** Doesn't help - the reason a user asks about this is that they need to know who made a specific change, and documentation doesn't give them the answer.

### Additional context

Relevant code:

- `api/audit/models.py` - `AuditLog.author` (`SET_NULL`), `AuditLog.master_api_key`, `AuditLog.is_system_event`
- `api/audit/serializers.py` - `AuditLogListSerializer` / `AuditLogRetrieveSerializer` field lists, neither includes `master_api_key`
- `api/audit/tasks.py` - `_create_feature_state_audit_log_for_change_request` creates `is_system_event=True` entries with no author
- `frontend/web/components/AuditLog.tsx` - renders `{author?.first_name} {author?.last_name}` with no fallback

Worth noting that the flag change webhook already handles this correctly: `trigger_feature_state_change_webhooks` in `api/features/tasks.py` falls back to `master_api_key.name` for `changed_by`. So a customer receiving webhooks currently gets *better* attribution than the same customer reading the audit log UI. Aligning the audit log with that existing behaviour seems like the obvious baseline.

Possibly relevant to #6304 (Epic: Audit Log Enhancements).

Raised from a SaaS customer support conversation.

Contributor guide

Open the contributing guide

Research direction

Start with api/audit/serializers.py and frontend/web/components/AuditLog.tsx, then compare attribution handling in trigger_feature_state_change_webhooks in api/features/tasks.py. Done means API responses expose master API key attribution and the UI distinguishes author, API key, system events, and deleted users without a blank User cell; the deleted-user behavior still needs a data decision.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, react
Domain
api, backend, frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.