The register had nine waits in four days, one four hops deep: F-0003's embargo waited on F-0009, which waited on railiance-platform, which waited on live OpenBao verification, which waited on a credential nobody has. No single link was wrong, which is why it needed a rule. docs/method/dependencies.md: the register never waits to decide, it decides and revises. Every wait carries who, what, since, what it would change, what happens if nobody answers, and the date that default applies. Depth one — a record never waits on a record that is itself waiting. Defaults are dates and are pessimistic: silence costs the grade the evidence supports rather than buying a softer one, and owners are told the default in advance because a default nobody was warned about is an ambush. Applied: F-0009's embargo now lifts on railiance-platform reporting coverage, with live verification as a refinement rather than a condition, cutting the F-0003 chain from four hops to two. All eight open waits are typed with defaults. make check reports them with age, owner and default date, flags defaults come due, and catches depth-two violations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
184 lines
7.9 KiB
Markdown
184 lines
7.9 KiB
Markdown
---
|
||
id: RISK-F-0007
|
||
type: finding
|
||
title: "No consumer's tenant boundary is verified anywhere"
|
||
status: accepted
|
||
reported_by: net-kingdom
|
||
reported_via: risk-nexus
|
||
routed_by: risk-nexus
|
||
date_reported: "2026-08-19"
|
||
date_filed: "2026-08-19"
|
||
system: estate
|
||
environment: build
|
||
fix_owner: per-consumer, on request
|
||
fix_tracking: unset
|
||
related: [RISK-F-0004, RISK-F-0005]
|
||
# Graded by risk-nexus 2026-08-19 — docs/rulings/2026-08-19-second-grading.md
|
||
severity: high
|
||
severity_at_production: critical
|
||
impact: I3
|
||
likelihood: L3
|
||
fidelity_modifier: false
|
||
production_rescore: true
|
||
disclosure: embargoed
|
||
embargo_condition: "a verification exists for at least one consumer boundary"
|
||
embargo_since: "2026-08-19"
|
||
embargo_review: "2026-09-18"
|
||
escalation: answered
|
||
escalation_trigger: 4
|
||
escalation_status: assigned
|
||
escalation_answered: "2026-08-19"
|
||
escalation_answered_by: the-custodian
|
||
escalation_act: assign
|
||
accepted_by: the-custodian
|
||
accepted_on: "2026-08-19"
|
||
accepted_until: "production transition (hard expiry, not a date)"
|
||
decision: "pragmatic default before production — carried unverified; verification of a named consumer boundary on request"
|
||
last_checked: "2026-08-20T10:02:41Z"
|
||
next_check: "2026-08-20T10:02:41Z"
|
||
cadence: instant
|
||
clean_streak: 0
|
||
waiting_on:
|
||
- who: user-engine
|
||
what: "does anything verify that a caller for tenant A cannot reach tenant B (RISK-V-0002)"
|
||
since: "2026-08-20"
|
||
would_change: "likelihood falls for user-engine if a verification exists; a defect becomes its own finding if not"
|
||
default: "the on-request path is recorded as having produced no answer, which makes the acceptance itself unsupported and is escalated"
|
||
default_at: "2026-09-03"
|
||
graded_by: risk-nexus
|
||
ruling: RISK-RULING-2026-08-19-B
|
||
---
|
||
|
||
# RISK-F-0007 — nothing checks that tenants stay apart
|
||
|
||
## What is true
|
||
|
||
No consumer's tenant boundary is verified anywhere in the estate. This is
|
||
Broken Object Level Authorization, first on the OWASP API Security Top 10
|
||
since that list launched.
|
||
|
||
It is currently **open question 3 in NetKingdom's *Tenancy Posture*, which is a
|
||
draft and not a register** — which is precisely why `INTENT.md` names it as the
|
||
thing that had nowhere to live. Filing it here does not amend that standard,
|
||
does not ratify it, and does not settle the question it poses. A finding may
|
||
say a standard is wrong; it cannot change one.
|
||
|
||
## Why it is a finding and not a research note
|
||
|
||
The floor asks whether an owner could act and whether recording it changes a
|
||
decision. Both are yes, and the second is now demonstrable rather than
|
||
asserted: every time a repo has looked at its own boundary this month, it has
|
||
found a defect. `RISK-F-0004` (`tenant-engine`, unfiltered event read) and
|
||
`RISK-F-0005` (`audit-core`, no tenant filter on read) are two instances found
|
||
by two repos in one round.
|
||
|
||
An absent verification is not the same as an absent boundary. But the sample so
|
||
far is two for two, and the register does not get to assume the unexamined
|
||
cases are better than the examined ones.
|
||
|
||
## What is not established
|
||
|
||
- Whether any consumer boundary *is* in fact correct. Nobody has checked; that
|
||
is the finding.
|
||
- Who owns the fix. `fix_owner` is `unset` deliberately — see the escalation.
|
||
|
||
## Register ruling — 2026-08-19
|
||
|
||
`high` today (`I3` × `L3`), `critical` at production, embargoed, **escalated on
|
||
trigger 4 (ownership)**.
|
||
|
||
Today's impact is `I3` and not `I4` because there is no real tenant data behind
|
||
the unverified boundary yet; the consequence of one occurrence is bounded by
|
||
what the estate is currently holding. `L3`: no additional step is needed by
|
||
anything already inside, and the two confirmed instances say the reach is real
|
||
rather than theoretical.
|
||
|
||
`production_rescore: true`, and this is the finding that matters most on that
|
||
day: at production the same defect is `I4` × `L3` — `critical` — and the
|
||
re-score is not optional.
|
||
|
||
**Escalation, trigger 4.** This is the ownership case the trigger was written
|
||
for, in its purest form: the defect is estate-wide, no repo owns "every
|
||
consumer's boundary", and a routing exchange cannot settle it because there is
|
||
nobody to route it to. Only the operator can say who verifies tenant
|
||
boundaries — a repo, a test suite, a review obligation on each consumer — or
|
||
that the estate deliberately carries it unverified until production.
|
||
|
||
Until that is answered, `fix_owner` stays `unset` rather than being assigned to
|
||
a repo that has not accepted it. An unowned finding with an honest `unset` is
|
||
better than a routed one nobody agreed to.
|
||
|
||
## Reviews
|
||
|
||
- **2026-08-19** — filed and graded. Open at review: has an owner been named;
|
||
have any further instances been found; is the Tenancy Posture question still
|
||
open.
|
||
|
||
## Operator decision — 2026-08-19: pragmatic default, tighter control on request
|
||
|
||
The custodian ruled: **before production, carry it.** Verifying every
|
||
consumer's tenant boundary is not attempted as a programme now; a named
|
||
boundary is verified when someone asks for it.
|
||
|
||
The finding moves to `accepted` — which in this register is not `closed`. It
|
||
keeps its severity, its review interval and its production re-score, and it
|
||
stays in `REGISTER.md` where an accepted risk is supposed to be visible while
|
||
it is being carried.
|
||
|
||
**The acceptance expires at the production transition, and that is an event,
|
||
not a date.** `production_rescore` is `true`: at production the same defect is
|
||
`I4` × `L3` — `critical` — and the acceptance does not survive it. Nobody has
|
||
to remember; `make check` lists it under what is owed at that transition.
|
||
|
||
### The on-request path
|
||
|
||
So that "on request" is a mechanism and not a sentiment:
|
||
|
||
- **Who may ask** — any repo that consumes or is consumed by another, the
|
||
operator, or an outside counterparty through the operator.
|
||
- **What they get** — verification scoped to one named consumer boundary, not
|
||
a general audit. The request names the consumer and what would have to be
|
||
true.
|
||
- **Who does it** — the owning repo of that consumer. This register scopes and
|
||
records; it does not verify, and it does not fix.
|
||
- **What it produces** — a dated verification record here. If the boundary
|
||
holds, that is evidence and this finding's likelihood falls for that
|
||
consumer. If it does not, that is a finding of its own with its own owner,
|
||
filed normally.
|
||
|
||
Requests arrive as a message to `risk-nexus` and appear in the register within
|
||
one review cycle.
|
||
|
||
### What the register keeps saying while this is carried
|
||
|
||
The two-for-two record stands: every repo that has looked at its own boundary
|
||
this month found a defect (`RISK-F-0004`, `RISK-F-0005`). The acceptance does
|
||
not make that less true, and this finding is the place it stays visible. If a
|
||
third instance arrives, the pragmatic default is worth re-taking before
|
||
production rather than at it.
|
||
|
||
## Reviews
|
||
|
||
- **2026-08-19** — escalation answered, accepted until production with
|
||
verification on request. Open at review: any request received; any further
|
||
instances found; whether production is close enough to re-take the default.
|
||
|
||
## Check — 2026-08-20: the on-request path has been walked once
|
||
|
||
`RISK-V-0002` — verification of `user-engine`'s tenant boundary, requested
|
||
through the documented path on 2026-08-20.
|
||
|
||
`user-engine` was chosen because they are a consumer with a boundary who is
|
||
**not** already carrying a finding about one. Using `tenant-engine`
|
||
(`RISK-F-0004`) or `audit-core` (`RISK-F-0005`) would have tested the path
|
||
against systems already known to fail it, which would have proved nothing about
|
||
the path.
|
||
|
||
The acceptance recorded here rests on that path working. Until 2026-08-20 it
|
||
had never been used, which made it a plan rather than a route. All three
|
||
outcomes are informative and the least comfortable one is the most useful:
|
||
if nothing comes back, the estate learns that it is carrying this finding on an
|
||
assumption that asking works.
|
||
|
||
Grade unchanged. Nothing about the boundary itself has moved.
|
||
- **2026-08-20** — not clean: On-request verification walked for the first time: RISK-V-0002 asks user-engine. Cadence instant → instant; checked again immediately.
|