Task · GCI-0023

State what each declared reader scope actually permits, not only the routes this collector calls

Description

CAPABILITIES.md’s org-realm reader table has a “Verified route” column, and for logs:read it lists /loki/api/v1/labels and /loki/api/v1/label/<name>/values. Read in isolation that reads as the scope’s boundary. It is not. It is the list of routes this collector calls.

Measured 2026-09-11 with a throwaway org-realm policy carrying logs:read and nothing else, on a control organisation, minted and deleted inside one probe: GET {lokiUrl}/loki/api/v1/query_range returned HTTP 200 and a real log line. logs:read is a full Loki read scope.

The scope is retained deliberately. This task records why, it does not narrow anything

Decision, 2026-09-11 by Rob, now a standing decision in doc-0002:

So the deliverable is candour about a declared capability, not a disclosure of a problem. Write it as a capability the product has and a reason it has it. Do not write it as a risk being mitigated, and do not hedge it - a reader who cannot tell whether the breadth is intentional will assume it is not.

Why it is a documentation defect and not a code defect

docs/security.md is accurate as written: the credential is read-only by scope and the HTTP client rejects every method except GET. Neither sentence claims log content is unreachable, and neither is wrong.

The gap is that query_range is itself a GET, so the method restriction does not bound it. The property that keeps this collector away from log content today is narrower and should be stated as what it is: the code only ever calls label endpoints. That is a property of the implementation, enforced by review and by the source list, not by the credential.

This matters because AGENTS.md makes the read-only posture load-bearing in what this platform can tell an organisation about what it runs. An org security review that reads the capability table and concludes “this credential cannot read our logs” has been misled by a table that was only ever claiming something else - and the correct answer, “it can, here is why it is granted, here is what the code actually calls, and you consented to it”, is a better answer than the one the table implies.

What to change

Deliberately NOT in scope

Removing or narrowing any scope, and adding any log-content read to the collector. This task changes what is documented, not what is granted and not what the code calls. A future analytics feature that reads log content is its own task with its own review; nothing here authorises it.

Establishing the real breadth of a scope requires minting a token, which is a write. logs:read is settled. For any scope where that probe has not been run, say so explicitly and name the probe rather than asserting a boundary in either direction - an unverified claim of narrowness is the exact defect this task exists to remove.

Acceptance Criteria

Definition of Done

Implementation Plan

Audit declared reader scopes in CAPABILITIES.md, state verified breadth and explicit unverified boundaries without narrowing any scope, update docs/security.md with the consent basis, then validate documentation gates.

Implementation Notes

Tracker reconciliation 2026-09-11: criterion 2 now includes the effective-limit GET added by GCI-0022. The security boundary remains no Loki log-query endpoint; calling the new non-content limits route does not restore the stale label-endpoints-only statement.

Final Summary

CAPABILITIES.md now separates routes this collector calls from the broader permissions of each declared scope. logs:read is documented as full Loki read with the consent and retention rationale; effective limits are included without adding a log-content query; traces and profiles remain explicitly unverified with the required probe named; rules breadth is recorded. No scope was narrowed. Final gate at b6cf849614054894e2bea49d0154d958fc016d7b passed.

View the source file on GitHub