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.
Summary
CharactersController::requirementReport()carries this reason, written as an inline// @no-admin-idor-exemptcomment and repeated in its docblock:The first half is false. Measured, not assumed.
Measured
Every schema this app ships, walked at
development@3936113f:And the two that do declare one declare no
readkey at all:Per
ConductionNL/.github#372,PermissionHandler::hasGroupPermission()returnstruefor any schema whoseauthorizationblock is empty or null, and the opt-in that would close it (enforce_default_closed) defaults tofalse— 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 noauthorizationblock oncharacter, RBAC has nothing to enforce.What is and is not exposed
So the controller is not adding exposure; the exposure is at the register-config layer, and the endpoint inherits it. Fixing this in
CharactersControllerwould 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).// @no-admin-idor-exemptcomment 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 ofPermissionHandler, not on an observed200.Suggested fix
Declare a
readauthorization block on thecharacterschema (and review the other 14 read-open schemas), rather than adding a controller-level guard. Tracks withopenregister#1955/#2011.