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_sessionandget_many_sessionseach answerforbiddenwhen the caller does not holdiam::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
- Add the read guard to the three read verbs, mirroring the shape the write and
delete verbs use, so a refusal is the same
forbiddenerror on every verb of the resource. - Build
ores.iamand its suites on the preset, and run the tests that cover the handler. - 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:deleteis still required by the delete verbs and still granted by no role, which the inventory records as its own gap: only a holder of theiam::*wildcard can delete a session row. This task does not change that.
7. Test Scenarios
| Scenario | State | Notes |
|---|---|---|
8. PRs
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.