Skip to content

docs: audit logs, exporting them to a SIEM, and the event format - #734

Open
eseidel wants to merge 7 commits into
mainfrom
docs/audit-logs
Open

eseidel wants to merge 7 commits into
mainfrom
docs/audit-logs

Conversation

@eseidel

@eseidel eseidel commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Enterprise customers can read their organization's audit log in the console and through GET /organizations/{id}/audit-logs, but nothing in the docs says it exists, who can read it, how to export it to a SIEM without missing events, or what an event looks like. Security teams evaluating Shorebird ask for exactly this.

This adds /account/audit-logs/:

  • What the audit log records, the plan (Enterprise) and roles (Owner, Admin, or a service account with the Auditor role) that can read it, the 90-day availability guarantee, and the org and app Audit Log tabs in the console.
  • Exporting to a SIEM: an Auditor service account, polling with order=asc, saving next_cursor, deduplicating on metadata.uid, and one sentence on why the newest minute is held back. Examples with curl and shorebird api.
  • The format: OCSF 1.9.0 API Activity, one sample event, and the fields people filter on (api.operation, actor types including Shorebird staff, actor.session and when is_mfa is absent, api.token, src_endpoint, unmapped).
  • The current event names, grouped by resource, one line each. The page says names can change and how a retired name appears.

It also lists the audit log on the Enterprise page and adds ocsf, siem and urlencode to the cspell words.

The service account and trust rule pages are in #733. Links to them use absolute https://docs.shorebird.dev/account/service-accounts/ and /account/trust-rules/ URLs (as #733 does for this page) so each PR builds on its own. They can become relative links once both land.

Landing

After all of:

  • docs: service accounts and trust rules #733 lands. Then rebase this branch and switch the https://docs.shorebird.dev/account/service-accounts/ and /account/trust-rules/ links to relative ones, so the link validator checks them.
  • Abhishek's approval (Eric has LGTM'd).

_shorebird#3620, #3615 and #3616 are all in prod (#3620 shipped in Deploy All run https://github.com/shorebirdtech/_shorebird/actions/runs/39080022147).

🤖 Generated with Claude Code

@eseidel

eseidel commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Accuracy review against the server and CLI code

These checked out against the server: oldest-first cursor echo on empty pages, the 60s settle window, per_page clamped to 100 (default 30), inclusive date filters, the 400 on an unknown operation, and the OCSF sample's field names, nesting and string-typed ids. Both examples work as written in bash.

Sev Where Issue Resolution
🔴 Blocker Events, "Accounts and billing" user.display_name_update, user.delete, billing.overage_limit_update and billing.overage_subscription_create are recorded in the acting user's personal organization, never in a team organization's log, so a SIEM alert on them would never fire. Fixed in c06d0aa: section removed; the page now says changes to a person's own account aren't part of an organization's audit log.
🟡 Watch organization.delete A completed deletion's event is written to the organization being deleted, so it can't be read afterward. Only a partial failure stays visible. Fixed: described as a deletion that stopped partway, with a note that a fully deleted organization's log can't be read.
🟡 Watch Rename note Names are computed at read time, so a rename relabels past events too. The raw resource_type.action fallback applies only to a retired stored pair, and releases.update was a wrong example. operation rejects fallback names. Fixed: rewritten as described; the example was dropped and the page notes that operation takes only listed names.
🟡 Watch CLI snippet Uses bash arrays under an sh fence, and depends on the curl script's variables. Fixed: fences are bash, and the snippet says it replaces the curl call in the script above.
🟡 Watch curl script [ -n "$next" ] && ... exits 1 on a first run with no events, which a scheduler reports as a failure. Fixed: uses if ...; then ...; fi.
🟡 Watch actor.user For a service account, email_addr holds the name[bot] label, not an address. Fixed: the page says to identify a service account by uid. A server change to stop serving email_addr for service accounts is open separately.
🟡 Watch actorId filter Deleting a user clears the actor id on their past events, so actorId no longer finds them. Fixed: noted under actorId.
🟡 Watch unmapped.actor_source none was missing, and metadata.correlation_uid wasn't documented. Fixed: both added.

One more fix from the same pass: the sample event was a console action with a browser user agent. Console actions reach the API from the console's servers, so the sample is now a shorebird patches rollback with the CLI's user agent.

@eseidel

eseidel commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor Author

Pushed 46321e3: the ci keys under unmapped are run_id and rule_id, not runId/ruleId, to match the server. Prettier also reflowed one paragraph in the same list.

…Id skips staff

The CLI user agent in the sample isn't sent by any released CLI. Console
actions now record the person's IP and browser user agent (_shorebird#3620).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eseidel

eseidel commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Adversarial review (independent agent with no context from the authoring sessions, at 52d6ef5, checked against _shorebird main, the public CLI source, and docs#733)

No factual errors in the event mapping, cursor logic or permissions. Findings:

  1. Blocker unless it lands after docs: service accounts and trust rules #733. Links to /account/service-accounts/ (including #issue-and-revoke-keys) and /account/trust-rules/ are absolute URLs. Those pages exist only in docs: service accounts and trust rules #733, and starlight-links-validator doesn't check absolute URLs, so landing docs: audit logs, exporting them to a SIEM, and the event format #734 first publishes dead links with green CI.
  2. Should-fix. Some claims depend on server changes that may not be in prod yet: console IP and user agent (_shorebird#3620, #3624); service account name and no email_addr (#3613, #3615); snake_case ci keys (#3616); is_mfa, session, staff redaction and shared-pair names (#3585, #3581, #3584, #3593).
  3. Should-fix. metadata.correlation_uid is the caller's X-Request-ID header (request_context_extensions.dart:362, and the same in the auth and admin writers), not a Shorebird request ID. Nothing in the server, console, CLI or infra sets it.
  4. Should-fix. The intro says the log records who "issued an API key". Only service account keys are audited. Console and CLI sign-ins by people, and refused requests, aren't in the log either.
  5. Should-fix. In the curl script, ${cursor:+--data-urlencode "cursor=$cursor"} stays one word in zsh (verified), so the second run fails on the macOS default shell. || exit 1 also closes an interactive terminal if the script is pasted in.
  6. Should-fix. "At least 90 days" isn't traceable to code. There's no purge job, so nothing contradicts it, but confirm it's an intended commitment.
  7. Nits:
    • A deleted service account's events also lose actor_id (ON DELETE SET NULL), not just a deleted person's.
    • "operation takes only the names listed here" is wrong: the four personal-account events are accepted too.
    • service_account.sign_in details are flat unmapped keys (repository, run_id, job_workflow_ref, rule_id, project_id, ...), not under ci.
    • actor_source is absent on older rows.
    • Timestamps without a zone are parsed as server-local (UTC).
    • shorebird api needs CLI 1.6.129 or later.
    • docs: service accounts and trust rules #733 says the actor appears as name[bot]. That's the console label; the API name has no [bot].
    • Outside this PR: unreleased CLI --paginate will loop-detect and exit 1 on order=asc, because oldest-first always echoes the cursor.

Checked and correct:

  • Access: the Enterprise gate; Owner, Admin and Auditor access.
  • Endpoint and query parameters: the path; order (default and 400); per_page 30/100; inclusive dates; actorId excluding staff; 400 on an unknown operation; 429 with Retry-After.
  • Cursor and polling: the cursor echo on empty oldest-first pages; the 60s hold-back covering every writer.
  • OCSF fields: required API Activity attributes; type_uid; activity ids; the sample against toOcsf() and the rollback route's metadata; staff redaction; {"name":"unknown"}; api.token; session and is_mfa semantics; metadata.uid and tenant_uid.
  • Events: the full event list against the registry; the rename and fallback rules; organization.delete partial-only.
  • Console and CLI: the console tabs, filter and detail fields; the org ID in the URL; CLI -X, -f, SHOREBIRD_TOKEN, and the exit code.

Script runs in bash and zsh; what isn't recorded; correlation_uid is the
caller's X-Request-ID; sign-in details are flat unmapped keys; deleted
service accounts; actor_source can be absent; dates need a zone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eseidel

eseidel commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Review summary. Every claim on the page has been checked against _shorebird main (audit-logs/index.dart, pagination.dart, select_audit_logs.dart, audit_log_ocsf.dart, audit_event.dart, capabilities/, the console audit log pages, and the auth service writer), ADR-0021, and the SIEM design doc. Fixes are in 52d6ef5 and 7bc77df. The docs build, Prettier, cspell and Vale pass. The poll script was run under both bash and zsh against a stub curl, across a first run and a run with a saved cursor.

From my own pass (52d6ef5)

  • The sample event's user agent, shorebird-cli/1.6.129 (macos; dart …), isn't sent by any released CLI. It's on CLI main, not in v1.6.129. The sample is now a console rollback with a browser user agent.
  • The page now says console actions record the person's own IP and browser user agent (_shorebird#3620).
  • The page now says actorId never matches events by Shorebird staff.

Adversarial review findings

  1. Links into docs: service accounts and trust rules #733. Answered, not changed yet. Landing now requires docs: service accounts and trust rules #733 to land first. Before merging, this branch is rebased and the links become relative, so the link validator checks them. Switching now would fail CI here.
  2. Prod dependencies. #3615 and #3616 are in prod (Deploy All to Prod, 10-17 01:09 and 02:43 UTC). #3613, #3581, #3584, #3585 and #3593 were verified in prod on 10-16 (design doc). #3620 (console IP and user agent) merged after the last prod deploy, so Landing waits for the next Deploy All to Prod.
  3. correlation_uid. Fixed: the page says it is the X-Request-ID header the request sent.
  4. "Issued an API key". Fixed: the page now says "a service account key". A new sentence says sign-ins, personal API keys and refused requests aren't events of their own; each event names its session or key instead.
  5. zsh. Fixed: the script is a #!/usr/bin/env bash script with set -euo pipefail and array-built query arguments, which work in both shells. || exit 1 is gone. The CLI variant uses the same pattern.
  6. 90 days. No change. It's an intended minimum, from the design doc ("Docs state … at least 90 days. Rows are kept longer in practice, with no purge job.").
  7. Nits.
    • Fixed: deleted service accounts are now covered by the actorId note.
    • Fixed: the operation wording is now "takes event names, not that fallback form".
    • Fixed: service_account.sign_in details are described as flat unmapped claims (repository, workflow_ref, run_id, ref, rule_id), not under ci.
    • Fixed: actor_source "absent on older events".
    • Fixed: dates now say "with a time zone".
    • Fixed: the page says shorebird api needs CLI 1.6.129 or later.
    • Not changed here: name[bot] belongs to docs: service accounts and trust rules #733.
    • Not changed here: --paginate with order=asc is unreleased CLI code, outside this PR.

Open for Eric, not changed: console events recorded before #3620 deploys carry the console server's IP and the user agent node. A SIEM backfill will see those. I left this off the page because there's no deploy date to state yet.

@eseidel

eseidel commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

lgtm

@eseidel

eseidel commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Landing status at 7bc77df: CI is green and Eric has LGTM'd. Three things still hold this PR:

  • docs: service accounts and trust rules #733 is still open, waiting on Abhishek's review. Once it lands, this branch gets rebased, the service account and trust rule links become relative, and CI runs again.
  • _shorebird#3620 isn't in prod yet. The last Deploy All to Prod (10-17 02:43 UTC, 0bf9a8d) is from before it merged. The page says console actions record the person's IP and browser user agent, which is only true after the next Deploy All.
  • An approving review is still needed. Branch protection requires one; that should come from Abhishek's docs review.

I'll merge through the queue once all three hold.

@eseidel

eseidel commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

@AbhishekDoshi26 this is ready for your review: the public audit log and SIEM export page. Eric has LGTM'd the content, and the accuracy and adversarial reviews are above, with every finding fixed or answered. It lands after #733 (please review that one too), so the links between the two pages can become relative.

#733 renamed #issue-and-revoke-keys to #create-and-revoke-keys, and the
console now creates a service account with the Developer role first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant