You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Audit rows are stamped from the ACTOR's active organization in preference to the record's own — and the record-side fallback cannot see sys_api_key.active_organization_id #8707
Filed from #8287 (PR: claude/issue-8287-api-key-organization). This is the unimplemented half of that card's follow-through decision 2 — the decision itself was already taken by the maintainer; what this issue records is that its implementation site is not where the card assumed, and has a blast radius that made it wrong to carry as a rider.
The decision being honoured
#8287's ruling required an explicit choice on whether revocation/audit rows need the organization stamped. The decision taken: stamp from the row's own organization — the org the record is about — ⛔ not the actor's active organization. Rationale on that card: sys_api_key declares enable.trackHistory: true, and an audit row stamped with the wrong tenant is unreadable to the tenant admin it concerns under isolated, reproducing the very defect one layer down.
Precedence is the wrong way round for the decision.sess.tenantId ?? recordOrgId prefers the actor's active organization; the record's own organization is only a fallback. So a revocation performed from a session whose active org differs from the key's org stamps the actor's org — exactly the case the decision names. The existing comment explains the fallback was added so audit rows would not be invisible under RLS; the same reasoning argues for the record's org winning whenever it is present.
plugin-audit is a fifth lane, outside the four-lane surface the cross-lane designation (comment 5293093118) enumerated.
The precedence flip affects every audited object, not sys_api_key — that deserves its own measurement, not a rider on an identity card.
Landing only the narrow half (teaching recordOrgId about active_organization_id) would be inert without the precedence change, i.e. declared-but-unenforced — the shape AGENTS.md PD chore: version packages #10 / ADR-0049 forbid.
Scope for whoever takes this
Decide the precedence deliberately: is "the record's organization wins when present" correct for every audited object? It looks strictly more correct — an audit row describes a change to a record, and stamping the actor's org makes it unreadable to the tenant it concerns — but it is a behaviour change across the whole audit surface and should be measured, not assumed.
Repro: revoke another user's API key while your active organization differs from the key's, then read sys_audit_log as a tenant admin of the key's organization.
Filed from #8287 (PR:
claude/issue-8287-api-key-organization). This is the unimplemented half of that card's follow-through decision 2 — the decision itself was already taken by the maintainer; what this issue records is that its implementation site is not where the card assumed, and has a blast radius that made it wrong to carry as a rider.The decision being honoured
#8287's ruling required an explicit choice on whether revocation/audit rows need the organization stamped. The decision taken: stamp from the row's own organization — the org the record is about — ⛔ not the actor's active organization. Rationale on that card:
sys_api_keydeclaresenable.trackHistory: true, and an audit row stamped with the wrong tenant is unreadable to the tenant admin it concerns underisolated, reproducing the very defect one layer down.What the code actually does
packages/plugins/plugin-audit/src/audit-writers.ts:Two separate gaps:
sess.tenantId ?? recordOrgIdprefers the actor's active organization; the record's own organization is only a fallback. So a revocation performed from a session whose active org differs from the key's org stamps the actor's org — exactly the case the decision names. The existing comment explains the fallback was added so audit rows would not be invisible under RLS; the same reasoning argues for the record's org winning whenever it is present.organization_idonly.[finding] API keys carry no organization — under the isolated posture a minted key reads no org data at all (no leak, but the key surface is inert) #8287 givessys_api_keyanactive_organization_idcolumn (named that way deliberately — see the PR — so the credential table does not become org-walled and start hiding keys from their own owners).recordOrgIdtherefore resolves toundefinedon this object and the fallback cannot fire even when precedence is fixed.Why it was not fixed in #8287's PR
plugin-auditis a fifth lane, outside the four-lane surface the cross-lane designation (comment5293093118) enumerated.sys_api_key— that deserves its own measurement, not a rider on an identity card.recordOrgIdaboutactive_organization_id) would be inert without the precedence change, i.e. declared-but-unenforced — the shape AGENTS.md PD chore: version packages #10 / ADR-0049 forbid.Scope for whoever takes this
sys_api_keyis the first object where the answer is notorganization_id; a schema-level answer would be better than a second hard-coded name, and [finding] API keys carry no organization — under the isolated posture a minted key reads no org data at all (no leak, but the key surface is inert) #8287's PR deliberately did not invent one.sys_audit_logas a tenant admin of the key's organization.