Skip to content

Security: $expand bypasses RLS/FLS on the referenced object (data leak on lookups) #2850

Description

@os-zhuang

Found by the ADR-0090 D10 adversarial security review. Pre-existing, general (affects all callers, not agent-specific).

What

expandRelatedRecords (packages/objectql/src/engine.ts:~1890-1896) resolves the referenced object by calling the driver directly, not this.find():

const expandOpts = this.buildDriverOptions(execCtx);
const relatedRecords = await driver.find(referenceObject, relatedQuery, expandOpts) ?? [];

It never re-enters executeWithMiddleware, so the referenced object's RLS, FLS, and CRUD gate are NOT applied — only tenantId is threaded (buildDriverOptions), so cross-tenant is still blocked. The middleware's post-pass FLS masking (security-plugin.ts ~1023) masks only the top-level object; field-masker does not recurse into expanded/nested records.

Impact

A caller who can legitimately read a base row can ?expand=<fk> and receive the full referenced record — including rows the referenced object's owner-only RLS would hide and fields FLS would mask — for any FK on a row it can see. Tenant isolation is the only surviving boundary.

Reachable via the REST data API: GET /data/:object/:id?expand=… and POST /data/:object/query forward expand (http-dispatcher.ts:~1953). (The MCP query/get tools do not forward expand, so the pure-agent surface is not exposed — but any API-key/session user is.)

Fix direction

Route $expand resolution through the security middleware for the referenced object (RLS + FLS applied per referenced object), or apply the referenced object's read filter + field mask to the expanded batch before attaching. Preserve the batched $in performance (don't N+1).

Refs

  • packages/objectql/src/engine.ts:~1821-1903 (expandRelatedRecords)
  • packages/runtime/src/http-dispatcher.ts:~1953-1958 (REST expand forwarding)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p1High: required for production / M2

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions