Read the repo inbox after grading, which is the wrong order and is now recorded as such. flex-auth had answered the NetworkPolicy question on 2026-08-18 (narrow ingress, not default-deny — L3 becomes L2, critical becomes high) and reported RISK-F-0001 fixed at 12:35 today with live 401 probes. F-0001 closes fixed and public; its escalation is withdrawn before it was ever sent. RISK-F-0002's ordering constraint lifts with it and its trigger-6 escalation is withdrawn. audit-core had routed the erasure-versus-audit legal question here on 2026-08-18 asking for an owner. RISK-N-0002 was wrong to call it a note: the remedy is not retrofittable, so the decision can only be taken early. Promoted to RISK-F-0008, owned by this repo as regulatory intake, escalated on trigger 2. Accepted rapp-postgres's record format and ops-warden's typed-act escalation vocabulary. Reading the inbox is now question zero of every review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
214 lines
9.9 KiB
Markdown
214 lines
9.9 KiB
Markdown
---
|
||
id: RISK-F-0001
|
||
type: finding
|
||
title: "flex-auth /v1/check authenticates no caller"
|
||
status: fixed
|
||
reported_by: flex-auth
|
||
reported_via: rapp-postgres
|
||
routed_by: rapp-postgres
|
||
date_reported: "2026-08-17"
|
||
system: flex-auth
|
||
environment: production
|
||
fix_owner: flex-auth
|
||
fix_tracking: FLEX-WP-0015 (finished 2026-08-19)
|
||
# Graded by risk-nexus 2026-08-19 — docs/rulings/2026-08-19-first-grading.md
|
||
severity: high
|
||
severity_at_production: high
|
||
severity_superseded: "critical (2026-08-19, graded on L3 before reading the inbox)"
|
||
impact: I4
|
||
likelihood: L2
|
||
fidelity_modifier: false
|
||
production_rescore: false
|
||
disclosure: public
|
||
publication: pending-handover
|
||
embargo_condition: "met 2026-08-19 — FLEX-WP-0015 finished, live probes return 401"
|
||
embargo_since: "2026-08-19"
|
||
embargo_review: "2026-08-19"
|
||
escalation: withdrawn
|
||
escalation_trigger: 1
|
||
escalation_status: withdrawn-before-sending
|
||
date_fixed: "2026-08-19"
|
||
last_reviewed: "2026-08-19"
|
||
review_by: "2026-08-26"
|
||
graded_by: risk-nexus
|
||
ruling: RISK-RULING-2026-08-19
|
||
---
|
||
|
||
# RISK-F-0001 — flex-auth authenticates no caller on the decision surface
|
||
|
||
## What is true
|
||
|
||
`POST /v1/check` and `POST /v1/batch_check` authenticate no caller. Any
|
||
workload with network reach to the ClusterIP Service can assert any subject and
|
||
any tenant and receive an authoritative **allow**.
|
||
|
||
`flex-auth` is the estate's authorization oracle. Every service that delegates
|
||
a decision to it is relying on an answer that anyone able to reach the pod can
|
||
obtain for any identity they care to name.
|
||
|
||
Self-reported by `flex-auth` as `A0` on their own inbound surface, in their
|
||
Tenancy Posture review. Their words: "flex-auth is the estate's authorization
|
||
oracle and it trusts its callers completely."
|
||
|
||
## How it was found
|
||
|
||
Not by a probe, an incident, or an alert. By `flex-auth` assessing themselves
|
||
against the Tenancy Posture A ladder during a review they were asked to do —
|
||
and their own note says they did not know they were carrying it.
|
||
|
||
That provenance matters for triage: nothing was watching for this, and nothing
|
||
would have found it. It has presumably been true for as long as the endpoint
|
||
has existed.
|
||
|
||
## Exposure, as far as the reporter stated it
|
||
|
||
- The Service is `ClusterIP`, so reach requires a workload inside the cluster.
|
||
- No claim was made that network policy restricts which workloads can reach it,
|
||
and this record does not assume one. **If a default-deny NetworkPolicy fronts
|
||
the service, that materially changes the exposure and should be verified
|
||
rather than inferred** — `flex-auth` did not state it either way, and I have
|
||
not checked, because doing so would be reporting on a system I do not own.
|
||
|
||
## What makes it worse than a single service's defect
|
||
|
||
A false allow from this endpoint is not confined to `flex-auth`. It is the
|
||
answer other services act on. `tenant-engine` separately reports that its own
|
||
mutations are authorized by `flex-auth` and that direct authority over its rows
|
||
would mean "privilege escalation across NetKingdom rather than data tampering
|
||
confined to one store". The same reasoning applies to a forged allow.
|
||
|
||
## Owner and state
|
||
|
||
`flex-auth` owns the fix and has tracked it as `FLEX-WP-0015-T02`, to ship
|
||
through the staged-promotion path rather than a direct apply. They classify it
|
||
as the only urgent item of their five follow-ups. Nothing is asked of them by
|
||
this record beyond what they have already committed to.
|
||
|
||
## What this repo is asked to decide
|
||
|
||
1. **Severity.** Not the reporter's to set.
|
||
2. **Disclosure.** Build mode is currently public-by-default, and this is
|
||
precisely the class of finding where that stops being obviously right — a
|
||
live authorization bypass in the service every other service trusts. The
|
||
controlled-disclosure scheme this repo anticipates does not exist yet, so
|
||
the choice today is publish or hold, with no mechanism between them.
|
||
3. **Escalation.** Whether this reaches the operator personally. The candidate
|
||
triggers in INTENT include "anything exposing real tenant data" — this
|
||
exposes the decision that governs access to it, which may or may not be the
|
||
same thing, and that judgement is this repo's.
|
||
|
||
## Related, reported at the same time and not yet filed
|
||
|
||
Three further defects surfaced from the same review round. They are recorded
|
||
here so they are visible, not filed as findings, because filing them was not
|
||
asked for:
|
||
|
||
- `tenant-engine` — `events()` returns the entire event log unfiltered. A live
|
||
cross-tenant read at `E2`.
|
||
- `audit-core` — read path applies no tenant filter; a credential with
|
||
`may_read` can read any tenant's events. Bounded by deployment (`may_read:
|
||
false` on the production sender) and not by code. Tracked `AUDIT-WP-0008-T04`.
|
||
- `apps-pg` (`railiance-platform`) — no backup configured at all: no
|
||
`barmanObjectStore`, no retention policy, `BestEffort` QoS. `R0` there means
|
||
no recovery, not merely no erasure policy.
|
||
|
||
All four were found the same way, by repos reading their own code against a
|
||
ladder, within a day of each other. That is a fact about the estate's
|
||
observability worth carrying into triage: **four live defects, none found by
|
||
monitoring.**
|
||
|
||
## Register ruling — 2026-08-19
|
||
|
||
`critical` (`I4` × `L3`, no fidelity modifier), embargoed until
|
||
`FLEX-WP-0015-T02` ships, **escalated** to the operator on trigger 1.
|
||
|
||
The question this finding put — whether governing access to tenant data counts
|
||
as exposing it — is answered **yes**. An authorization oracle that can be
|
||
forged is not one step removed from the data; it is the step.
|
||
|
||
Impact is `I4` because a forged allow does not stay here: it is the answer
|
||
other services act on, and `tenant-engine` has stated what direct authority
|
||
over its rows would mean. Likelihood is `L3` — the normal working set, inside
|
||
the cluster, no additional step — and the register follows the reporter in
|
||
neither assuming a default-deny NetworkPolicy nor assuming its absence.
|
||
|
||
No fidelity modifier: this endpoint answers honestly about a caller it never
|
||
checked. The false-record hazard lives in `RISK-F-0002`'s constraint, where a
|
||
consumer of this endpoint would begin signing records asserting an
|
||
authorization that was never made. **That constraint binds this finding's
|
||
remediation**: `ops-warden`'s `policy.enabled` must not be turned on until
|
||
`/v1/check` authenticates its callers, and the ordering is
|
||
|
||
```
|
||
flex-auth warn-only -> ops-warden gate presents its SA token -> logs clean
|
||
-> flex-auth fail-closed -> ops-warden policy.enabled: true
|
||
```
|
||
|
||
Nothing further is asked of `flex-auth` beyond what they have committed to,
|
||
except one fact only they can supply: **is there a default-deny NetworkPolicy
|
||
in front of the Service?** It is the single fact that would most change this
|
||
grade, and it is the first question at review.
|
||
|
||
Reasoning: `docs/rulings/2026-08-19-first-grading.md`.
|
||
|
||
## Reviews
|
||
|
||
- **2026-08-19** — graded. Next review 2026-08-26 (`critical` → 7 days).
|
||
Open at review: the NetworkPolicy question; whether `FLEX-WP-0015-T02` has
|
||
moved; whether the embargo still holds.
|
||
|
||
## Re-grade and close — 2026-08-19 (same day)
|
||
|
||
**Corrected from `critical` to `high`, and closed as `fixed`.** Both changes
|
||
come from messages that were already in this repo's inbox when the first grade
|
||
was set. The register graded before it read them.
|
||
|
||
**The exposure was narrower than graded.** `flex-auth` answered the
|
||
NetworkPolicy question on 2026-08-18: both production Deployments ship a
|
||
NetworkPolicy in the same manifest, and it is *narrower* than default-deny —
|
||
ingress restricted to one `namespaceSelector` plus one `podSelector` on port
|
||
8080, egress empty. In force since before the period the `A0` describes. So the
|
||
reachable set was never "any pod in the cluster"; it was the single paired
|
||
workload per Deployment.
|
||
|
||
That is `L2`, not `L3`. Impact stays `I4` — what a forged allow reaches does
|
||
not change — so the grade is `high`. `flex-auth` also corrected their own
|
||
earlier phrasing to `ops-warden` in the same message, unprompted, and that
|
||
correction is why the fact reached this register at all.
|
||
|
||
Three caveats `flex-auth` asked to be recorded rather than taken from them, and
|
||
they are why the grade did not fall further: label selectors are network
|
||
position, not identity; the policy could not bind the asserted `resource.system`,
|
||
which is what made cross-system impersonation possible; and enforcement depends
|
||
on a CNI they could not verify from a cluster where `kubectl` returned
|
||
`Unauthorized`. They said so rather than letting a manifest stand in for a probe.
|
||
|
||
**It is fixed.** On 2026-08-19 `flex-auth` reported both production Deployments
|
||
enforcing ADR-0004 TokenReview, with live unbound-request probes returning 401
|
||
rather than a decision, on both the `user-engine` and `tenant-engine` pins.
|
||
`FLEX-WP-0015` is finished and `tenancy.current.A` is 2. That is a probe
|
||
against the running system, which is the standard `docs/method/review.md` sets
|
||
for closing: something concrete read, not something been told.
|
||
|
||
**Disclosure flips to `public`.** The embargo condition was "FLEX-WP-0015-T02
|
||
ships to production" and it is met. Handover to `policy-nexus` is the next
|
||
step and is not done yet.
|
||
|
||
**The escalation is withdrawn without being sent.** It was `pending-operator`
|
||
for roughly four hours, and the fix landed first. Withdrawing it is correct —
|
||
escalating a fixed defect makes the operator the queue for history — but the
|
||
register does not get to be pleased about it. The escalation would have been
|
||
sent on facts that were already stale, and only luck put the fix on the same
|
||
day.
|
||
|
||
**What this cost, recorded because it is the register's own defect.** The
|
||
NetworkPolicy answer arrived 2026-08-18. The fix notice arrived 2026-08-19
|
||
at 12:35. The first grading ran at 21:14 the same day, on neither. Reading the
|
||
inbox is now step 0 of grading and question 0 of every review — see
|
||
`docs/method/review.md` — and this finding is the case that bought it.
|
||
|
||
## Reviews
|
||
|
||
- **2026-08-19** — re-graded `high`, closed `fixed`, disclosure `public`,
|
||
escalation withdrawn. Remaining: handover to `policy-nexus`; the CNI
|
||
enforcement question is `flex-auth`'s and no longer this finding's.
|