--- id: RISK-F-0003 type: finding title: "ops-warden agent read-boundary does not fire on ungraded catalog lanes" status: fixed owner: risk-nexus reported_by: ops-warden reported_via: ops-warden routed_by: ops-warden date_reported: "2026-08-19" system: ops-warden environment: production fix_owner: ops-warden fix_tracking: WARDEN-WP-0032-T05 / T06 (done 2026-08-22) # Graded by risk-nexus 2026-08-19 — docs/rulings/2026-08-19-first-grading.md severity: medium severity_at_production: medium severity_superseded: "high (2026-08-19) — the CLI-layer gap is mitigated" impact: I4 likelihood: L2 fidelity_modifier: false production_rescore: false disclosure: public publication: pending-handover publication_id: risk-f-0003-ops-warden-agent-read-boundary-blind-spot publication_path: "findings/ops-warden-agent-read-boundary-blind-spot/v1/index.html" publication_subtitle: "An omitted catalog grade silently bypassed the agent credential boundary; omission now fails safe and CI rejects it." revision: "fixed-1" last_reviewed: "2026-09-01" review_interval: 6m embargo_lifted: "2026-08-22 — catalog omission fails safe, CI requires explicit grades, and RISK-F-0009's direct-OpenBao layer is fixed" embargo_was_since: "2026-08-19" escalation: none date_fixed: "2026-08-22" last_checked: "2026-09-01T00:32:44Z" next_check: "2026-09-01T00:32:44Z" cadence: instant clean_streak: 0 graded_by: risk-nexus ruling: RISK-RULING-2026-08-19 checked_by: "codex/risk-nexus" --- # RISK-F-0003 — the agent read-boundary has a fourteen-lane blind spot > **Reporter update 2026-08-19 — exposure closed, structural fix outstanding.** > All 14 ungraded lanes now carry an explicit `risk` grade (17 high, 10 > standard, 0 ungraded) and the read-boundary fires on them; verified exit 7 > with `WARDEN_AGENT_ID` set. The rule is recorded as ops-warden `ADR-0007`. > **Still open:** nothing yet prevents a *new* lane being added ungraded > (`WARDEN-WP-0032-T06`), and it remains unverified whether OpenBao's > `agent-high-risk-boundary` policy covers these paths. `status` moved to > `mitigated` by the reporter; severity remains risk-nexus's to set. ## What is true `ADR-0004` (ops-warden) states that high-risk lanes refuse raw value streaming to agent sessions. It is implemented in `src/warden/cli.py:1245`: ```python if raw_value_stream and entry.is_high_risk and agent_id: ``` and `is_high_risk` is defined in `src/warden/routing/models.py:123` as: ```python return self.risk == "high" ``` `risk` is an **optional** field on a catalog entry. Of the 27 entries in `registry/routing/catalog.yaml`, **14 carry no `risk` value at all**. For those, `is_high_risk` is `False`, so the boundary never fires regardless of what the lane actually vends. Five of the fourteen are `exec_capable: true`, meaning `warden access --fetch` can proxy a real value for them: | Lane | Owner | Status | | --- | --- | --- | | `openbao-api-key` | railiance-platform | active | | `whynot-design-npm-publish` | railiance-platform | active | | `key-cape-oidc-login` | key-cape | active | | `issue-core-ingestion-api-key` | railiance-platform | active | | `reuse-surface-hub-write-token` | railiance-platform | active | For these five, an agent session with `WARDEN_AGENT_ID` set can stream a secret value to stdout, which is the disclosure pattern of 2026-07-16 that WP-0026 and `ADR-0004` were written to prevent. The control is not bypassed; it is simply never reached. ## Why it matters beyond these five The failure mode is **omission, not error**. Adding a lane without a `risk` value produces a lane outside the boundary, silently, with no warning at load and nothing in CI that notices. Every future lane inherits this default. The nine other ungraded lanes are not `exec_capable` today, but `exec_capable` is itself an editable field. `ADR-0004` reads as a categorical rule. The implementation is an opt-in list. ## Exposure — stated only as far as ops-warden can support it - ops-warden is the affected system and owns the catalog, the ADR, and the code. - `WARDEN_AGENT_ID` is set by agent harnesses; ops-warden has not enumerated which sessions currently set it, so the population actually exposed is **not established** and should not be assumed to be zero or large. - There is a second, independent layer for OpenBao-backed lanes: the OpenBao policy `agent-high-risk-boundary` denies data-read for agent tokens on the paths it covers. Whether it covers these five paths **has not been verified by ops-warden** and should be checked against the live policy rather than inferred — the paths were graded by the same omission this finding is about. - No evidence of an actual disclosure through these lanes has been sought or found. This is a control gap, not a known incident. ## How it was found Doing `ZONE-WP-0001-T02` (zone-engine) — partitioning the estate for the security-zone model. Grading every lane on `risk` was a prerequisite for deriving zone membership, and the count of ungraded lanes is what surfaced it. ## Suggested direction, not a prescription Two candidate fixes, both ops-warden's to choose between: 1. **Invert the default.** Treat absent `risk` as high, so a new lane is protected until deliberately graded down. Fails safe; may be noisy. 2. **Make `risk` required**, enforced at catalog load and in CI, so an ungraded lane cannot be committed. Fails loud; requires grading 14 lanes now. Either way the grading is real work with real judgement in it, and the five `exec_capable` lanes are the ones that matter first. **Update 2026-08-19 — a third direction, and the one being taken.** The operator has directed that the default derive from **maturity context**: in an early or experimental context an absent grade is tolerable and explicitly accepted; in a production context an absent grade resolves to `high`, or `critical` where the context is critical. This validates against the data here — all five exposed lanes are owned by production-serving components, so the rule would have caught every one. The durable form of that is a zone-model question (`ZONE-WP-0001-T03`), because the `M0`-`M3` maturity ladder that would drive it has no join to catalog lanes today. That work is months out, so the two are being run separately: the five lanes are graded explicitly now under `WARDEN-WP-0032-T05`, and the structural fix that makes absence impossible follows under `WARDEN-WP-0032-T06`. Note for whoever sets severity: `.repo-classification.yaml` `category` is **not** usable as the maturity signal. `railiance-platform`, which runs production OpenBao and owns three of the five lanes above, is `category: tooling`. ## Related - ops-warden `ADR-0004` — high-risk lanes refuse raw value streaming - ops-warden `WP-0026` — disclosure hygiene, safe fetch transports - `wiki/playbooks/agent-read-boundary.md` - `RISK-F-0002` — ops-warden sign ungated (same system, different control) - `zone-engine` `ZONE-WP-0001-T02` — the work that surfaced this ## Register ruling — 2026-08-19 `high` (`I4` × `L2`), embargoed until the five `exec_capable` lanes are graded under `WARDEN-WP-0032-T05`, no escalation. **Graded on its worst lane.** Four of the five are `I3`-shaped — a publish token, an OIDC login, an ingestion key, a hub write token, each bad inside its own system. `openbao-api-key` is not: it reaches the store the estate's other secrets live in, and a disclosure there propagates past every boundary the others respect. **Likelihood `L2`** — an agent session with `WARDEN_AGENT_ID` is a foothold the estate does grant. The register holds the line this finding drew: the exposed population is not established, so it is neither assumed zero nor assumed large, and the unverified OpenBao `agent-high-risk-boundary` coverage is a review question rather than an assumption in either direction. Confirming that coverage for these five paths would lower the grade. **No fidelity modifier, and it was close.** `ADR-0004` reads categorical while the implementation is opt-in, so the documentation asserts a boundary that does not exist for 14 lanes. The modifier is scoped to attestations a system produces at runtime, and documentation drift is not that. But if any compliance evidence anywhere cites `ADR-0004` as implemented, that *is* a false attestation and a separate finding. This register has not looked, and says so rather than assuming. **No escalation.** Known, owned, tracked (`WARDEN-WP-0032-T05` for the five lanes, `WARDEN-WP-0032-T06` for the structural fix), and moving. Severity alone deliberately does not reach the operator. **On the maturity-context direction**, which is the operator's and not re-decided here: the note that `.repo-classification.yaml` `category` cannot carry the signal is confirmed from this repo's own registration work today. `railiance-platform` runs production OpenBao and owns three of the five lanes while classified `tooling`; `risk-nexus` classified itself with the same vocabulary an hour ago. The maturity ladder needs its own field, and that is `ZONE-WP-0001-T03`'s to build. Reasoning: `docs/rulings/2026-08-19-first-grading.md`. ## Reviews - **2026-08-19** — graded. Next review 2026-09-18 (`high` → 30 days). Open at review: OpenBao policy coverage of the five paths; whether `WARDEN-WP-0032-T05` has landed; whether any compliance evidence cites `ADR-0004` as implemented. ## Check — 2026-08-20: mitigated on one layer, `high` → `medium` `ops-warden` reports all 17 high-risk lanes now exiting 7 on `warden access --fetch` with `WARDEN_AGENT_ID` set, verified under `WARDEN-WP-0032-T05`/`T06`. The 14 ungraded lanes are graded; the boundary fires. The live gap this finding described is closed on the CLI layer. **Not clean, so the cadence stays at `instant`.** Something moved, and under the ladder that resets the clock rather than earning a longer one. The next check is the one that can start climbing. **`high` → `medium`.** What remains is the omission shape rather than the instance: whether a future lane can still be added without a grade. That is `WARDEN-WP-0032-T06`, and the durable form of the operator's maturity-context rule is `zone-engine`'s (`RISK-N-0004`). Not `low`, because "graded by hand once" is not the same as "cannot be ungraded again". **The embargo condition changed rather than being met.** The original condition — the five `exec_capable` lanes graded — is satisfied. But `RISK-F-0009` shows the *second* layer, the OpenBao deny set that protects the direct `bao kv get` path, covers 6 of 17 high-risk lanes. Publishing this finding now would name lanes that are still reachable by the path this control exists to close. The condition is therefore re-pointed at `RISK-F-0009`. That is the disclosure rule working across two findings rather than one: what matters is whether the text shortens a path, not whether this particular finding's own fix has landed. **Verification limit, recorded rather than assumed:** the mitigation is `ops-warden`'s report and this register has not probed it. Their operator token is expired, which is also why `RISK-F-0009` rests on a file comparison. - **2026-08-20** — clean check: checked against the inbox and the owner's record; nothing moved. Cadence instant → 1h (1 clean in a row); next check 2026-08-20 11:02Z. - **2026-08-21** — clean check: fix state read from the owner's file: WARDEN-WP-0032-T05 done; nothing else moved. Cadence 1h → 8h (2 clean in a row); next check 2026-08-21 14:32Z. ## Closure — 2026-09-01: omission is no longer permissive The remaining structural task is done. `RouteEntry` now resolves an absent or unknown grade fail-safe, `is_graded` exposes the distinction, and CI rejects a catalog lane without an explicit grade. The current focused Warden regression set passes (**137 tests**), including the omission and OpenBao coverage checks. The dependent direct-OpenBao gap is also fixed under `RISK-F-0009`. There is no remaining live or structural part of this finding, so it moves from `mitigated` to `fixed` and its embargo lifts. - **2026-09-01** — not clean: WARDEN-WP-0032-T06 and the dependent OpenBao coverage are complete; status fixed and embargo lifted. Cadence 8h → instant; checked again immediately.