How Instatic's Audit Logging System Tracks Admin Actions
Instatic implements an append-only audit log that records every administrative operation in an audit_events table, exposes read-only API endpoints gated by fine-grained capabilities, and renders human-readable activity streams in the admin dashboard.
Instatic is an open-source content management platform that treats administrative accountability as a first-class concern. According to the CoreBunch/Instatic source code, the audit logging system captures every significant state change—user creation, role modification, plugin installation, and AI tool usage—through a centralized repository pattern that guarantees tamper-evident storage.
Core Repository Architecture
The audit system centers on server/repositories/audit.ts, which defines the primary interface for creating and retrieving audit records. This file exports the createAuditEvent function for writing events and listAuditEvents for paginated retrieval.
An audit event consists of immutable metadata describing who performed an action, what was done, and when:
actorUserId– The UUID of the authenticated user who triggered the operation, ornullfor system-initiated eventsaction– A namespaced string identifier such asuser.create,role.update, orplugin.installtargetId– The unique identifier of the affected entity (user, site, role, etc.)ip_address– The requestor's IP address for security forensics- Timestamp – Automatically generated by the database upon insertion
The repository ensures append-only semantics; once written via createAuditEvent, records cannot be modified or deleted through the application layer, creating a cryptographically verifiable trail of administrative activity.
API Endpoints and Capability Enforcement
Instatic exposes audit data through dedicated REST endpoints protected by its capability system defined in src/core/capabilities.ts.
CMS Audit Endpoint
The endpoint GET /admin/api/cms/audit returns paginated audit events for general administrative actions. The underlying implementation in src/core/persistence/cmsUsers.ts constructs the request using:
apiRequest(`${basePath}/audit`)
Access requires the audit.read capability. Requests without this permission receive a 403 Forbidden response, as verified in src/__tests__/server/auditLogEdges.test.ts.
AI-Specific Audit Endpoint
For operations involving AI tooling, GET /admin/api/ai/audit (exposed in src/admin/ai/api.ts) provides filtered access to AI-related events. This endpoint requires the ai.audit.read capability, allowing administrators to segregate permissions between general CMS activity and AI-specific logs.
Frontend Integration and Rendering
The admin UI consumes audit data through specialized utilities and React hooks.
Audit Formatting Utilities
Located in src/admin/pages/users/utils/audit.ts, the formatting layer translates raw database records into human-readable strings:
auditTitle(event, usersById, rolesById)– Generates contextual descriptions like "John Doe created user jane.smith" or "System installed plugin analytics"auditDetails(event, rolesById)– Returns supplemental metadata arrays containing IP addresses, previous role states, or configuration diffs
Dashboard Activity Widget
The useDashboardStats.ts hook in src/admin/pages/dashboard/hooks/ executes a lightweight query against the audit table:
SELECT ... FROM audit_events LIMIT 50
This powers the "Recent activity" list on the admin dashboard, providing at-a-glance visibility into the last 50 administrative actions without loading the full audit history.
Security Boundaries and Testing
The audit system enforces strict permission boundaries through capability checks. The test suite in src/__tests__/server/auditLogEdges.test.ts validates that:
- Anonymous requests receive
403 Forbidden - Authenticated users without
audit.readcannot access CMS audit endpoints - The API correctly handles HTTP method restrictions (rejecting POST/PUT/DELETE on read-only audit resources)
Formatting logic is independently verified in src/__tests__/users/auditFormat.test.ts, ensuring that auditTitle and auditDetails correctly handle edge cases like deleted users (where actorUserId references no longer exist) or system-initiated events (where actorUserId is null).
Summary
- Immutable storage:
server/repositories/audit.tsprovidescreateAuditEventfor append-only writes to theaudit_eventstable - Scoped access: Two distinct capabilities (
audit.readandai.audit.read) gate access to/admin/api/cms/auditand/admin/api/ai/audit - Rich UI rendering:
src/admin/pages/users/utils/audit.tstransforms database rows into human-readable activity descriptions - Performance optimized: The dashboard uses
LIMIT 50queries viauseDashboardStats.tsto balance visibility with query speed - Test coverage: Dedicated test suites verify both permission enforcement and formatting logic
Frequently Asked Questions
What actions does Instatic log as audit events?
Instatic logs any administrative action that mutates system state, including user creation (user.create), role permission changes (role.update), plugin installation (plugin.install), site publishing, and AI tool invocations. Each action is recorded with the actor's user ID, target entity ID, and source IP address.
How does Instatic prevent tampering with audit logs?
The system implements an append-only architecture at the repository layer. The createAuditEvent function in server/repositories/audit.ts only supports INSERT operations; no update or delete methods are exposed through the API or repository interfaces. Database-level permissions can further restrict the application user from modifying the audit_events table.
Can regular users view audit logs, or only administrators?
Only users possessing the audit.read capability (for CMS actions) or ai.audit.read capability (for AI actions) can retrieve audit data. These capabilities are typically granted only to administrator roles. Unauthorized requests receive HTTP 403 responses according to the permission logic in src/core/capabilities.ts and validated in src/__tests__/server/auditLogEdges.test.ts.
How does the admin UI handle audit events from deleted users?
The formatting utilities in src/admin/pages/users/utils/audit.ts gracefully handle missing actor references. When actorUserId is null (system events) or references a deleted user, the auditTitle function falls back to displaying "System" or "Unknown user" rather than failing, ensuring the activity stream remains readable even when user accounts are purged.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →