feat: publish Risk Nexus findings and methods
All checks were successful
Build and publish policy-nexus image / build-and-push (push) Successful in 1m10s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
This commit is contained in:
tegwick 2026-09-01 01:56:46 +02:00
parent 4c8a7b9666
commit c1b60f322e
70 changed files with 3888 additions and 198 deletions

View file

@ -0,0 +1,274 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="c5517c754bd84b0ebf47878ba0f26df0ecb3b4a4">
<meta name="policy-source-digest" content="8bf47414a9f958afb57649f087a2617f91a8f2aeaa4f34d04d8fb3a0f453e840">
<title>The legal basis for retaining audit facts against an erasure request has been assumed, never established</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>RISK-F-0008</span> <span class="stat">accepted · published-1</span> <span>risk-nexus</span> <span>reviewed 2026-08-20</span><span>generated from canonical source — do not edit</span></div><h1>The legal basis for retaining audit facts against an erasure request has been assumed, never established</h1><p class="sub">The estate retains personal data in audit records on grounds nobody had established. Published as a question, because it is one.</p><p class="sub">Source: <code>risk-nexus · findings/RISK-F-0008-audit-retention-legal-basis-assumed.md · c5517c754bd84b0ebf47878ba0f26df0ecb3b4a4</code></p><p class="sub">Review due: 2027-02-20</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#what-is-true"><span class="n">·</span>What is true</a></li><li><a href="#why-this-repo-owns-it"><span class="n">·</span>Why this repo owns it</a></li><li><a href="#the-three-questions-as-asked"><span class="n">·</span>The three questions, as asked</a></li><li><a href="#register-ruling-2026-08-19"><span class="n">·</span>Register ruling — 2026-08-19</a></li><li><a href="#how-it-got-here"><span class="n">·</span>How it got here</a></li><li><a href="#reviews"><span class="n">·</span>Reviews</a></li><li><a href="#suggested-disposition-2026-08-20-proposed-by-risk-nexus"><span class="n">·</span>Suggested disposition — 2026-08-20, proposed by risk-nexus</a></li><li><a href="#operator-decision-2026-08-20-minimise-the-identity-keep-the-accountability"><span class="n">·</span>Operator decision — 2026-08-20: minimise the identity, keep the accountability</a></li><li><a href="#the-determination-exists-2026-08-20"><span class="n">·</span>The determination exists — 2026-08-20</a></li><li><a href="#operator-decision-2026-08-20-no-external-determination-and-a-policy-set-instead"><span class="n">·</span>Operator decision — 2026-08-20: no external determination, and a policy set instead</a></li></ol></nav><main><section id="what-is-true"><h2>What is true</h2>
<p><code>audit-core</code> holds audit evidence across tenants, targets <code>R2</code> on the Tenancy Posture retention ladder, and has declared <code>R4</code> — verified erasure — unreachable by design. The technical reasoning is sound and documented (<code>audit-core/docs/erasure-and-audit.md</code>, framework Decision 4.5.3): crypto-shredding would destroy the evidence the service exists to hold, and their integrity chain commits to a SHA-256 of the cleartext record, which survives key destruction as a confirmation oracle against low-entropy audit rows. Destroying a key does not erase content a surviving commitment can still be tested against.</p>
<p>The consequence is that if an Article 17 request arrives naming a data subject in the audit trail, <code>audit-core</code> has no mechanism. The answer would rest on audit evidence being exempt — legal obligation, or legitimate interest in fraud and security investigation.</p>
<p><strong>Those grounds are ordinary. Nobody in this estate has actually reached them.</strong> <code>audit-core</code> routed the question here on 2026-08-18 rather than absorbing it, saying plainly that they are not competent to answer it and that they have been assuming it. §19.11 of the framework says the same in its own words: the legal basis for retaining audit facts remains a risk/legal question outside the framework.</p>
</section>
<section id="why-this-repo-owns-it"><h2>Why this repo owns it</h2>
<p>This is the first finding where <code>fix_owner</code> is <code>risk-nexus</code>.</p>
<p><code>INTENT.md</code> moved regulatory intake here from <code>policy-nexus</code> on 2026-08-17, precisely because deciding what a rule demands of us is a judgement about risk rather than an act of publishing. <code>audit-core</code> routed it by both available routes and asked for an owner rather than an opinion. Refusing it would be this repo declining its own remit.</p>
<p>What this repo owns is the <strong>record</strong>: what the source says, when, and what therefore is or is not established. It does not own legal advice — <code>INTENT.md</code> is explicit — and it does not own the redesign. If the basis does not hold, <code>audit-core</code> owns encrypt-then-hash at accept time, and that is not retrofittable onto events already accepted.</p>
</section>
<section id="the-three-questions-as-asked"><h2>The three questions, as asked</h2>
<ol><li>On what basis does the estate retain personal data inside audit records against an erasure request, and does that basis hold for the categories <code>audit-core</code> stores?</li><li>Does it hold across the full 30-day recoverable window and beyond, given that at <code>P1</code> the real erasure horizon is the maximum across every co-resident on <code>platform-pg</code>, not the value <code>audit-core</code> declares?</li><li>If it does not hold, <code>R4</code> is urgent rather than theoretical, and the answer is a substantial redesign with a long lead time.</li></ol>
</section>
<section id="register-ruling-2026-08-19"><h2>Register ruling — 2026-08-19</h2>
<p><code>medium</code> today (<code>I3</code> × <code>L2</code>), <code>high</code> at production, <code>public</code>, <strong>escalated on trigger 2</strong>.</p>
<p><code>I3</code>: an unmet retention obligation in the audit store crosses from a technical question to an obligation with an outside counterparty, and the remediation is a non-retrofittable redesign rather than a patch. <code>L2</code>: no request has arrived and the estate holds no real data subject's records yet, but the trigger is somebody else's to pull and needs no foothold here.</p>
<p><code>production_rescore: true</code>. The likelihood of an Article 17 request is a function of having real users; that is exactly what production means.</p>
<p><strong>Escalation, trigger 2</strong> — "creates or reveals an obligation with an outside counterparty". It reveals one. The estate cannot decide unilaterally that this obligation is small, and the operator is the only party who can commission an answer that is more than an assumption. The ask is narrow: authorise someone to establish the basis, or record that the estate knowingly runs on the assumption and for how long.</p>
<p><strong>Disclosure <code>public</code>.</strong> Nothing here shortens a path to a defect: it is a question about a legal basis, published as a question. <code>audit-core</code>'s technical reasoning is already written down and worth reading.</p>
</section>
<section id="how-it-got-here"><h2>How it got here</h2>
<p>Ruled a note on 2026-08-19 (<code>RISK-N-0002</code>) on the reasoning that no obligation exists yet. That ruling was made without reading <code>audit-core</code>'s message, which had been in this repo's inbox since 2026-08-18 and asks specifically for an owner. The note was wrong on the second floor test: recording this <em>does</em> change a decision, because the redesign it might force cannot be retrofitted and therefore has to be decided early or not at all.</p>
<p><code>RISK-N-0002</code> is superseded by this record.</p>
</section>
<section id="reviews"><h2>Reviews</h2>
<ul><li><strong>2026-08-19</strong> — promoted from note, graded, escalated. Open at review: has the basis been established or the assumption recorded; has anything changed about what categories <code>audit-core</code> stores.</li></ul>
</section>
<section id="suggested-disposition-2026-08-20-proposed-by-risk-nexus"><h2>Suggested disposition — 2026-08-20, proposed by risk-nexus</h2>
<p>Offered because this repo owns the finding and the operator asked for a direction. It is not legal advice, and this repo cannot make it into one: what follows is a <em>route to an answer</em> and a hedge against the answer being no.</p>
<h3>The reframe: the expensive thing is not the legal question</h3>
<p><code>audit-core</code> asks whether the exemption holds. That question is cheap to answer badly and expensive to answer properly, and the temptation is to schedule the proper version and wait.</p>
<p>But the cost of a "no" is not fixed — it grows daily. The remedy they name, encrypt-then-hash at accept time, cannot be retrofitted onto events already accepted. <strong>Every day the estate accepts events under the current scheme, the un-erasable set grows by one day.</strong> So the decision that actually needs taking now is not "is it exempt" but "do we keep manufacturing records we could never erase while we find out".</p>
<p>That splits the finding into two decisions with very different prices.</p>
<h3>1. Establish the basis internally, now, for the cost of an afternoon</h3>
<p>Not a legal opinion — a <strong>written determination</strong> that says which ground is being relied on, for which category of data, and for how long. Today the estate has no such document; that is the whole finding.</p>
<p>The shape it should take, per category of personal data in the audit trail:</p>
<div class="scroll"><table><thead><tr><th>Category</th><th>Likely ground</th><th>The part that is actually arguable</th></tr></thead><tbody><tr><td>Operator and agent identifiers</td><td>Art 6(1)(f) legitimate interest in security, with Recital 49 squarely on point</td><td>little — this is the ordinary case</td></tr><tr><td>Counterparty or end-user identifiers in event payloads</td><td>Art 17(3)(e), defence of legal claims; Art 6(1)(f)</td><td><strong>duration</strong>, not existence</td></tr><tr><td>Commercial records that happen to pass through audit</td><td>Art 17(3)(b) plus German §257 HGB / §147 AO retention</td><td>scope — retention duties cover books and invoices, not application logs generally</td></tr></tbody></table></div>
<p>Where such determinations usually fail is <strong>not</strong> the ground. It is the retention period: a blanket "we keep audit forever under legitimate interest" is much weaker than "we keep these fields for N months because X". That lands precisely on <code>audit-core</code>'s question 2 — the real horizon being the maximum across every co-resident on <code>platform-pg</code> rather than the declared value.</p>
<p>Recording the determination converts an assumption into a position that can be argued with. That is what this register exists to produce, and it does not require a lawyer to write down.</p>
<h3>2. Stop the un-erasable set from growing — a cheaper hedge than the redesign</h3>
<p><code>audit-core</code>'s stated obstacle is precise and correct: their chain commits to <code>SHA-256(cleartext)</code>, audit records are low-entropy, so the retained hash survives key destruction as a confirmation oracle. Guess, hash, compare.</p>
<p>The oracle exists because the commitment is over cleartext with no secret in it. A <strong>keyed commitment</strong> removes it: replace the digest with an HMAC (or a hash over record plus a high-entropy per-subject salt) where the key or salt lives outside the audit store and is destroyable per subject.</p>
<p>What that buys, and why it is cheaper than the redesign they costed:</p>
<ul><li>Destroying the per-subject key makes the commitment untestable — no guess can be confirmed. That is crypto-shredding restored, which their analysis correctly found unavailable under a plain hash.</li><li>The integrity chain still verifies. It chains over commitment values, and those persist after key destruction; what is lost is the ability to re-derive a commitment from cleartext, which is exactly what erasure means.</li><li>It is a change at accept time only. No re-processing of stored events, no new storage layer, no change to the read path.</li></ul>
<p>This is a suggestion to <code>audit-core</code>, not an instruction, and they own whether it is sound — they know their chain and this repo does not. The claim worth testing with them is narrow: <strong>does a keyed commitment restore erasability without breaking chain verification?</strong> If yes, the expensive redesign becomes a contingency rather than a plan, and the daily accrual stops.</p>
<h3>3. Buy the real answer only when something triggers it</h3>
<p>An external determination costs money and needs a real question. Propose three triggers, any of which fires it:</p>
<ul><li>the estate first holds a real person's data;</li><li>a counterparty contract requires a stated erasure position;</li><li>an actual Art 17 request arrives.</li></ul>
<p>Until one fires, the internal determination plus the hedge is a proportionate posture, and <code>severity_at_production: high</code> plus <code>production_rescore: true</code> already guarantee this is re-read before production completes.</p>
<h3>What this repo would record if the operator agrees</h3>
<p><code>status: accepted</code> with the determination attached, <code>escalation</code> answered as <code>rule</code>, and the review kept at 90 days. The finding stays open and visible until the determination exists — an accepted risk with no written basis is the same assumption it started as, wearing a different word.</p>
<h3>Also worth saying, because it is the cheapest fix of all</h3>
<p>Every field of personal data that never enters the audit trail is a field with no erasure question. Where an opaque subject identifier would carry the same evidentiary weight as a name or an address, the identifier is strictly better, and that is a <code>audit-core</code> design choice available today at no legal cost.</p>
</section>
<section id="operator-decision-2026-08-20-minimise-the-identity-keep-the-accountability"><h2>Operator decision — 2026-08-20: minimise the identity, keep the accountability</h2>
<p>The custodian ruled on what goes into an audit record, which is the half of this finding that shrinks the question rather than answering it:</p>
<ol><li><strong>Opaque subject identifiers are preferred.</strong> Where an opaque id carries the same evidentiary weight as a name or an address, it is the id that goes in.</li><li><strong>Agent identifiers where possible.</strong> Agents act; attribute to the acting agent identity rather than to a person behind it.</li><li><strong>Operator credentials only where necessary.</strong> Not as a convenience, not as a default — where the record genuinely requires the operator.</li><li><strong>Policy decisions are tracked to the responsible party.</strong> Accountability is preserved by linking a decision to who is answerable for it, not by retaining personal data in the record itself.</li><li><strong>Zone guarantees may raise the floor.</strong> If a zone establishes additional privacy, pseudonymity or anonymity guarantees, those apply — the current level is not a permanent ceiling. That work is <code>zone-engine</code>'s (<code>ZONE-WP-0001</code>), and this finding should be re-read when a zone lands one.</li></ol>
<p><strong>Why this is more than a preference.</strong> Personal data that never enters the audit trail has no erasure question, no exemption to establish, and nothing to argue about with a regulator. Points 1-3 shrink the population the legal basis has to cover; point 4 is what stops that shrinking from costing accountability, which is the usual objection to minimising an audit log.</p>
<p>It also changes the shape of the accrual problem. The un-erasable set still grows daily, but each day's records now carry less that would need erasing — so the cost of a "no" answer falls with every event accepted under the new rule rather than rising.</p>
<p><strong>What is still outstanding</strong>, and stays escalated:</p>
<ul><li>The <strong>written determination</strong> of the retention basis — which ground, for which category, for how long. <code>risk-nexus</code> owns writing it; it needs no further authorisation and is scheduled into the next workplan.</li><li>The <strong>trigger list</strong> for buying an external answer (first real person's data, first counterparty contract requiring a stated position, first Art 17 request). Proposed, not yet ruled on.</li></ul>
<p>The escalation is therefore <code>partially-answered</code>, not closed. <code>make check</code> will keep listing it.</p>
<p><strong>Routed to <code>audit-core</code> on 2026-08-20</strong>, together with the keyed-commitment question — which remains theirs to judge, because they know their chain.</p>
</section>
<section id="the-determination-exists-2026-08-20"><h2>The determination exists — 2026-08-20</h2>
<p><code>docs/regulatory/RISK-REG-0001</code> (<code>audit-retention-basis.md</code>). The estate now has a written position rather than an assumption, which was this finding's substance.</p>
<p>What it says, in short: Art 6(1)(f) with Art 32 for operator and agent audit records; Art 17(3)(e) for records evidencing a counterparty transaction; Art 17(3)(b) only where a commercial or tax retention duty independently applies, and not extended to application logs generally.</p>
<p><strong>The weak part is duration, not existence</strong>, and the record says so rather than sounding confident. A position of the form "we keep audit forever because it is audit" is the one that fails; a period per category is what holds. The estate does not have one yet, and the reason is <code>audit-core</code>'s own question 2 — at <code>P1</code> the real horizon is the maximum across every co-resident on <code>platform-pg</code>, not the declared value. <strong>That infrastructure fact is the most likely point of failure in the whole position.</strong></p>
<p>The operator's minimisation ruling improves this materially: it shrinks the category whose retention is hardest to justify, leaving mostly the row where the ground is strong. A weak argument avoided by holding less data beats a strong one relied upon.</p>
<p>The finding stays open. What remains is a retention period per category, which waits on the co-residency horizon, and the trigger list for buying an external determination. The record is reviewed every 90 days with this finding, or immediately on any trigger.</p>
<ul><li><strong>2026-08-20</strong> — not clean: The determination now exists: RISK-REG-0001 states the grounds per category and names duration as the weak point. Cadence instant → instant; checked again immediately.</li></ul>
</section>
<section id="operator-decision-2026-08-20-no-external-determination-and-a-policy-set-instead"><h2>Operator decision — 2026-08-20: no external determination, and a policy set instead</h2>
<p>Ruled: <strong>the estate will not buy an external determination while it is building.</strong> The internal determination (<code>RISK-REG-0001</code>) stands as the recorded position, and the finding moves to <code>accepted</code> — deliberately carried, with a named accepter and a condition that ends it.</p>
<p>That is not the same as the trigger list being rejected. The triggers survive as what ends the acceptance: a real person's data, or a counterparty requiring a stated position. What was declined is spending money in advance of either.</p>
<p><strong>The compensating control is the thing that makes this defensible.</strong> Rather than defer the question, the operator directed that the estate <strong>define and keep a set of legal policies for reuse</strong>, because future work contexts will need specific positions in place and should retrieve them rather than research them.</p>
<p><code>docs/regulatory/policies/</code> now catalogues thirteen, keyed by activation condition. Two of them turned out to be <strong>already active and unowned</strong>: commercial and tax retention (<code>RISK-POL-0009</code>), and the e-invoicing receiving obligation (<code>RISK-POL-0012</code>), live since 2025 with no system in the estate named as the receiving point.</p>
<p>Finding an unnoticed live obligation in the first hour of building the catalogue is the argument for having built it. The reason this repo exists is that regulation was previously "consulted and discarded"; a set that answers "what applies if we do X" before anyone does X is the opposite of that.</p>
<p><strong>Still open under the acceptance</strong>, and unchanged by this ruling: <code>audit-core</code> on whether a keyed commitment restores erasability, and the <code>platform-pg</code> co-residency horizon that decides whether the stated retention periods are achievable. An accepted risk still gets checked.</p>
<ul><li><strong>2026-08-20</strong> — not clean: Trigger list ruled: no external determination in build mode; accepted with the legal policy set as the compensating control. Cadence instant → instant; checked again immediately.</li></ul>
</section><footer><span>RISK-F-0008 · published-1 · accepted</span><span>risk-nexus · findings/RISK-F-0008-audit-retention-legal-basis-assumed.md · c5517c754bd84b0ebf47878ba0f26df0ecb3b4a4</span></footer></main></div></div></html>

View file

@ -0,0 +1,274 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="a13d954f8597fd92201746e2d52f031a5a88d969">
<meta name="policy-source-digest" content="8bf47414a9f958afb57649f087a2617f91a8f2aeaa4f34d04d8fb3a0f453e840">
<title>The legal basis for retaining audit facts against an erasure request has been assumed, never established</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>RISK-F-0008</span> <span class="stat">accepted · published-1</span> <span>risk-nexus</span> <span>reviewed 2026-08-20</span><span>generated from canonical source — do not edit</span></div><h1>The legal basis for retaining audit facts against an erasure request has been assumed, never established</h1><p class="sub">The estate retains personal data in audit records on grounds nobody had established. Published as a question, because it is one.</p><p class="sub">Source: <code>risk-nexus · findings/RISK-F-0008-audit-retention-legal-basis-assumed.md · a13d954f8597fd92201746e2d52f031a5a88d969</code></p><p class="sub">Review due: 2027-02-20</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#what-is-true"><span class="n">·</span>What is true</a></li><li><a href="#why-this-repo-owns-it"><span class="n">·</span>Why this repo owns it</a></li><li><a href="#the-three-questions-as-asked"><span class="n">·</span>The three questions, as asked</a></li><li><a href="#register-ruling-2026-08-19"><span class="n">·</span>Register ruling — 2026-08-19</a></li><li><a href="#how-it-got-here"><span class="n">·</span>How it got here</a></li><li><a href="#reviews"><span class="n">·</span>Reviews</a></li><li><a href="#suggested-disposition-2026-08-20-proposed-by-risk-nexus"><span class="n">·</span>Suggested disposition — 2026-08-20, proposed by risk-nexus</a></li><li><a href="#operator-decision-2026-08-20-minimise-the-identity-keep-the-accountability"><span class="n">·</span>Operator decision — 2026-08-20: minimise the identity, keep the accountability</a></li><li><a href="#the-determination-exists-2026-08-20"><span class="n">·</span>The determination exists — 2026-08-20</a></li><li><a href="#operator-decision-2026-08-20-no-external-determination-and-a-policy-set-instead"><span class="n">·</span>Operator decision — 2026-08-20: no external determination, and a policy set instead</a></li></ol></nav><main><section id="what-is-true"><h2>What is true</h2>
<p><code>audit-core</code> holds audit evidence across tenants, targets <code>R2</code> on the Tenancy Posture retention ladder, and has declared <code>R4</code> — verified erasure — unreachable by design. The technical reasoning is sound and documented (<code>audit-core/docs/erasure-and-audit.md</code>, framework Decision 4.5.3): crypto-shredding would destroy the evidence the service exists to hold, and their integrity chain commits to a SHA-256 of the cleartext record, which survives key destruction as a confirmation oracle against low-entropy audit rows. Destroying a key does not erase content a surviving commitment can still be tested against.</p>
<p>The consequence is that if an Article 17 request arrives naming a data subject in the audit trail, <code>audit-core</code> has no mechanism. The answer would rest on audit evidence being exempt — legal obligation, or legitimate interest in fraud and security investigation.</p>
<p><strong>Those grounds are ordinary. Nobody in this estate has actually reached them.</strong> <code>audit-core</code> routed the question here on 2026-08-18 rather than absorbing it, saying plainly that they are not competent to answer it and that they have been assuming it. §19.11 of the framework says the same in its own words: the legal basis for retaining audit facts remains a risk/legal question outside the framework.</p>
</section>
<section id="why-this-repo-owns-it"><h2>Why this repo owns it</h2>
<p>This is the first finding where <code>fix_owner</code> is <code>risk-nexus</code>.</p>
<p><code>INTENT.md</code> moved regulatory intake here from <code>policy-nexus</code> on 2026-08-17, precisely because deciding what a rule demands of us is a judgement about risk rather than an act of publishing. <code>audit-core</code> routed it by both available routes and asked for an owner rather than an opinion. Refusing it would be this repo declining its own remit.</p>
<p>What this repo owns is the <strong>record</strong>: what the source says, when, and what therefore is or is not established. It does not own legal advice — <code>INTENT.md</code> is explicit — and it does not own the redesign. If the basis does not hold, <code>audit-core</code> owns encrypt-then-hash at accept time, and that is not retrofittable onto events already accepted.</p>
</section>
<section id="the-three-questions-as-asked"><h2>The three questions, as asked</h2>
<ol><li>On what basis does the estate retain personal data inside audit records against an erasure request, and does that basis hold for the categories <code>audit-core</code> stores?</li><li>Does it hold across the full 30-day recoverable window and beyond, given that at <code>P1</code> the real erasure horizon is the maximum across every co-resident on <code>platform-pg</code>, not the value <code>audit-core</code> declares?</li><li>If it does not hold, <code>R4</code> is urgent rather than theoretical, and the answer is a substantial redesign with a long lead time.</li></ol>
</section>
<section id="register-ruling-2026-08-19"><h2>Register ruling — 2026-08-19</h2>
<p><code>medium</code> today (<code>I3</code> × <code>L2</code>), <code>high</code> at production, <code>public</code>, <strong>escalated on trigger 2</strong>.</p>
<p><code>I3</code>: an unmet retention obligation in the audit store crosses from a technical question to an obligation with an outside counterparty, and the remediation is a non-retrofittable redesign rather than a patch. <code>L2</code>: no request has arrived and the estate holds no real data subject's records yet, but the trigger is somebody else's to pull and needs no foothold here.</p>
<p><code>production_rescore: true</code>. The likelihood of an Article 17 request is a function of having real users; that is exactly what production means.</p>
<p><strong>Escalation, trigger 2</strong> — "creates or reveals an obligation with an outside counterparty". It reveals one. The estate cannot decide unilaterally that this obligation is small, and the operator is the only party who can commission an answer that is more than an assumption. The ask is narrow: authorise someone to establish the basis, or record that the estate knowingly runs on the assumption and for how long.</p>
<p><strong>Disclosure <code>public</code>.</strong> Nothing here shortens a path to a defect: it is a question about a legal basis, published as a question. <code>audit-core</code>'s technical reasoning is already written down and worth reading.</p>
</section>
<section id="how-it-got-here"><h2>How it got here</h2>
<p>Ruled a note on 2026-08-19 (<code>RISK-N-0002</code>) on the reasoning that no obligation exists yet. That ruling was made without reading <code>audit-core</code>'s message, which had been in this repo's inbox since 2026-08-18 and asks specifically for an owner. The note was wrong on the second floor test: recording this <em>does</em> change a decision, because the redesign it might force cannot be retrofitted and therefore has to be decided early or not at all.</p>
<p><code>RISK-N-0002</code> is superseded by this record.</p>
</section>
<section id="reviews"><h2>Reviews</h2>
<ul><li><strong>2026-08-19</strong> — promoted from note, graded, escalated. Open at review: has the basis been established or the assumption recorded; has anything changed about what categories <code>audit-core</code> stores.</li></ul>
</section>
<section id="suggested-disposition-2026-08-20-proposed-by-risk-nexus"><h2>Suggested disposition — 2026-08-20, proposed by risk-nexus</h2>
<p>Offered because this repo owns the finding and the operator asked for a direction. It is not legal advice, and this repo cannot make it into one: what follows is a <em>route to an answer</em> and a hedge against the answer being no.</p>
<h3>The reframe: the expensive thing is not the legal question</h3>
<p><code>audit-core</code> asks whether the exemption holds. That question is cheap to answer badly and expensive to answer properly, and the temptation is to schedule the proper version and wait.</p>
<p>But the cost of a "no" is not fixed — it grows daily. The remedy they name, encrypt-then-hash at accept time, cannot be retrofitted onto events already accepted. <strong>Every day the estate accepts events under the current scheme, the un-erasable set grows by one day.</strong> So the decision that actually needs taking now is not "is it exempt" but "do we keep manufacturing records we could never erase while we find out".</p>
<p>That splits the finding into two decisions with very different prices.</p>
<h3>1. Establish the basis internally, now, for the cost of an afternoon</h3>
<p>Not a legal opinion — a <strong>written determination</strong> that says which ground is being relied on, for which category of data, and for how long. Today the estate has no such document; that is the whole finding.</p>
<p>The shape it should take, per category of personal data in the audit trail:</p>
<div class="scroll"><table><thead><tr><th>Category</th><th>Likely ground</th><th>The part that is actually arguable</th></tr></thead><tbody><tr><td>Operator and agent identifiers</td><td>Art 6(1)(f) legitimate interest in security, with Recital 49 squarely on point</td><td>little — this is the ordinary case</td></tr><tr><td>Counterparty or end-user identifiers in event payloads</td><td>Art 17(3)(e), defence of legal claims; Art 6(1)(f)</td><td><strong>duration</strong>, not existence</td></tr><tr><td>Commercial records that happen to pass through audit</td><td>Art 17(3)(b) plus German §257 HGB / §147 AO retention</td><td>scope — retention duties cover books and invoices, not application logs generally</td></tr></tbody></table></div>
<p>Where such determinations usually fail is <strong>not</strong> the ground. It is the retention period: a blanket "we keep audit forever under legitimate interest" is much weaker than "we keep these fields for N months because X". That lands precisely on <code>audit-core</code>'s question 2 — the real horizon being the maximum across every co-resident on <code>platform-pg</code> rather than the declared value.</p>
<p>Recording the determination converts an assumption into a position that can be argued with. That is what this register exists to produce, and it does not require a lawyer to write down.</p>
<h3>2. Stop the un-erasable set from growing — a cheaper hedge than the redesign</h3>
<p><code>audit-core</code>'s stated obstacle is precise and correct: their chain commits to <code>SHA-256(cleartext)</code>, audit records are low-entropy, so the retained hash survives key destruction as a confirmation oracle. Guess, hash, compare.</p>
<p>The oracle exists because the commitment is over cleartext with no secret in it. A <strong>keyed commitment</strong> removes it: replace the digest with an HMAC (or a hash over record plus a high-entropy per-subject salt) where the key or salt lives outside the audit store and is destroyable per subject.</p>
<p>What that buys, and why it is cheaper than the redesign they costed:</p>
<ul><li>Destroying the per-subject key makes the commitment untestable — no guess can be confirmed. That is crypto-shredding restored, which their analysis correctly found unavailable under a plain hash.</li><li>The integrity chain still verifies. It chains over commitment values, and those persist after key destruction; what is lost is the ability to re-derive a commitment from cleartext, which is exactly what erasure means.</li><li>It is a change at accept time only. No re-processing of stored events, no new storage layer, no change to the read path.</li></ul>
<p>This is a suggestion to <code>audit-core</code>, not an instruction, and they own whether it is sound — they know their chain and this repo does not. The claim worth testing with them is narrow: <strong>does a keyed commitment restore erasability without breaking chain verification?</strong> If yes, the expensive redesign becomes a contingency rather than a plan, and the daily accrual stops.</p>
<h3>3. Buy the real answer only when something triggers it</h3>
<p>An external determination costs money and needs a real question. Propose three triggers, any of which fires it:</p>
<ul><li>the estate first holds a real person's data;</li><li>a counterparty contract requires a stated erasure position;</li><li>an actual Art 17 request arrives.</li></ul>
<p>Until one fires, the internal determination plus the hedge is a proportionate posture, and <code>severity_at_production: high</code> plus <code>production_rescore: true</code> already guarantee this is re-read before production completes.</p>
<h3>What this repo would record if the operator agrees</h3>
<p><code>status: accepted</code> with the determination attached, <code>escalation</code> answered as <code>rule</code>, and the review kept at 90 days. The finding stays open and visible until the determination exists — an accepted risk with no written basis is the same assumption it started as, wearing a different word.</p>
<h3>Also worth saying, because it is the cheapest fix of all</h3>
<p>Every field of personal data that never enters the audit trail is a field with no erasure question. Where an opaque subject identifier would carry the same evidentiary weight as a name or an address, the identifier is strictly better, and that is a <code>audit-core</code> design choice available today at no legal cost.</p>
</section>
<section id="operator-decision-2026-08-20-minimise-the-identity-keep-the-accountability"><h2>Operator decision — 2026-08-20: minimise the identity, keep the accountability</h2>
<p>The custodian ruled on what goes into an audit record, which is the half of this finding that shrinks the question rather than answering it:</p>
<ol><li><strong>Opaque subject identifiers are preferred.</strong> Where an opaque id carries the same evidentiary weight as a name or an address, it is the id that goes in.</li><li><strong>Agent identifiers where possible.</strong> Agents act; attribute to the acting agent identity rather than to a person behind it.</li><li><strong>Operator credentials only where necessary.</strong> Not as a convenience, not as a default — where the record genuinely requires the operator.</li><li><strong>Policy decisions are tracked to the responsible party.</strong> Accountability is preserved by linking a decision to who is answerable for it, not by retaining personal data in the record itself.</li><li><strong>Zone guarantees may raise the floor.</strong> If a zone establishes additional privacy, pseudonymity or anonymity guarantees, those apply — the current level is not a permanent ceiling. That work is <code>zone-engine</code>'s (<code>ZONE-WP-0001</code>), and this finding should be re-read when a zone lands one.</li></ol>
<p><strong>Why this is more than a preference.</strong> Personal data that never enters the audit trail has no erasure question, no exemption to establish, and nothing to argue about with a regulator. Points 1-3 shrink the population the legal basis has to cover; point 4 is what stops that shrinking from costing accountability, which is the usual objection to minimising an audit log.</p>
<p>It also changes the shape of the accrual problem. The un-erasable set still grows daily, but each day's records now carry less that would need erasing — so the cost of a "no" answer falls with every event accepted under the new rule rather than rising.</p>
<p><strong>What is still outstanding</strong>, and stays escalated:</p>
<ul><li>The <strong>written determination</strong> of the retention basis — which ground, for which category, for how long. <code>risk-nexus</code> owns writing it; it needs no further authorisation and is scheduled into the next workplan.</li><li>The <strong>trigger list</strong> for buying an external answer (first real person's data, first counterparty contract requiring a stated position, first Art 17 request). Proposed, not yet ruled on.</li></ul>
<p>The escalation is therefore <code>partially-answered</code>, not closed. <code>make check</code> will keep listing it.</p>
<p><strong>Routed to <code>audit-core</code> on 2026-08-20</strong>, together with the keyed-commitment question — which remains theirs to judge, because they know their chain.</p>
</section>
<section id="the-determination-exists-2026-08-20"><h2>The determination exists — 2026-08-20</h2>
<p><code>docs/regulatory/RISK-REG-0001</code> (<code>audit-retention-basis.md</code>). The estate now has a written position rather than an assumption, which was this finding's substance.</p>
<p>What it says, in short: Art 6(1)(f) with Art 32 for operator and agent audit records; Art 17(3)(e) for records evidencing a counterparty transaction; Art 17(3)(b) only where a commercial or tax retention duty independently applies, and not extended to application logs generally.</p>
<p><strong>The weak part is duration, not existence</strong>, and the record says so rather than sounding confident. A position of the form "we keep audit forever because it is audit" is the one that fails; a period per category is what holds. The estate does not have one yet, and the reason is <code>audit-core</code>'s own question 2 — at <code>P1</code> the real horizon is the maximum across every co-resident on <code>platform-pg</code>, not the declared value. <strong>That infrastructure fact is the most likely point of failure in the whole position.</strong></p>
<p>The operator's minimisation ruling improves this materially: it shrinks the category whose retention is hardest to justify, leaving mostly the row where the ground is strong. A weak argument avoided by holding less data beats a strong one relied upon.</p>
<p>The finding stays open. What remains is a retention period per category, which waits on the co-residency horizon, and the trigger list for buying an external determination. The record is reviewed every 90 days with this finding, or immediately on any trigger.</p>
<ul><li><strong>2026-08-20</strong> — not clean: The determination now exists: RISK-REG-0001 states the grounds per category and names duration as the weak point. Cadence instant → instant; checked again immediately.</li></ul>
</section>
<section id="operator-decision-2026-08-20-no-external-determination-and-a-policy-set-instead"><h2>Operator decision — 2026-08-20: no external determination, and a policy set instead</h2>
<p>Ruled: <strong>the estate will not buy an external determination while it is building.</strong> The internal determination (<code>RISK-REG-0001</code>) stands as the recorded position, and the finding moves to <code>accepted</code> — deliberately carried, with a named accepter and a condition that ends it.</p>
<p>That is not the same as the trigger list being rejected. The triggers survive as what ends the acceptance: a real person's data, or a counterparty requiring a stated position. What was declined is spending money in advance of either.</p>
<p><strong>The compensating control is the thing that makes this defensible.</strong> Rather than defer the question, the operator directed that the estate <strong>define and keep a set of legal policies for reuse</strong>, because future work contexts will need specific positions in place and should retrieve them rather than research them.</p>
<p><code>docs/regulatory/policies/</code> now catalogues thirteen, keyed by activation condition. Two of them turned out to be <strong>already active and unowned</strong>: commercial and tax retention (<code>RISK-POL-0009</code>), and the e-invoicing receiving obligation (<code>RISK-POL-0012</code>), live since 2025 with no system in the estate named as the receiving point.</p>
<p>Finding an unnoticed live obligation in the first hour of building the catalogue is the argument for having built it. The reason this repo exists is that regulation was previously "consulted and discarded"; a set that answers "what applies if we do X" before anyone does X is the opposite of that.</p>
<p><strong>Still open under the acceptance</strong>, and unchanged by this ruling: <code>audit-core</code> on whether a keyed commitment restores erasability, and the <code>platform-pg</code> co-residency horizon that decides whether the stated retention periods are achievable. An accepted risk still gets checked.</p>
<ul><li><strong>2026-08-20</strong> — not clean: Trigger list ruled: no external determination in build mode; accepted with the legal policy set as the compensating control. Cadence instant → instant; checked again immediately.</li></ul>
</section><footer><span>RISK-F-0008 · published-1 · accepted</span><span>risk-nexus · findings/RISK-F-0008-audit-retention-legal-basis-assumed.md · a13d954f8597fd92201746e2d52f031a5a88d969</span></footer></main></div></div></html>

View file

@ -0,0 +1,245 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="c5517c754bd84b0ebf47878ba0f26df0ecb3b4a4">
<meta name="policy-source-digest" content="b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda">
<title>flex-auth /v1/check authenticates no caller</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>RISK-F-0001</span> <span class="stat">fixed · published-1</span> <span>risk-nexus</span> <span>reviewed 2026-08-20</span><span>generated from canonical source — do not edit</span></div><h1>flex-auth /v1/check authenticates no caller</h1><p class="sub">The estate&#x27;s authorization oracle authenticated no caller for as long as the endpoint existed. Found by reading, not by monitoring; fixed in two days.</p><p class="sub">Source: <code>risk-nexus · findings/RISK-F-0001-flex-auth-unauthenticated-check.md · c5517c754bd84b0ebf47878ba0f26df0ecb3b4a4</code></p><p class="sub">Review due: 2027-02-20</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#what-is-true"><span class="n">·</span>What is true</a></li><li><a href="#how-it-was-found"><span class="n">·</span>How it was found</a></li><li><a href="#exposure-as-far-as-the-reporter-stated-it"><span class="n">·</span>Exposure, as far as the reporter stated it</a></li><li><a href="#what-makes-it-worse-than-a-single-service-s-defect"><span class="n">·</span>What makes it worse than a single service&#x27;s defect</a></li><li><a href="#owner-and-state"><span class="n">·</span>Owner and state</a></li><li><a href="#what-this-repo-is-asked-to-decide"><span class="n">·</span>What this repo is asked to decide</a></li><li><a href="#related-reported-at-the-same-time-and-not-yet-filed"><span class="n">·</span>Related, reported at the same time and not yet filed</a></li><li><a href="#register-ruling-2026-08-19"><span class="n">·</span>Register ruling — 2026-08-19</a></li><li><a href="#reviews"><span class="n">·</span>Reviews</a></li><li><a href="#re-grade-and-close-2026-08-19-same-day"><span class="n">·</span>Re-grade and close — 2026-08-19 (same day)</a></li><li><a href="#reviews"><span class="n">·</span>Reviews</a></li></ol></nav><main><section id="what-is-true"><h2>What is true</h2>
<p><code>POST /v1/check</code> and <code>POST /v1/batch_check</code> authenticate no caller. Any workload with network reach to the ClusterIP Service can assert any subject and any tenant and receive an authoritative <strong>allow</strong>.</p>
<p><code>flex-auth</code> 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.</p>
<p>Self-reported by <code>flex-auth</code> as <code>A0</code> 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."</p>
</section>
<section id="how-it-was-found"><h2>How it was found</h2>
<p>Not by a probe, an incident, or an alert. By <code>flex-auth</code> 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.</p>
<p>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.</p>
</section>
<section id="exposure-as-far-as-the-reporter-stated-it"><h2>Exposure, as far as the reporter stated it</h2>
<ul><li>The Service is <code>ClusterIP</code>, so reach requires a workload inside the cluster.</li><li>No claim was made that network policy restricts which workloads can reach it, and this record does not assume one. <strong>If a default-deny NetworkPolicy fronts the service, that materially changes the exposure and should be verified rather than inferred</strong><code>flex-auth</code> did not state it either way, and I have not checked, because doing so would be reporting on a system I do not own.</li></ul>
</section>
<section id="what-makes-it-worse-than-a-single-service-s-defect"><h2>What makes it worse than a single service's defect</h2>
<p>A false allow from this endpoint is not confined to <code>flex-auth</code>. It is the answer other services act on. <code>tenant-engine</code> separately reports that its own mutations are authorized by <code>flex-auth</code> 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.</p>
</section>
<section id="owner-and-state"><h2>Owner and state</h2>
<p><code>flex-auth</code> owns the fix and has tracked it as <code>FLEX-WP-0015-T02</code>, 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.</p>
</section>
<section id="what-this-repo-is-asked-to-decide"><h2>What this repo is asked to decide</h2>
<ol><li><strong>Severity.</strong> Not the reporter's to set.</li><li><strong>Disclosure.</strong> 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.</li><li><strong>Escalation.</strong> 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.</li></ol>
</section>
<section id="related-reported-at-the-same-time-and-not-yet-filed"><h2>Related, reported at the same time and not yet filed</h2>
<p>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:</p>
<ul><li><code>tenant-engine</code><code>events()</code> returns the entire event log unfiltered. A live cross-tenant read at <code>E2</code>.</li><li><code>audit-core</code> — read path applies no tenant filter; a credential with <code>may_read</code> can read any tenant's events. Bounded by deployment (<code>may_read: false</code> on the production sender) and not by code. Tracked <code>AUDIT-WP-0008-T04</code>.</li><li><code>apps-pg</code> (<code>railiance-platform</code>) — no backup configured at all: no <code>barmanObjectStore</code>, no retention policy, <code>BestEffort</code> QoS. <code>R0</code> there means no recovery, not merely no erasure policy.</li></ul>
<p>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: <strong>four live defects, none found by monitoring.</strong></p>
</section>
<section id="register-ruling-2026-08-19"><h2>Register ruling — 2026-08-19</h2>
<p><code>critical</code> (<code>I4</code> × <code>L3</code>, no fidelity modifier), embargoed until <code>FLEX-WP-0015-T02</code> ships, <strong>escalated</strong> to the operator on trigger 1.</p>
<p>The question this finding put — whether governing access to tenant data counts as exposing it — is answered <strong>yes</strong>. An authorization oracle that can be forged is not one step removed from the data; it is the step.</p>
<p>Impact is <code>I4</code> because a forged allow does not stay here: it is the answer other services act on, and <code>tenant-engine</code> has stated what direct authority over its rows would mean. Likelihood is <code>L3</code> — 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.</p>
<p>No fidelity modifier: this endpoint answers honestly about a caller it never checked. The false-record hazard lives in <code>RISK-F-0002</code>'s constraint, where a consumer of this endpoint would begin signing records asserting an authorization that was never made. <strong>That constraint binds this finding's remediation</strong>: <code>ops-warden</code>'s <code>policy.enabled</code> must not be turned on until <code>/v1/check</code> authenticates its callers, and the ordering is</p>
<pre>flex-auth warn-only -&gt; ops-warden gate presents its SA token -&gt; logs clean
-&gt; flex-auth fail-closed -&gt; ops-warden policy.enabled: true</pre>
<p>Nothing further is asked of <code>flex-auth</code> beyond what they have committed to, except one fact only they can supply: <strong>is there a default-deny NetworkPolicy in front of the Service?</strong> It is the single fact that would most change this grade, and it is the first question at review.</p>
<p>Reasoning: <code>docs/rulings/2026-08-19-first-grading.md</code>.</p>
</section>
<section id="reviews"><h2>Reviews</h2>
<ul><li><strong>2026-08-19</strong> — graded. Next review 2026-08-26 (<code>critical</code> → 7 days). Open at review: the NetworkPolicy question; whether <code>FLEX-WP-0015-T02</code> has moved; whether the embargo still holds.</li></ul>
</section>
<section id="re-grade-and-close-2026-08-19-same-day"><h2>Re-grade and close — 2026-08-19 (same day)</h2>
<p><strong>Corrected from <code>critical</code> to <code>high</code>, and closed as <code>fixed</code>.</strong> 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.</p>
<p><strong>The exposure was narrower than graded.</strong> <code>flex-auth</code> answered the NetworkPolicy question on 2026-08-18: both production Deployments ship a NetworkPolicy in the same manifest, and it is <em>narrower</em> than default-deny — ingress restricted to one <code>namespaceSelector</code> plus one <code>podSelector</code> on port 8080, egress empty. In force since before the period the <code>A0</code> describes. So the reachable set was never "any pod in the cluster"; it was the single paired workload per Deployment.</p>
<p>That is <code>L2</code>, not <code>L3</code>. Impact stays <code>I4</code> — what a forged allow reaches does not change — so the grade is <code>high</code>. <code>flex-auth</code> also corrected their own earlier phrasing to <code>ops-warden</code> in the same message, unprompted, and that correction is why the fact reached this register at all.</p>
<p>Three caveats <code>flex-auth</code> 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 <code>resource.system</code>, which is what made cross-system impersonation possible; and enforcement depends on a CNI they could not verify from a cluster where <code>kubectl</code> returned <code>Unauthorized</code>. They said so rather than letting a manifest stand in for a probe.</p>
<p><strong>It is fixed.</strong> On 2026-08-19 <code>flex-auth</code> reported both production Deployments enforcing ADR-0004 TokenReview, with live unbound-request probes returning 401 rather than a decision, on both the <code>user-engine</code> and <code>tenant-engine</code> pins. <code>FLEX-WP-0015</code> is finished and <code>tenancy.current.A</code> is 2. That is a probe against the running system, which is the standard <code>docs/method/review.md</code> sets for closing: something concrete read, not something been told.</p>
<p><strong>Disclosure flips to <code>public</code>.</strong> The embargo condition was "FLEX-WP-0015-T02 ships to production" and it is met. Handover to <code>policy-nexus</code> is the next step and is not done yet.</p>
<p><strong>The escalation is withdrawn without being sent.</strong> It was <code>pending-operator</code> 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.</p>
<p><strong>What this cost, recorded because it is the register's own defect.</strong> 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 <code>docs/method/review.md</code> — and this finding is the case that bought it.</p>
</section>
<section id="reviews"><h2>Reviews</h2>
<ul><li><strong>2026-08-19</strong> — re-graded <code>high</code>, closed <code>fixed</code>, disclosure <code>public</code>, escalation withdrawn. Remaining: handover to <code>policy-nexus</code>; the CNI enforcement question is <code>flex-auth</code>'s and no longer this finding's.</li><li><strong>2026-08-20</strong> — not clean: Publication handover requested; publication front-matter applied and the wait on policy-nexus typed. Cadence instant → instant; checked again immediately.</li></ul>
</section><footer><span>RISK-F-0001 · published-1 · fixed</span><span>risk-nexus · findings/RISK-F-0001-flex-auth-unauthenticated-check.md · c5517c754bd84b0ebf47878ba0f26df0ecb3b4a4</span></footer></main></div></div></html>

View file

@ -0,0 +1,245 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="a13d954f8597fd92201746e2d52f031a5a88d969">
<meta name="policy-source-digest" content="b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda">
<title>flex-auth /v1/check authenticates no caller</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>RISK-F-0001</span> <span class="stat">fixed · published-1</span> <span>risk-nexus</span> <span>reviewed 2026-08-20</span><span>generated from canonical source — do not edit</span></div><h1>flex-auth /v1/check authenticates no caller</h1><p class="sub">The estate&#x27;s authorization oracle authenticated no caller for as long as the endpoint existed. Found by reading, not by monitoring; fixed in two days.</p><p class="sub">Source: <code>risk-nexus · findings/RISK-F-0001-flex-auth-unauthenticated-check.md · a13d954f8597fd92201746e2d52f031a5a88d969</code></p><p class="sub">Review due: 2027-02-20</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#what-is-true"><span class="n">·</span>What is true</a></li><li><a href="#how-it-was-found"><span class="n">·</span>How it was found</a></li><li><a href="#exposure-as-far-as-the-reporter-stated-it"><span class="n">·</span>Exposure, as far as the reporter stated it</a></li><li><a href="#what-makes-it-worse-than-a-single-service-s-defect"><span class="n">·</span>What makes it worse than a single service&#x27;s defect</a></li><li><a href="#owner-and-state"><span class="n">·</span>Owner and state</a></li><li><a href="#what-this-repo-is-asked-to-decide"><span class="n">·</span>What this repo is asked to decide</a></li><li><a href="#related-reported-at-the-same-time-and-not-yet-filed"><span class="n">·</span>Related, reported at the same time and not yet filed</a></li><li><a href="#register-ruling-2026-08-19"><span class="n">·</span>Register ruling — 2026-08-19</a></li><li><a href="#reviews"><span class="n">·</span>Reviews</a></li><li><a href="#re-grade-and-close-2026-08-19-same-day"><span class="n">·</span>Re-grade and close — 2026-08-19 (same day)</a></li><li><a href="#reviews"><span class="n">·</span>Reviews</a></li></ol></nav><main><section id="what-is-true"><h2>What is true</h2>
<p><code>POST /v1/check</code> and <code>POST /v1/batch_check</code> authenticate no caller. Any workload with network reach to the ClusterIP Service can assert any subject and any tenant and receive an authoritative <strong>allow</strong>.</p>
<p><code>flex-auth</code> 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.</p>
<p>Self-reported by <code>flex-auth</code> as <code>A0</code> 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."</p>
</section>
<section id="how-it-was-found"><h2>How it was found</h2>
<p>Not by a probe, an incident, or an alert. By <code>flex-auth</code> 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.</p>
<p>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.</p>
</section>
<section id="exposure-as-far-as-the-reporter-stated-it"><h2>Exposure, as far as the reporter stated it</h2>
<ul><li>The Service is <code>ClusterIP</code>, so reach requires a workload inside the cluster.</li><li>No claim was made that network policy restricts which workloads can reach it, and this record does not assume one. <strong>If a default-deny NetworkPolicy fronts the service, that materially changes the exposure and should be verified rather than inferred</strong><code>flex-auth</code> did not state it either way, and I have not checked, because doing so would be reporting on a system I do not own.</li></ul>
</section>
<section id="what-makes-it-worse-than-a-single-service-s-defect"><h2>What makes it worse than a single service's defect</h2>
<p>A false allow from this endpoint is not confined to <code>flex-auth</code>. It is the answer other services act on. <code>tenant-engine</code> separately reports that its own mutations are authorized by <code>flex-auth</code> 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.</p>
</section>
<section id="owner-and-state"><h2>Owner and state</h2>
<p><code>flex-auth</code> owns the fix and has tracked it as <code>FLEX-WP-0015-T02</code>, 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.</p>
</section>
<section id="what-this-repo-is-asked-to-decide"><h2>What this repo is asked to decide</h2>
<ol><li><strong>Severity.</strong> Not the reporter's to set.</li><li><strong>Disclosure.</strong> 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.</li><li><strong>Escalation.</strong> 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.</li></ol>
</section>
<section id="related-reported-at-the-same-time-and-not-yet-filed"><h2>Related, reported at the same time and not yet filed</h2>
<p>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:</p>
<ul><li><code>tenant-engine</code><code>events()</code> returns the entire event log unfiltered. A live cross-tenant read at <code>E2</code>.</li><li><code>audit-core</code> — read path applies no tenant filter; a credential with <code>may_read</code> can read any tenant's events. Bounded by deployment (<code>may_read: false</code> on the production sender) and not by code. Tracked <code>AUDIT-WP-0008-T04</code>.</li><li><code>apps-pg</code> (<code>railiance-platform</code>) — no backup configured at all: no <code>barmanObjectStore</code>, no retention policy, <code>BestEffort</code> QoS. <code>R0</code> there means no recovery, not merely no erasure policy.</li></ul>
<p>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: <strong>four live defects, none found by monitoring.</strong></p>
</section>
<section id="register-ruling-2026-08-19"><h2>Register ruling — 2026-08-19</h2>
<p><code>critical</code> (<code>I4</code> × <code>L3</code>, no fidelity modifier), embargoed until <code>FLEX-WP-0015-T02</code> ships, <strong>escalated</strong> to the operator on trigger 1.</p>
<p>The question this finding put — whether governing access to tenant data counts as exposing it — is answered <strong>yes</strong>. An authorization oracle that can be forged is not one step removed from the data; it is the step.</p>
<p>Impact is <code>I4</code> because a forged allow does not stay here: it is the answer other services act on, and <code>tenant-engine</code> has stated what direct authority over its rows would mean. Likelihood is <code>L3</code> — 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.</p>
<p>No fidelity modifier: this endpoint answers honestly about a caller it never checked. The false-record hazard lives in <code>RISK-F-0002</code>'s constraint, where a consumer of this endpoint would begin signing records asserting an authorization that was never made. <strong>That constraint binds this finding's remediation</strong>: <code>ops-warden</code>'s <code>policy.enabled</code> must not be turned on until <code>/v1/check</code> authenticates its callers, and the ordering is</p>
<pre>flex-auth warn-only -&gt; ops-warden gate presents its SA token -&gt; logs clean
-&gt; flex-auth fail-closed -&gt; ops-warden policy.enabled: true</pre>
<p>Nothing further is asked of <code>flex-auth</code> beyond what they have committed to, except one fact only they can supply: <strong>is there a default-deny NetworkPolicy in front of the Service?</strong> It is the single fact that would most change this grade, and it is the first question at review.</p>
<p>Reasoning: <code>docs/rulings/2026-08-19-first-grading.md</code>.</p>
</section>
<section id="reviews"><h2>Reviews</h2>
<ul><li><strong>2026-08-19</strong> — graded. Next review 2026-08-26 (<code>critical</code> → 7 days). Open at review: the NetworkPolicy question; whether <code>FLEX-WP-0015-T02</code> has moved; whether the embargo still holds.</li></ul>
</section>
<section id="re-grade-and-close-2026-08-19-same-day"><h2>Re-grade and close — 2026-08-19 (same day)</h2>
<p><strong>Corrected from <code>critical</code> to <code>high</code>, and closed as <code>fixed</code>.</strong> 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.</p>
<p><strong>The exposure was narrower than graded.</strong> <code>flex-auth</code> answered the NetworkPolicy question on 2026-08-18: both production Deployments ship a NetworkPolicy in the same manifest, and it is <em>narrower</em> than default-deny — ingress restricted to one <code>namespaceSelector</code> plus one <code>podSelector</code> on port 8080, egress empty. In force since before the period the <code>A0</code> describes. So the reachable set was never "any pod in the cluster"; it was the single paired workload per Deployment.</p>
<p>That is <code>L2</code>, not <code>L3</code>. Impact stays <code>I4</code> — what a forged allow reaches does not change — so the grade is <code>high</code>. <code>flex-auth</code> also corrected their own earlier phrasing to <code>ops-warden</code> in the same message, unprompted, and that correction is why the fact reached this register at all.</p>
<p>Three caveats <code>flex-auth</code> 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 <code>resource.system</code>, which is what made cross-system impersonation possible; and enforcement depends on a CNI they could not verify from a cluster where <code>kubectl</code> returned <code>Unauthorized</code>. They said so rather than letting a manifest stand in for a probe.</p>
<p><strong>It is fixed.</strong> On 2026-08-19 <code>flex-auth</code> reported both production Deployments enforcing ADR-0004 TokenReview, with live unbound-request probes returning 401 rather than a decision, on both the <code>user-engine</code> and <code>tenant-engine</code> pins. <code>FLEX-WP-0015</code> is finished and <code>tenancy.current.A</code> is 2. That is a probe against the running system, which is the standard <code>docs/method/review.md</code> sets for closing: something concrete read, not something been told.</p>
<p><strong>Disclosure flips to <code>public</code>.</strong> The embargo condition was "FLEX-WP-0015-T02 ships to production" and it is met. Handover to <code>policy-nexus</code> is the next step and is not done yet.</p>
<p><strong>The escalation is withdrawn without being sent.</strong> It was <code>pending-operator</code> 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.</p>
<p><strong>What this cost, recorded because it is the register's own defect.</strong> 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 <code>docs/method/review.md</code> — and this finding is the case that bought it.</p>
</section>
<section id="reviews"><h2>Reviews</h2>
<ul><li><strong>2026-08-19</strong> — re-graded <code>high</code>, closed <code>fixed</code>, disclosure <code>public</code>, escalation withdrawn. Remaining: handover to <code>policy-nexus</code>; the CNI enforcement question is <code>flex-auth</code>'s and no longer this finding's.</li><li><strong>2026-08-20</strong> — not clean: Publication handover requested; publication front-matter applied and the wait on policy-nexus typed. Cadence instant → instant; checked again immediately.</li></ul>
</section><footer><span>RISK-F-0001 · published-1 · fixed</span><span>risk-nexus · findings/RISK-F-0001-flex-auth-unauthenticated-check.md · a13d954f8597fd92201746e2d52f031a5a88d969</span></footer></main></div></div></html>