Skip to content

[finding] IRlsMembershipResolver.resolve receives no object or operation — an app-shaped membership set is computed on every RLS computation, whatever object is being read #16620

Description

@os-sam

Observation from implementing objectstack-ai/ats#13 (PR objectstack-ai/ats#48), cli 17.3.0.

What

RlsMembershipContext carries userId, tenantId, accessible_org_ids, positions, permissions — and nothing about the request itself. plugin-security's stageRlsMembership(context) is called from computeLayeredRlsFilter for every object's RLS computation and memoised per execution context (context.__rlsMembershipStaged), so a resolver runs exactly once per request, but it runs for every request that reaches RLS at all, and it cannot tell which object or operation the request is for.

Why it matters

The contract names this seam for "app-shaped sets: the accounts in a rep's territories, the records a case team can touch" — sets that are usually the result of a query. In ats the applicant set (applicant_candidate_ids, the distinct ats_application.candidate ids across the caller's employer organizations) backs a policy on ats_candidate only, yet the read that produces it runs on every request by an employer-side caller — a read of ats_job pays for a query on ats_application whose result no policy on ats_job consumes. The app can only skip the work by the caller's shape (callers with no accessible_org_ids skip it), never by the request's.

Possible shapes (not a ruling)

  • Add object and operation (or the set of policy keys actually referenced by the applicable policies) to RlsMembershipContext, so a resolver can return {} for a key no applicable policy reads.
  • Or let the resolver declare which keys apply to which objects (keys: [{ key, objects }]) and have stageRlsMembership skip staging when no applicable policy on the object names a declared key.

Either keeps the fail-closed contract intact: a key that is not resolved still makes its policies drop out.

Evidence

Observation, not a defect: the contract behaves as written. Filed unlabelled beyond finding for triage.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions