Skip to content

auditMetaItem's organizationId is dead on both ends — the audit read returns every org's rows for a (type, name), while its comment describes a scope filter that is not in the query #8747

Description

@hotlong

What

ObjectStackProtocolImplementation.auditMetaItem (packages/metadata-protocol/src/protocol.ts)
declares organizationId?: string | null in its request type and never reads it. The
query it builds is:

const where: Record[string, unknown] = {
    type: singular,
    name: request.name,
};

Directly above that where, the method's own comment describes filtering that is not
there:

Org-scoped lookup: include rows for the specific org AND env-wide (organization_id IS
NULL) rows so the editor sees both tenant overlays and env-level package writes.

That sentence describes an $or on organization_id. No such clause is built, and
request.organizationId is referenced nowhere in the method body.

It is dead on the caller side too: the only production caller,
GET /api/v1/meta/:type/:name/audit (packages/rest/src/rest-server.ts), passes type,
name, environmentId and limit — never organizationId. So the parameter is
unreachable from the wire as well as unused in the body.

Why it matters

sys_metadata_audit rows carry a real organization_id (the write path is careful about
it — an env-wide draft audits env-wide, a tenant overlay audits to its org). The read door
then discards that dimension: for a given (type, name) it returns every org's rows,
including actor, note and lock_state. Studio's audit-log tab serves from exactly this
method.

Two readings, and I could not settle which applies without going outside this card's scope:

  1. Real disclosure — a tenant admin viewing the audit tab for a metadata item whose
    name also exists in another tenant sees that tenant's audit events.
  2. Compensated lower down — the engine/driver applies tenant scoping to find such
    that the protocol-level omission is invisible in practice, and the defect is then a
    comment that over-claims plus a parameter that should be deleted or wired.

Either way the current state is declared-not-enforced (AGENTS.md Prime Directive #10): the
signature and comment both advertise org scoping that the code does not perform. Which
reading holds decides whether this is a security fix or a cleanup, so it is worth
measuring rather than assuming.

Suggested resolution

Measure reading 1 first (two orgs, same (type, name), one audit read). Then either wire
the $or the comment describes and thread organizationId from the REST route, or delete
the parameter and correct the comment. A pin either way — the comment has been describing
absent behaviour for long enough that the next reader will believe it.

Not in scope of

Found while verifying the reader for #8595 (PR #8746), which needed to know exactly what
auditMetaItem keys on. #8595 only adds rows on the publish pre-flight path and does not
touch this query. Related but distinct: #8707 is about how audit rows are stamped at
write time; this one is about the read discarding the stamp.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions