Skip to content

Querying the audit log

Every install request, every block, every admin mutation, and every authentication event writes one or more rows into audit_log. The audit row is the canonical “why did this happen” record. This page shows the queries that come up most often when you’re triaging.

There are two surfaces for querying:

  • The dashboard’s audit page at /admin/ui/orgs/<slug>/audit — point-and-click, with filters for time window, decision, package name, actor, subject. Tier-driven retention applies (Free 7d, Team 30d, Org 365d, Enterprise unlimited).
  • The JSON admin API at GET /api/v1/admin/orgs/<slug>/audit?since=&until=&package=&decision=&event=&actor=&subject=&cursor=&limit= — scriptable, same filters as the dashboard, cursor pagination.

For per-tenant operators most queries below show the JSON-API shape. The dashboard surface mirrors it.

Terminal window
curl -fsS \
-H "x-upwarden-admin-token: $ADMIN_API_TOKEN" \
"https://vg-admin.<your-domain>/api/v1/admin/orgs/<slug>/audit?package=foo&decision=BLOCKED&since=2026-06-03T00:00:00Z&limit=5" \
| jq '.entries[] | {created_at, version, decision_reason, verdict_source}'

The decision_reason envelope names the specific layer that fired (oss_first_party, blocklist_hit, cooldown_active, etc.). The verdict_source names which surface produced the row.

You revoked + re-minted a key; want to confirm the old one is no longer producing traffic:

Terminal window
curl -fsS \
-H "x-upwarden-admin-token: $ADMIN_API_TOKEN" \
"https://vg-admin.<your-domain>/api/v1/admin/orgs/<slug>/audit?actor=<oldKeyId>&since=2026-06-03T00:00:00Z&limit=200" \
| jq '.entries[] | {created_at, packageName, decision}'

Empty result = the key is fully decommissioned. Anything other than empty = someone still has the old key.

All blocks for a specific package across all tenants (operator-side)

Section titled “All blocks for a specific package across all tenants (operator-side)”

When a CVE drops and you want to confirm every tenant is being protected, that cross-tenant view is a platform-operator query run directly against the audit store — there is no per-tenant admin-API endpoint that spans tenants. On the managed service that’s Upwarden staff; in a self-hosted deployment it’s your own platform team. Individual tenant operators query their own tenant with the filters above.

decisionReason is a JSON envelope encoded into a varchar(256). The shape varies by event-string. Common ones:

event-stringCarries
oss_first_party{ "registry": "npm" } etc. — install allowed because it’s a known-safe registry-first-party.
blocklist_hit{ "rule_id": "...", "source": "osv" | "operator" } — install blocked by a blocklist rule.
cooldown_active{ "published_at": "...", "min_age_days": 14 } — install blocked by the cooldown window.
oauth_login_succeeded{ "member_id": "...", "is_new_member": true | false, "provider": "google" | "github" | "microsoft" } — dashboard sign-in via OAuth.
passkey_authentication_succeeded{ "member_id": "...", "credential_id": "...", "counter_before": N, "counter_after": N+1 } — passkey login.

A full reference of every envelope shape lives in the Audit envelope reference (auto-generated from the encoder helpers in the Upwarden repo).

Most audit rows carry actor_tenant_id = NULL — the action was driven by a member of your own tenant, by an API key bound to your tenant, or by the admin-token surface on your own install.

A non-NULL actor_tenant_id means an Upwarden-platform principal performed a cross-tenant operation against your tenant — for example, an Upwarden support engineer adjusting a setting on your behalf via the platform-admin surface. The value is the Upwarden-platform tenant id; your own tenant stays on the tenant_id column.

This separation lets your SIEM cleanly distinguish “our own operators changed something” from “Upwarden changed something on our behalf” without ambiguity. The two cases get different review workflows in most compliance programmes.

The full platform-admin model — when Upwarden engineers can act cross-tenant, what gates that capability, and how the operator-side audit surface looks — is internal Vanguard runbook territory, covered in docs/admin-guide.md in the source repo.

For SIEM ingestion or compliance evidence, the export endpoint at /admin/ui/orgs/<slug>/audit/export?format=csv|json produces a flat dump with the same filters. The export itself is audited — operators can prove who pulled which window.