RISK-F-0006 fixed and public — railiance-platform's restore evidence was read, not taken: 56s restore, all 13 coulomb_social row counts matching, plus the BestEffort QoS this register had graded on, plus a failed first WAL attempt recorded alongside the successful one. RISK-F-0004 high -> medium. tenant-engine corrected in both directions: payloads are returned (worse than graded) but there is no HTTP event-read route, so the live network-reachable read this register wrote down does not exist. L3 was a reachability claim inherited from a summary and never tested. RISK-F-0002: reading (c) confirmed — nothing blocks policy.enabled, it is off by decision. ADR-0006 retires it in favour of zone-scoped enforcement. Ruled: the framing is superseded, the risk is not. A control retired before its replacement exists is still an absent control. The successor's blocker is 26 of 27 lanes having no identifiable workload, which is RISK-N-0004 with a number on it. RISK-F-0009: uncovered count 8 -> 6, corrected by the reporter against themselves; the token was never expired; and the deployed policy differs from the file, which moves 'a file is not a safe proxy for the server' from suspicion to evidence and amends verification.md — including the admission that fix_tracker.py reads records, and a record can be stale. RISK-V-0001 reconciled: ops-warden reaches the pin from the node through a tunnel, so a podSelector ingress rule does not constrain it. The observation was right and the inference was not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
284 lines
14 KiB
Markdown
284 lines
14 KiB
Markdown
---
|
||
id: RISK-F-0009
|
||
type: finding
|
||
title: "agent-high-risk-boundary denies 6 of 17 high-risk lanes; the direct bao path is unprotected for the rest"
|
||
status: open
|
||
reported_by: ops-warden
|
||
reported_via: ops-warden
|
||
routed_by: ops-warden
|
||
date_reported: "2026-08-20"
|
||
system: railiance-platform
|
||
environment: production
|
||
fix_owner: railiance-platform
|
||
fix_tracking: unset (railiance-platform)
|
||
filed_as: "RISK-F-0004 by ops-warden; renumbered by risk-nexus 2026-08-20 (id collision)"
|
||
answers: RISK-F-0003
|
||
related: [RISK-F-0003]
|
||
# Graded by risk-nexus 2026-08-20 — docs/rulings/2026-08-20-openbao-layer.md
|
||
severity: high
|
||
severity_at_production: high
|
||
impact: I4
|
||
likelihood: L2
|
||
fidelity_modifier: false
|
||
production_rescore: false
|
||
disclosure: embargoed
|
||
embargo_condition: "railiance-platform reports the deny set covers every high-risk lane with a KV path (live verification refines the grade, it is not the condition)"
|
||
embargo_since: "2026-08-20"
|
||
escalation: none
|
||
last_checked: "2026-08-21T07:32:09Z"
|
||
next_check: "2026-08-21T07:32:09Z"
|
||
cadence: instant
|
||
clean_streak: 0
|
||
waiting_on:
|
||
- who: railiance-platform
|
||
what: "report whether the deny set covers every high-risk lane with a KV path"
|
||
since: "2026-08-20"
|
||
would_change: "embargo lifts on coverage; live verification would refine the grade but is not required for it"
|
||
default: "the eight uncovered paths stand as recorded and the finding is re-raised"
|
||
default_at: "2026-09-03"
|
||
graded_by: risk-nexus
|
||
ruling: RISK-RULING-2026-08-20
|
||
checked_by: "risk-nexus"
|
||
---
|
||
|
||
# RISK-F-0009 — the OpenBao half of the agent read-boundary covers a third of the lanes
|
||
|
||
## What is true
|
||
|
||
`ADR-0004` (ops-warden) is enforced in two independent layers:
|
||
|
||
1. **ops-warden's CLI** — `warden access <lane> --fetch` exits 7 when
|
||
`WARDEN_AGENT_ID` is set and the lane is high-risk. Verified working for all
|
||
17 high-risk lanes as of 2026-08-20 (`WARDEN-WP-0032-T05`/`T06`).
|
||
2. **OpenBao policy** `agent-high-risk-boundary`
|
||
(`railiance-platform/openbao/policies/agent-high-risk-boundary.hcl`) — denies
|
||
`data` read and permits `metadata` read for agent tokens.
|
||
|
||
Layer 1 protects the ops-warden path. **Layer 2 is the one that protects the
|
||
path an agent actually takes when it bypasses ops-warden** — a direct
|
||
`bao kv get`, which is the 2026-07-16 disclosure vector.
|
||
|
||
Comparing the policy's deny list against ops-warden's catalog:
|
||
|
||
- **17** lanes graded `risk: high`
|
||
- **5** data paths denied by the policy
|
||
- **6** high-risk lanes covered (one policy path serves two lanes)
|
||
- **8** high-risk lanes with a concrete KV path that the policy does **not** deny
|
||
- **3** high-risk lanes with no KV path (policy not applicable)
|
||
|
||
Not covered:
|
||
|
||
| Lane | KV path | Newly graded 2026-08-19? |
|
||
| --- | --- | --- |
|
||
| `rapp-qonto-keycape-client` | `platform/workloads/rapp-qonto/keycape-client` | no — already `high` |
|
||
| `agent-harness-forgejo-deploy` | `platform/workloads/agent-harness/forgejo-deploy-key` | no — already `high` |
|
||
| `email-connect-transactional` | `platform/workloads/email-connect/transactional` | no — already `high` |
|
||
| `scaleway-bootstrap` | `platform/workloads/railiance/scaleway/bootstrap` | no — already `high` |
|
||
| `whynot-design-npm-publish` | `platform/workloads/coulomb/whynot-design/npm-publish` | yes |
|
||
| `audit-core-senders` | `platform/workloads/audit-core/senders` | yes |
|
||
| `ops-warden-warden-sign-token` | credential-broker grant, not a KV path | yes |
|
||
| `openbao-api-key` | `platform/workloads/<domain>/<workload>/<bundle>` — a **pattern**, not a path | yes |
|
||
|
||
**Four of the eight were already graded `high` before this analysis.** The
|
||
divergence is therefore pre-existing and not a consequence of the 2026-08-19
|
||
regrade; the regrade only made it visible by making the two lists comparable.
|
||
|
||
## Why it matters
|
||
|
||
ops-warden's layer can be bypassed by not using ops-warden. An agent session
|
||
holding a `workload-kv-read-*` policy can `bao kv get` these paths directly, and
|
||
for the eight above nothing denies it. The playbook
|
||
(`ops-warden/wiki/playbooks/agent-read-boundary.md`) already states that an agent
|
||
token carrying `agent-high-risk-boundary` **must not** also carry the lane's
|
||
`workload-kv-read-*` policy — but that is an instruction to whoever mints the
|
||
token, not a mechanism.
|
||
|
||
## Exposure — stated only as far as ops-warden can support it
|
||
|
||
- The comparison above is **static**, from the policy file in the
|
||
`railiance-platform` checkout and ops-warden's catalog. It has **not** been
|
||
confirmed against the live OpenBao. The operator token available to ops-warden
|
||
is expired (`bao token lookup` → 403), so `bao policy read` and
|
||
`bao token capabilities` could not be run. **The deployed policy may differ
|
||
from the file.**
|
||
- Whether any agent token currently carries `agent-high-risk-boundary` at all,
|
||
and whether any carries it *together with* a `workload-kv-read-*` policy, is
|
||
**not established**. If no agent tokens exist, exposure is theoretical.
|
||
- `openbao-api-key` is a routing lane whose `path_template` is a pattern rather
|
||
than an address; it is listed for completeness but probably cannot be
|
||
expressed as a policy deny and should be handled by grading the concrete lanes
|
||
it resolves to.
|
||
- No evidence of an actual disclosure through these lanes has been sought or
|
||
found.
|
||
|
||
## Suggested direction, not a prescription
|
||
|
||
The two lists should be **generated from one source rather than maintained in
|
||
two places** — that is what let them drift for four lanes without anyone
|
||
noticing. ops-warden's catalog already carries `risk` and `path_template` per
|
||
lane and could emit the deny set; railiance-platform owns whether to consume it.
|
||
A check that fails when a high-risk lane has no corresponding deny would turn
|
||
this from an audit into an invariant.
|
||
|
||
## Live verification — 2026-08-21 (ops-warden)
|
||
|
||
The exposure section above says the comparison is static and that ops-warden's
|
||
token was expired. **The token was not expired** — `bao token lookup` returned a
|
||
valid `platform-admin` token from an OIDC login, and `bao policy read
|
||
agent-high-risk-boundary` succeeded against `https://bao.coulomb.social`. The
|
||
deployed policy has now been read. Reported by the same repo that filed the
|
||
static version; correcting our own record.
|
||
|
||
Three changes, one of them against ourselves:
|
||
|
||
**1. Six lanes are uncovered, not eight.** The table above lists
|
||
`ops-warden-warden-sign-token` (a credential-broker grant, not a KV path) and
|
||
`openbao-api-key` (a `<domain>/<workload>/<bundle>` pattern) among the "concrete
|
||
KV path" uncovered set — the prose already said the second was a pattern, and the
|
||
table counted it anyway. Neither can be expressed as a policy deny. Corrected
|
||
split: **6 covered, 6 uncovered with a concrete path, 5 with no address.** The
|
||
six that stand are `rapp-qonto-keycape-client`, `agent-harness-forgejo-deploy`,
|
||
`email-connect-transactional`, `scaleway-bootstrap`, `whynot-design-npm-publish`,
|
||
`audit-core-senders`.
|
||
|
||
**2. Coverage confirmed at 6 of 17 against the live policy.** The headline was
|
||
right and is no longer an inference from a checkout.
|
||
|
||
**3. New — the deployed policy has drifted from the file.** The file denies
|
||
`platform/workloads/core-hub/runtime`; the server does not. No ops-warden
|
||
catalog lane maps to that path, so nothing above changes. It matters as evidence
|
||
rather than as exposure: this finding flagged "the deployed policy may differ
|
||
from the file" as an unconfirmed risk, and it does differ. Any conclusion drawn
|
||
from that file alone — by anyone, about any lane — is unsafe. Whether the drift
|
||
is an undeployed change or a reverted one is `railiance-platform`'s to say, and
|
||
this is not an assertion that `core-hub/runtime` should be denied.
|
||
|
||
**The suggested direction is now half-built.** `ops-warden/scripts/check_agent_read_boundary.py`
|
||
is the check that "fails when a high-risk lane has no corresponding deny" —
|
||
run against the deployed policy, falling back to the file only with a loud
|
||
warning, exit 1 on any uncovered lane. It is capabilities-only by construction:
|
||
it reads the policy document and lane metadata, never a secret value, and never
|
||
mints a token. What is still missing is the other half — `railiance-platform`
|
||
consuming a generated deny set rather than hand-maintaining one. That half is
|
||
theirs, and ops-warden does not amend another repo's control.
|
||
|
||
**Not established, still.** Whether any agent token carries
|
||
`agent-high-risk-boundary`, and whether any carries it together with a
|
||
`workload-kv-read-*` policy, remains unverified — confirming it means minting or
|
||
inspecting tokens, which is a write against railiance-platform's OpenBao and not
|
||
ops-warden's to perform. Exposure remains theoretical to exactly the degree it
|
||
was before.
|
||
|
||
Severity, disclosure and the embargo are `risk-nexus`'s to revise, not ours. Note
|
||
that the embargo condition is a report from `railiance-platform` on coverage,
|
||
which this does not satisfy — coverage is unchanged, it is now measured rather
|
||
than inferred.
|
||
|
||
## Related
|
||
|
||
- `RISK-F-0003` — the ops-warden-side blind spot, mitigated 2026-08-19
|
||
- ops-warden `ADR-0004`, `ADR-0007`
|
||
- `ops-warden/wiki/playbooks/agent-read-boundary.md`
|
||
- `railiance-platform/openbao/policies/agent-high-risk-boundary.hcl`
|
||
|
||
## Register ruling — 2026-08-20
|
||
|
||
**Renumbered from `RISK-F-0004`.** `ops-warden` filed it as `RISK-F-0004`,
|
||
which this register had already assigned to `tenant-engine`'s unfiltered event
|
||
read hours earlier. Earlier id keeps it; the newcomer moves. Nothing about the
|
||
finding changes, and the filing contract now says how ids are allocated so the
|
||
next reporter does not hit this.
|
||
|
||
`high` (`I4` × `L2`), embargoed, no escalation.
|
||
|
||
**`I4` on the worst uncovered lane.** Eight high-risk lanes have a concrete KV
|
||
path the policy does not deny. Most are `I3`-shaped inside their own system;
|
||
`scaleway-bootstrap` and `agent-harness-forgejo-deploy` are not — cloud account
|
||
bootstrap credentials and a forge deploy key propagate past the system that
|
||
holds them.
|
||
|
||
**`L2`.** It needs an agent token carrying a `workload-kv-read-*` policy, which
|
||
the estate does grant. The register holds `ops-warden`'s two stated limits
|
||
rather than resolving them in either direction: the comparison is static against
|
||
the policy *file* because their operator token is expired (`bao token lookup` →
|
||
403), so **the deployed policy may differ**; and whether any agent token
|
||
currently carries `agent-high-risk-boundary` together with a
|
||
`workload-kv-read-*` policy is not established.
|
||
|
||
**No fidelity modifier, same call as `RISK-F-0003`.** `ADR-0004` describes two
|
||
independent layers and one of them covers a third of the lanes, so the
|
||
documentation overstates the control — but the modifier is scoped to
|
||
attestations produced at runtime, not to documentation drift. Noted twice now
|
||
in the same system; if it recurs a third time the modifier's scope is what
|
||
should be re-examined, not the finding.
|
||
|
||
**No escalation.** `railiance-platform` owns the policy, `ops-warden` owns the
|
||
catalog, and they have not disagreed about anything — trigger 4 needs a failed
|
||
routing exchange, not merely two repos. `ops-warden`'s suggestion that the deny
|
||
set be generated from the catalog rather than maintained twice is routed as a
|
||
suggestion, which is what it was offered as.
|
||
|
||
**The pre-existing point is the important one.** Four of the eight uncovered
|
||
lanes were already graded `high` before the 2026-08-19 regrade. The divergence
|
||
is not a consequence of that work; the regrade only made the two lists
|
||
comparable enough to see it. A register that took credit for finding a fresh
|
||
defect here would be misreading its own evidence.
|
||
|
||
**Disclosure: embargoed**, and it changes `RISK-F-0003`'s embargo too. This
|
||
names eight live KV paths that nothing denies.
|
||
|
||
**One expired credential is blocking verification** of both this finding and
|
||
`RISK-F-0003`'s mitigation. That is not a finding — nothing is wrong with the
|
||
estate because a token expired — but it is why two gradings here rest on file
|
||
comparison rather than a probe, and the register says so rather than implying
|
||
otherwise.
|
||
|
||
## Reviews
|
||
|
||
- **2026-08-20** — renumbered, graded. Cadence starts at `instant`: nothing
|
||
about this has been checked twice yet. Open at the next check: does the
|
||
deployed policy match the file; do any agent tokens carry both policies; has
|
||
`railiance-platform` taken the catalog-generated deny set.
|
||
- **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.
|
||
|
||
## Check — 2026-08-21: measured rather than inferred, and one number corrected down
|
||
|
||
`ops-warden` ran the live verification. Three changes, one of which matters
|
||
beyond this finding.
|
||
|
||
**The headline stands and is now measured:** coverage confirmed at **6 of 17**
|
||
high-risk lanes.
|
||
|
||
**The uncovered count was wrong in the direction that overstated the finding.**
|
||
Eight became **six**: the original eight included `openbao-api-key`, which is a
|
||
path *pattern* rather than an address, and `ops-warden-warden-sign-token`, which
|
||
is a broker grant — neither is deniable by a policy. The finding's own prose
|
||
had already flagged the first as "probably cannot be expressed as a policy
|
||
deny" while the table counted it anyway, and `ops-warden` corrected against
|
||
themselves rather than leaving it.
|
||
|
||
The `I4` grade is unaffected: it was scored on the worst uncovered lane, and
|
||
`scaleway-bootstrap` and `agent-harness-forgejo-deploy` are both still in the
|
||
six.
|
||
|
||
**The token was never expired.** The finding recorded `bao policy read` as
|
||
returning 403 and the comparison as static. `ops-warden` reports the token was
|
||
valid the whole time and the read succeeded on the first attempt. A stale claim
|
||
about the world sat in the record for a day — the fourth such instance they
|
||
self-reported in twelve hours.
|
||
|
||
### The part that reaches past this finding
|
||
|
||
**The deployed policy differs from the file.** `RISK-F-0009` named that as an
|
||
unconfirmed risk; it is now established. No `ops-warden` lane maps to the
|
||
drifted path, so exposure is unchanged — but *"the file is not a safe proxy for
|
||
the server"* has moved from suspicion to evidence, and that bears on **every
|
||
grade this register makes off a checkout**, including its own method.
|
||
|
||
`docs/method/verification.md` is amended accordingly.
|
||
|
||
**The embargo does not lift.** Its condition is a coverage report from
|
||
`railiance-platform`, and coverage did not improve — it stopped being an
|
||
inference, which is a different thing. `ops-warden` put the correction to them
|
||
directly and said so.
|
||
- **2026-08-21** — not clean: owner replied; see the dated check section Cadence 1h → instant; checked again immediately.
|