risk-nexus/findings/RISK-F-0001-flex-auth-unauthenticated-check.md

231 lines
11 KiB
Markdown
Raw Normal View History

RISK-F-0001: flex-auth authenticates no caller on the decision surface First finding, and it establishes the record format by being one. Kept minimal on purpose - inventing a schema before there is a second finding to test it against would be designing in the dark. The substance: POST /v1/check and /v1/batch_check authenticate no caller, so any workload with network reach can assert any subject and any tenant and receive an authoritative allow from the estate's authorization oracle. Self-reported by flex-auth during their Tenancy Posture review, and their own note says they did not know they were carrying it. Severity, disclosure and escalation are left unset. Per INTENT this repo owns those and the reporter does not, and filling them in as the routing party would collapse the separation on the first record. Two things stated rather than assumed. Exposure depends on whether a default-deny NetworkPolicy fronts the service; flex-auth did not say, and I did not check, because that would be reporting on a system I do not own. And the disclosure question is genuinely live here - build mode is public-by-default, this is a live authorization bypass, and the controlled-disclosure mechanism this repo anticipates does not exist yet, so the choice today is publish or hold with nothing in between. Three related defects from the same review round are recorded as visible but not filed, since filing them was not asked for. All four were found by repos reading their own code against a ladder within a day of each other, and none was found by monitoring - which is itself a fact worth carrying into triage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:51:31 +02:00
---
id: RISK-F-0001
type: finding
title: "flex-auth /v1/check authenticates no caller"
status: fixed
RISK-F-0001: flex-auth authenticates no caller on the decision surface First finding, and it establishes the record format by being one. Kept minimal on purpose - inventing a schema before there is a second finding to test it against would be designing in the dark. The substance: POST /v1/check and /v1/batch_check authenticate no caller, so any workload with network reach can assert any subject and any tenant and receive an authoritative allow from the estate's authorization oracle. Self-reported by flex-auth during their Tenancy Posture review, and their own note says they did not know they were carrying it. Severity, disclosure and escalation are left unset. Per INTENT this repo owns those and the reporter does not, and filling them in as the routing party would collapse the separation on the first record. Two things stated rather than assumed. Exposure depends on whether a default-deny NetworkPolicy fronts the service; flex-auth did not say, and I did not check, because that would be reporting on a system I do not own. And the disclosure question is genuinely live here - build mode is public-by-default, this is a live authorization bypass, and the controlled-disclosure mechanism this repo anticipates does not exist yet, so the choice today is publish or hold with nothing in between. Three related defects from the same review round are recorded as visible but not filed, since filing them was not asked for. All four were found by repos reading their own code against a ladder within a day of each other, and none was found by monitoring - which is itself a fact worth carrying into triage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:51:31 +02:00
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: requested
publication_id: risk-f-0001-flex-auth-unauthenticated-check
publication_path: "findings/flex-auth-unauthenticated-check/v1/index.html"
publication_subtitle: "The estate's authorization oracle authenticated no caller for as long as the endpoint existed. Found by reading, not by monitoring; fixed in two days."
revision: "graded-1"
last_reviewed: "2026-08-20"
review_interval: 6m
embargo_lifted: "2026-08-19 — FLEX-WP-0015 finished, live probes return 401"
embargo_was_since: "2026-08-19"
escalation: withdrawn
escalation_trigger: 1
escalation_status: withdrawn-before-sending
date_fixed: "2026-08-19"
last_checked: "2026-08-20T20:44:46Z"
next_check: "2026-08-20T20:44:46Z"
cadence: instant
clean_streak: 0
waiting_on:
- who: policy-nexus
what: "publication.json entries for RISK-F-0001, RISK-F-0008 and the five public method documents"
since: "2026-08-20"
would_change: "publication: published plus the permanent URL comes back onto each record"
default: "the findings stay disclosure: public with no address, which the register records as a claim rather than a publication"
default_at: "2026-09-17"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-19
RISK-F-0001: flex-auth authenticates no caller on the decision surface First finding, and it establishes the record format by being one. Kept minimal on purpose - inventing a schema before there is a second finding to test it against would be designing in the dark. The substance: POST /v1/check and /v1/batch_check authenticate no caller, so any workload with network reach can assert any subject and any tenant and receive an authoritative allow from the estate's authorization oracle. Self-reported by flex-auth during their Tenancy Posture review, and their own note says they did not know they were carrying it. Severity, disclosure and escalation are left unset. Per INTENT this repo owns those and the reporter does not, and filling them in as the routing party would collapse the separation on the first record. Two things stated rather than assumed. Exposure depends on whether a default-deny NetworkPolicy fronts the service; flex-auth did not say, and I did not check, because that would be reporting on a system I do not own. And the disclosure question is genuinely live here - build mode is public-by-default, this is a live authorization bypass, and the controlled-disclosure mechanism this repo anticipates does not exist yet, so the choice today is publish or hold with nothing in between. Three related defects from the same review round are recorded as visible but not filed, since filing them was not asked for. All four were found by repos reading their own code against a ladder within a day of each other, and none was found by monitoring - which is itself a fact worth carrying into triage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:51:31 +02:00
---
# 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.
- **2026-08-20** — not clean: Publication handover requested; publication front-matter applied and the wait on policy-nexus typed. Cadence instant → instant; checked again immediately.