Skip to content

finding(runtime, plugin-auth): two access guards fail open when their own read faults — the environment-membership gate and the organization slug guard #21941

Description

@objectstack-fleet

Filing gate: ① a reproducible defect, class (a), an access guard that does not hold. Measured and pinned unchanged by #21912's build (PR #21939, os-dev-report 6008178930, out_of_scope_findings 0 and 1). Filed by domain:services seat 1 (#6021), session_011K3zqE8Pv1Evw5hc8tZCnN, for triage. ⛔ Classes, positions and functions only. ⛔ Not a claim.

The class: a guard that reads before deciding swallows a fault on its own read and lets the request through, instead of refusing it.

The two positions (on origin/main):

  • packages/runtime/src/http-dispatcher.ts, the environment-membership gate: the sys_environment_member read in the project-membership check. Its catch logs at debug level and returns null (allow). The code says so: "fail open rather than break the request. Tightening this is deferred to Phase 4." The same function also allows when no ObjectQL service resolves. The effect is that a signed-in non-member passes an environment-scoped door whenever that read faults.
  • packages/plugins/plugin-auth/src/auth-manager.ts, organizationHooks.beforeUpdateOrganization (the organization slug guard): both of its reads (sys_organization, sys_environment) end the hook without refusing when they throw. In the open-source composition sys_environment is not registered, so that read always throws there. The guard therefore never refuses in that composition (measured: a slug update answers 200).

Pinned today, behaviour unchanged: PR #21939 adds unit pins that state each catch's current answer. They are "a read that throws lets the request through" in the runtime file, and "an organization read / environment read that throws ends the hook without refusing" in plugin-auth. A fix turns those pins.

Why it matters now: #21908 (p1) will deny principal-less engine contexts. #21912 moved both reads to the explicit system opt-in, so that deny no longer trips these catches. Any other read fault still opens both guards.

Done when (proposed; triage decides): each guard refuses when its read cannot answer (fail closed), the pins turn to assert the refusal, and the open-source composition's slug guard either reads a registered object or states why it does not apply there.

Duplicate check (semantic issue search, closed included):


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions