Audit Logs
Every operation in Bella Baxter is logged — secret reads, writes, deletes, authentication events, key rotations, and administrative changes.
Access the Audit Log
From the WebApp: Project → Audit Logs
Planned — not in the CLI yet
bella audit list is on the roadmap and is not in the CLI yet. Until then, read the audit log in the WebApp (Project → Audit Logs) or through the REST API. The CLI examples on this page, including the filtering and export ones, show the planned syntax.
bella audit list --project my-api
bella audit list --project my-api --environment production
bella audit list --project my-api --since 2026-03-01
bella audit list --project my-api --actor alice@example.com
bella audit list --project my-api --event SecretReadLog Entry Fields
Each log entry contains:
| Field | Description |
|---|---|
timestamp | UTC timestamp |
event | Event type (e.g. SecretRead, SecretUpdated, ApiKeyRevoked) |
actor.type | User, ApiKey, TrustToken |
actor.email / actor.name | Who performed the action |
actor.ip | The caller's IP address — see Which address is recorded |
projectSlug | Project |
environmentSlug | Environment |
resource | What was acted upon (secret key name, provider name, etc.) |
outcome | Success or Denied |
requestId | Correlation ID for tracing |
The "App" label is reported by the client, not verified
The console's App column, and the bellaClient / appClient fields of a streamed audit record (client.bellaClient and client.app in the envelope), are what the caller said it was, taken from the X-Bella-Client and X-App-Client request headers. Bella does not verify them, so do not write a rule that treats either as proof of who called; the actor, API key or user id and the IP address are the fields Bella establishes.
Bella limits what a caller can put there. bellaClient is one of Bella's own clients (bella-cli, bella-webapp, bella-mcp, bella-dotnet-sdk, bella-js-sdk, and the other bella-*-sdk names), or the literal unrecognised when the caller sent anything else; it is empty only when the caller sent nothing. appClient is the name you gave your own application (bella exec --app), with control characters removed and cut to 64 characters.
Event Types
| Event | Description |
|---|---|
SecretRead | A secret value was read |
SecretCreated | A secret was created |
SecretUpdated | A secret was updated |
SecretDeleted | A secret was deleted |
SecretsBulkPulled | All secrets fetched (e.g. bella pull) |
ApiKeyCreated | An API key was created |
ApiKeyRevoked | An API key was revoked |
UserLogin | A user logged in |
UserLoginFailed | A failed login attempt |
TrustTokenIssued | A keyless OIDC exchange was accepted |
TrustTokenRejected | An OIDC exchange was rejected |
LeaseIssued | A short-lived lease was issued |
LeaseRevoked | A lease was revoked |
MemberAdded | A user was added to a project or environment |
MemberRemoved | A user was removed |
ProviderCreated | A new provider was configured |
SshKeySigned | An SSH public key was signed |
Filtering
# All events for a specific user
bella audit list --actor alice@example.com
# Only secret reads in production
bella audit list --environment production --event SecretRead
# Denied access attempts (potential security issue)
bella audit list --outcome Denied
# Export as JSON
bella audit list --output json > audit.json
# Export as CSV
bella audit list --output csv > audit.csvAccess Log vs Audit Log
Bella maintains two distinct logs:
| Access Log | Audit Log | |
|---|---|---|
| What it captures | Every API request (including reads) | All state changes + access (union of both) |
| Retention | 90 days (default) | 365 days (default) |
| Use for | Real-time monitoring | Compliance, investigations |
The Access Log is the raw per-request log. The Audit Log is the curated, event-typed log for compliance use.
Compliance
The audit log supports common compliance requirements:
- SOC 2 — access control evidence, change management trail
- ISO 27001 — access logging, incident trail
- HIPAA — audit controls for covered entities
- GDPR — data access trail for subject access requests
Audit log streaming to SIEM systems (Splunk, Datadog, Azure Monitor) is an Enterprise feature.
What a SIEM receives, and what CEF cannot carry
The JSON destinations (generic HTTPS, Splunk HEC, Datadog, Azure Monitor) receive every event as one JSON envelope whose raw member holds the whole stored audit row. A few of its members matter for detection rules:
raw.scopeNarrowing({ "providerKeys": n, "globalKeys": n }) is present on asecret_scope_deniedevent written when a token limited to some scopes listed secrets and some were left out. The counts are how many keys were hidden per layer (nullwhere that layer was not queried, never0for "not asked"); key names are never included. Asecret_scope_deniedevent without it is a refused read of one named secret.raw.trustDomainId,raw.presentedAudiencesandraw.audienceMatcheddescribe a token exchange or workload attestation: which trust domain was evaluated, which audiences the caller presented, and whether they matched. The presented audiences are values the caller supplied and Bella did not verify: at most 8 values of 256 characters, with control characters replaced. Treat them as untrusted input when you display them (events stored before this protection existed are not rewritten).
The CEF (syslog over TLS) destination carries only the promoted fields and no raw. It therefore cannot tell a narrowed listing from a refused read, and it does not carry the trust domain or the presented audiences. If a rule depends on those, use a JSON destination.
Retention
Default retention: 365 days. Contact your instance admin to adjust.
Which address is recorded
actor.ip is the address of the caller, not of the reverse proxy or CDN in front of Bella.
Bella reads the standard forwarding chain, and it believes an asserted originating address only when it arrives from a hop the deployment has declared trusted. Two consequences worth knowing:
- A caller cannot change what is recorded about it. Sending your own
X-Forwarded-Forheader does nothing: each hop appends, and the chain is walked from the connecting end until it reaches a sender that is not a declared hop — which is you. The same rule is what stops a caller obtaining extra rate-limit allowance by varying a claimed address. - If a deployment's trust is not configured, addresses degrade toward the proxy's — the older behaviour — never toward a value a caller supplied.
Operator configuration
Most deployments need none.
| Your setup | What to configure |
|---|---|
| Clients reach your own reverse proxy directly (DNS-only in front) | Nothing. DNS is not a hop. |
| A CDN or WAF fronts your proxy | One setting: ClientAddress__TrustedEdge=cloudflare |
Without the second one, rows on a CDN-fronted deployment carry the CDN edge's address rather than your customer's — which is the safe direction to fail in, but not the one you want.
Country and city labels, and what they disclose
The console can show a country and city beside an address. Doing so means sending that address to a third-party location provider, so it is off by default and stays off until an operator configures a provider endpoint.
- Turn it on with
ClientAddress__GeolocationEnabled=trueandClientAddress__GeolocationEndpoint=https://<your-provider>/…. The endpoint must behttps; a plainhttpendpoint is refused at startup, and the process will not boot with one. - Turn it off at any time by removing
ClientAddress__GeolocationEnabled. Lookups stop immediately; nothing else changes. Whether lookups are on or off never affects whether an access is recorded. - Internal addresses are never sent. Private, loopback, link-local, carrier-shared and reserved addresses — including ones written in an alternate notation such as
::ffff:10.0.0.1— are excluded before any request is made. - A failed, refused or slow lookup never delays or fails the operation being audited. The row is written with its address and no city.