Task: Guard the session reads with the permission the catalogue grants

Table of Contents

This page documents a task in the Implement the credentials journeys in ores.web story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

Close the security defect The session read handlers skip the permission check the catalogue grants. iam.v1.sessions.list, .get and .get_many answer any authenticated caller with the tenant's sessions: client, address, country, byte counters and account. The catalogue grants iam::sessions:read ("View active sessions") for exactly this purpose, the account reads check iam::accounts:read and the login-record reads check iam::login_info:read, so the three reads are unguarded by oversight and not by design.

This lands before the Audit sign-ins screen, because that screen is built on these reads.

2. Status

Field Value
State DONE
Parent story Implement the credentials journeys in ores.web
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-10-01

3. Acceptance

  • list_sessions, get_session and get_many_sessions each answer forbidden when the caller does not hold iam::sessions:read, and answer as they do today when it does.
  • The write and delete verbs keep the guards they have.
  • The handler's own comment stops saying the reads check "the permission a write needs".
  • The suites that cover the handler pass, and the whole-tree build is green.
  • The change is recorded on the component's clean-standard registry entry, since the registry is what claims the component has no unguarded read.

4. Plan

  1. Add the read guard to the three read verbs, mirroring the shape the write and delete verbs use, so a refusal is the same forbidden error on every verb of the resource.
  2. Build ores.iam and its suites on the preset, and run the tests that cover the handler.
  3. Raise the pull request, with the live check that a caller without the permission is refused.

5. Measurements

What Value Command Commit
Unguarded session reads before 3 grep -n has_permission projects/ores.iam/core/include/ores.iam.core/messaging/session_handler.hpp 6934fc2cbd
Unguarded session reads after 0 the same command working tree
Guards on the resource 7, one per verb the same command working tree

6. Notes

  • The fix is small and the whole resource now reads the same way: every verb checks the permission its operation needs, and a refusal is forbidden.
  • The clean-standard registry entry for ores.iam recorded this class of defect as closed for accounts and login records; the session reads arrived later with the CRUD protocol and were never checked. The registry claim should be re-read after this lands rather than assumed.
  • iam::sessions:delete is still required by the delete verbs and still granted by no role, which the inventory records as its own gap: only a holder of the iam::* wildcard can delete a session row. This task does not change that.

7. Test Scenarios

Scenario State Notes
     

8. PRs

PR Title
#2492 [agile] Record the remaining credentials build tasks
#2465 [iam] Guard the session reads with the permission the catalogue grants

9. Review

# Comment summary File Decision Notes
1 The edited header is generated, so the next regeneration removes the guards session_handler.hpp Fixed The model owns the flag now: ores.iam.session.org sets :guard_reads: true, and the regeneration emits the same three guards. Verified by regenerating and rebuilding.
2 The comment rewording belongs in the template, not in one generated file cpp_nats_handler.hpp.mustache Declined here, captured The rewording was dropped rather than kept, and the template-wide fix filed as a capture: the sentence is emitted for every verb of every component.
3 Confirm the guard does not refuse a user-facing read of the caller's own session session_handler.hpp Answered iam::sessions:read is held by TenantAdmin through its * permission and by HttpService explicitly; no other role reads sessions, and the BFF's and ores.http's session routes are reached by those two. The browser's own session comes from GET /api/session, which is the BFF's cookie store and not this subject.
4 A test that the model sets guard_reads would close the regression risk the codegen suite Accepted as the next step The codegen suite already covers a model with the flag generating guarded reads; asserting that this model sets it is the cheap case, and it is recorded as the remaining work on this task.

10. Result

Done 2026-10-01, except for the test that asserts the refusal.

The guard is generated, not written. Review of the pull request caught that session_handler.hpp carries AUTO-GENERATED FILE - DO NOT EDIT MANUALLY, so the three guards had first been written by hand into a file the next regeneration would overwrite. The model owns the decision now: projects/ores.iam/modeling/ores.iam.session.org sets :guard_reads: true in its flags, and compass codegen regenerate --component iam --entity session --address ores.cpp.nats-handler emits the same three guards — list_sessions, get_session and get_many_sessions each checking iam::sessions:read before they decode. The account and login-record models set the same flag, so the three entities guard their reads alike and stay guarded through later regenerations.

The comment rewording the review also flagged was dropped rather than kept: that sentence lives in cpp_nats_handler.hpp.mustache and is emitted for every verb of every component, so fixing it in one generated file would revert on the next run. Filed as Every generated NATS handler says its reads check the permission a write needs so it is fixed once, in the template.

Verification: the library and the suite build with the regenerated handler, and ores.iam.core.tests passes whole — All tests passed (425 assertions in 165 test cases). The first run of that suite failed 59 of 165 cases, every one of them with a missing column (account_status, is_registration_default, tenant_type) because the restored database was behind the SQL on main; compass db recreate -y -k rebuilt it and the suite then passed, which is worth knowing for every later task in this story.

What is still missing. No test exercises the refusal, and the reason is larger than this task: projects/ores.iam/core/tests/ mentions no permission string, no has_permission and no forbidden anywhere, so no guard in the component is covered — not the account reads, not the login-record reads, not the write or delete verbs this guard copies. The session handler is therefore not the odd one out; it is the first verb set whose refusal anybody wanted to prove. The codegen suite already covers the mechanism — a model that sets guard_reads generates guarded reads — so the cheap regression guard is a case asserting that this model sets the flag; a case that calls a read without the permission and asserts forbidden needs a harness the component does not have yet. That decision belongs to whoever takes this task next.

Emacs 29.3 (Org mode 9.6.15)