Skip to content

Audit: an API key over every server of its org sees the org's list changes - #37

Open
jbob06 wants to merge 1 commit into
warcon-app:mainfrom
jbob06:audit-org-lists-for-keys
Open

jbob06 wants to merge 1 commit into
warcon-app:mainfrom
jbob06:audit-org-lists-for-keys

Conversation

@jbob06

@jbob06 jbob06 commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

The gap

An org ban or reserve list change is logged on the org, with serverId null. auditVisibility gives an API key ownedOrgIds: [], so visibleWhere never lets a key see a row like that, whatever the key's scope. The result: a key that reads the audit trail of every server in its org cannot see the changes all of those servers enforce.

We hit this with an integration that watches staff actions through GET /api/audit, using a key with audit.read on every server (serverIds null). A staff member's org-wide ban (list.add on the org list) never reached it. A server-list ban of the same player 35 seconds later did. Reading /api/audit with that key returned the rows on either side of the org ban and not the ban itself.

The change

A key with audit.read held to every server of its org (serverIds === null) now also sees that org's list-entry changes: list.add, list.remove, list.update, list.import and list.expire (new ORG_LIST_ROWS in audit-rows.ts).

  • AuditVisibility gets an optional listOrgIds, set only on that key path.
  • visibleWhere adds org_id IN listOrgIds AND server_id IS NULL AND action IN ORG_LIST_ROWS.

The line is narrow on purpose: an org list change takes effect on every server of the org, so anyone who can read every server's audit trail should see it. Everything else logged on the org stays the owners': member grants, roles, keys, webhooks, and list.members (who may edit the lists).

Unchanged

  • A key held to some servers, and a key without audit.read, see nothing new.
  • A key of a suspended org sees nothing new: covered is empty there, and the new branch requires it.
  • Every person's visibility is unchanged. The human path of auditVisibility returns exactly what it did, and access.test.ts's exact-shape assertions still pass.

Test

src/test/access.test.ts, "a key over every server sees its org's list changes, and nothing else logged on the org". It seeds org-level list.add, list.expire, list.members and org.member.grants rows, a server row, and an org list.add in another org, then checks:

  • keyAll sees the org's list.add and list.expire plus the server row, and not the others;
  • keyElsewhere (one other server) sees none of them;
  • keyView (no audit.read) sees none of them;
  • a person's visibility has no listOrgIds.

CI on my fork with the TimescaleDB service from ci.yml: lint, check and bun test all green, 848 pass, 0 fail, including this test. That run was jbob06#2, opened only for CI and now closed.

If you would rather make this an explicit key setting than infer it from "every server", or extend the same rule to people whose audit.read covers every server of an org, I am happy to change it.

🤖 Generated with Claude Code

https://claude.ai/code/session_011wkaN3kHsCQGhxCxeAqERQ

…anges

An org ban or reserve list change is logged on the org with no server
(serverId null), and auditVisibility gives an API key no owned orgs, so no
key sees one, whatever its scope. A key that reads the audit trail of every
server in the org, which is what an integration watching the whole org
holds, missed exactly the changes those servers all enforce: an org-wide ban
reached every server and never reached the key's audit read.

A key with audit.read held to every server of its org (serverIds null) now
also sees that org's list-entry changes: list.add, list.remove,
list.update, list.import and list.expire (ORG_LIST_ROWS). Nothing else
logged on the org: member grants, roles, keys, webhooks and list.members
stay the owners'. A key held to some servers, a key without audit.read, a
key of a suspended org, and every person's visibility are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wkaN3kHsCQGhxCxeAqERQ
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