Verify API logs and the region audit log
Catalyst can sign two logs as they are written, so you can check them without trusting the pipeline that stored them:
- The API log of each ID, agent and MCP server. Each ID signs every Dapr API call it records, with its own identity.
- The region audit log. Each region signs a record of every authenticated call to its API, with the caller the credential proved. That covers calls from the console, the CLI and your own automation.
This page shows how to verify both, list the calls in the region audit log, and save signed logs as evidence. For what each record covers and how the records fit together with workflow history, see Auditability.
Before you start
- Install the Diagrid CLI and log in with
diagrid login. - Turn signing on for the region. Both logs are off by default.
- The log store keeps signed lines for a few days, and the CLI reads at most 7 days back. To keep evidence longer, save it.
Turn on signing
Both logs are off by default, and each region turns them on separately. They cost some CPU on every API call, and they add lines to the region's logs.
On a self-managed region, set these values in the region's Helm values and upgrade the release:
agent:
config:
featureflags:
sidecar:
api_log_signing: true # each ID signs its API log
management:
config:
featureflags:
management:
audit_log: true # the region writes and signs its audit log
You can turn on either one without the other. An ID starts signing the next time it restarts, so IDs already running keep writing unsigned lines until then.
On a region Diagrid manages, contact Diagrid support to turn signing on.
Verification results
Each line, and the window as a whole, gets one of these results:
| Result | Means |
|---|---|
verified | Signed by the identity shown and unchanged since. |
tampered | A line was altered, injected or reordered, or was signed by an identity that isn't trusted or isn't the one expected. |
incomplete | The signature is genuine, but lines it covers are missing. The output lists the missing ranges. A deleted line and a line lost in transit look the same. |
unverified | No signature read so far covers the line. The newest lines wait a few seconds for the next signature, and lines at the edges of the window may have theirs outside it. On its own this is not evidence of tampering, but see What verification can't show for the start of a window. |
unsigned | The line was written while signing was off, before the ID restarted after signing was turned on, or by an ID with no identity. |
The verify commands exit non-zero when the result is tampered or incomplete, or when no record in the window verified. That makes them safe to use as a gate in a script.
Verify an ID's API log
Verify the last hour of an ID's API log:
diagrid app logs <app> --project <project> --verify
Checked 21 lines from 1 stream(s) against the signing authorities your project's region publishes.
verified 21 signed by the identity shown and unchanged
Verified records were written from 2026-10-29T09:00:03Z to 2026-10-29T09:59:41Z.
Stream afbf390ab7d36c3f2fc7a45e1f032afa
signed by spiffe://<region-trust-domain>/ns/prj-<project-id>/<app>
result verified, 18 of 18 records verified
Result: verified
Each Stream in the output is one run of the ID, and a restart starts a new one. The line count includes the signatures themselves, which is why it is higher than the record count. The Verified records were written from line is the stretch of time the result vouches for. Check that it reaches back to the start of the window you asked for; see What verification can't show.
The CLI checks that the log was signed by that ID in that project. Agents and MCP servers take the same flags:
diagrid agent logs <agent> --project <project> --verify
diagrid mcpserver logs <mcp-server> --project <project> --verify
Set the window with --since and --until, as an RFC 3339 time or a duration such as 24h. For a machine-readable result, add --output json.
Review the calls made to your regions
List every call to your regions' API in the last 24 hours, with each record's result:
diagrid audit list --region
TIMESTAMP ACTOR METHOD ROUTE PROJECT STATUS AUTH METHOD INTEGRITY
2026-11-02T14:48:25.376881861Z jane@example.com GET /apis/cra.diagrid.io/v1beta2/projects/{projectId}/workflowexecutions default 200 jwt verified
The actor comes from the credential the call authenticated with, never from a header the caller sets. Calls refused by the role check are recorded too.
audit list --region always exits zero, and warns when the window as a whole is tampered or incomplete. A deleted record has no row of its own to flag, so use audit verify to check the log as a whole:
diagrid audit verify --since 24h
audit verify checks that every record was signed by one of your regions, and exits non-zero on the same results as app logs --verify.
Save evidence for an auditor
Add --export to save the signed lines exactly as they were read, while verifying them:
diagrid app logs <app> --project <project> --verify \
--since 2026-10-29T00:00:00Z --until 2026-10-30T00:00:00Z --export app-2026-10-29.jsonl
diagrid audit verify \
--since 2026-10-29T00:00:00Z --until 2026-10-30T00:00:00Z --export audit-2026-10-29.jsonl
Save the signing authorities with them. These are the same ones signed workflow archives verify against:
diagrid workflow archive trust-anchor --project <project> --out trust.pem
Save the trust bundle at the same time as the logs, and keep them together. Signing authorities rotate, so a bundle saved later may not match older logs. That shows up as tampered against an untrusted identity, so check the bundle before concluding a file was altered.
Verify offline
A saved file holds the lines and their signatures, so it verifies with no connection to Catalyst. Name the identity the log must be signed by:
diagrid app logs <app> --verify --file app-2026-10-29.jsonl --trust-anchor trust.pem \
--app-id <app> --namespace prj-<project-id> --org <org-id>
diagrid audit verify --file audit-2026-10-29.jsonl --trust-anchor trust.pem --org <org-id>
The namespace and organization ID are in the signed by line and in the output of the original verification. The organization ID matters because each signature names the organization it was written for, so lines can't be moved from one organization's log into another's.
What verification can't show
- That a call happened as logged. A signature proves which ID or region wrote a line, not that what it wrote is true. This is the same limit as verifiable execution.
- The end of a log, or a whole log. Deleting the last records of an ID's run, or every line of one run, leaves nothing to compare against. A run that ended cleanly finishes with a final signature, so a run that stops without one is worth asking about.
- The start of a window. Records removed from the beginning of the window you read, together with their signatures, look like a window that starts later, and the rest still verifies. Hours can go from the front of a day's window this way. Check that the
Verified records were written fromtime reaches back to the start of the window you asked for. Records removed from inside the first signed range, without its signature, show asunverifiedrather thanincomplete. - Confidentiality. The logs are signed, not encrypted.
Reference
diagrid app logs,diagrid agent logsanddiagrid mcpserver logsdiagrid audit, includingaudit listandaudit verify- Verify workflow history