Skip to content

requirementReport's IDOR exemption reason is false: the character schema declares no authorization block, so the OR-delegated read is default-OPEN #319

Description

@rubenvdlinde

Summary

CharactersController::requirementReport() carries this reason, written as an inline // @no-admin-idor-exempt comment and repeated in its docblock:

Authorization is delegated to OpenRegister via the fetch — a user who cannot read the character via the OR-backed fetch gets a 404, so this endpoint exposes nothing the caller could not already read through the OR objects API.

The first half is false. Measured, not assumed.

Measured

Every schema this app ships, walked at development @ 3936113f:

total schemas: 16   |   with an `authorization` block: 2
CHARACTER schema in lib/Settings/larpingapp_register.json        -> authorization: ABSENT
CHARACTER schema in lib/Settings/register.d/portal-identity.json -> authorization: ABSENT

And the two that do declare one declare no read key at all:

larpingapp_register.json   xpAward     {"create":["gamemasters"],"update":["gamemasters"],"delete":["gamemasters"]}
event-checkin-roster.json  attendance  {"create":["gamemasters"],"update":["gamemasters"],"delete":["gamemasters"]}

Per ConductionNL/.github#372, PermissionHandler::hasGroupPermission() returns true for any schema whose authorization block is empty or null, and the opt-in that would close it (enforce_default_closed) defaults to false — deliberately, so the ~15 leaf apps without authorization blocks are not bricked.

So the delegated read is default-OPEN. ObjectService::find() does run with _rbac: true / _multitenancy: true, which is what makes the delegation claim look sound at the call site — but with no authorization block on character, RBAC has nothing to enforce.

What is and is not exposed

  • "a user who cannot read the character gets a 404"false. No authenticated user is refused.
  • "exposes nothing the caller could not already read through the OR objects API"true, and for the same reason: the OR objects API is equally open on this schema.

So the controller is not adding exposure; the exposure is at the register-config layer, and the endpoint inherits it. Fixing this in CharactersController would be the wrong layer.

Why this is filed rather than exempted

The gate's @no-admin-idor-exempt <reason> mechanism was one edit away from being used here, which would have written a false claim into the source and turned the finding green permanently. .github#372's table is what refuted it — the same measurement, one column narrower, was already on record for this app (larpingapp 23 schemas, 2 with authorization).

⚠️ Related: the inline // @no-admin-idor-exempt comment in the method body does not exempt anything — the checker's regex requires the tag in the docblock (^\s*\*\s*@no-admin-idor-exempt[ \t]+\S). It reads as a suppression that is not one, which is worse than no comment.

Not measured

No live two-arm probe (attacker + owner, status codes printed) was run — that needs a rig with larpingapp and openregister installed together. The claim above rests on the register config and on #372's reading of PermissionHandler, not on an observed 200.

Suggested fix

Declare a read authorization block on the character schema (and review the other 14 read-open schemas), rather than adding a controller-level guard. Tracks with openregister#1955 / #2011.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions