diff --git a/INTENT.md b/INTENT.md new file mode 100644 index 0000000..62e4b83 --- /dev/null +++ b/INTENT.md @@ -0,0 +1,140 @@ +# INTENT — whitehat-security + +## Why this repo exists + +`whitehat-security` is the estate's **adversarial evidence facility**. It +attacks the estate's own systems, on a schedule, to find out whether the +security properties they claim are actually true. + +It exists because of a specific gap. NetKingdom's *Tenancy Posture* standard +splits evidence into two kinds: **mechanical** — a structural assertion a +machine can make, which belongs in each repo's own CI — and **adversarial** — +a semantic claim that requires setting up hostile conditions and observing what +happens. Every repo can produce the first. **No repo can honestly produce the +second about itself**, and the framework's most severe open question has been +sitting unowned for exactly that reason: who verifies that a consumer's tenant +boundary actually holds. + +Two repos were asked and both correctly declined, on the grounds that a service +which grows fleet responsibilities because it happened to be nearby is how +boundaries erode. They were right. The work needed a home of its own. + +## Independence is the point + +A repo testing its own boundary grades its own homework. + +`rapp-postgres` wrote fifteen adversarial probes against its own isolation +model and found four real defects in its own provisioning SQL. That is good +work and it is still the author checking their own homework — the probes most +worth having are the ones an author would not think to write, because the same +assumption that produced the defect produces the blind spot. + +So this repo is **independent by construction**: + +- It does not take a repo's declared posture as true. It tests the claim. +- It is not owned by the repo whose canon it verifies. NetKingdom owns the + tenancy framework; a facility verifying conformance to that framework must + not report to it, or the self-grading problem simply moves up one level. +- It reports findings to `risk-nexus`, which owns severity and disclosure. It + does not decide how bad its own findings are, for the same reason. + +**Proposed owner: `the-custodian`**, consistent with `policy-nexus` and +`risk-nexus`, and deliberately not NetKingdom. To be confirmed. + +## What it owns + +- **Adversarial evidence artifacts.** The tests that produce the evidence the + Tenancy Posture ladders require but no repo can self-certify: + cross-tenant read and write attempts (E2), row-level-security conformance + under attack (E3), per-tenant credential confinement (E4), + noisy-neighbour characterisation (P1/P2), erasure verification (R4). +- **The attacker model.** What an adversary is assumed to hold at each level — + a leaked runtime credential, SQL execution, a compromised process — written + down, so a probe can be judged against a threat rather than against taste. +- **Cadence.** How often each probe runs. This is not a scheduling detail: for + any control whose guarantee is *detection rather than prevention*, the + interval between runs **is** the exposure window. `rapp-postgres` ADR-0003 + says so explicitly and leaves the number to be set here. +- **Differential testing between tenant contexts.** Running the same request as + two tenants and comparing responses is the mechanically reproducible core of + cross-tenant testing, and it is this repo's principal technique. +- **Rules of engagement.** What may be probed, where, with what credentials, + and what must never be done. An automated facility without this is + indistinguishable from the threat it models. + +## What it does not own + +- **Fixing anything.** A finding routes through `risk-nexus` to the repo that + owns the defect. A security facility that fixes things becomes a second + engineering team with no boundary and no reviewer. +- **Severity, disclosure and timing.** `risk-nexus`. This repo says what is + true; that repo says how much it matters and who hears about it. +- **Mechanical CI evidence.** Schema assertions, unit tests and provisioning + checks stay with the repo that owns the code. This repo does not take over + anyone's test suite. +- **Deciding what the security model should be.** NetKingdom's canon says what + good looks like. This repo says whether we have it. +- **Blocking delivery.** In build mode a finding is information. If that + changes it will be a recorded decision, not a habit that accretes. + +## Honest limits on "automated" + +The word in the repo's description is *automated*, and it must not be oversold +— overclaiming is the failure this estate keeps catching in itself. + +External testing practice is consistent that the highest-impact cross-tenant +findings concentrate in access control, business logic and tenant isolation: +precisely the categories that need someone who understands what the data +*means* to recognise that a response is wrong. A response can be well-formed, +200, fast, and contain another tenant's rows. + +So: + +- **Automate the reproducible.** Structural conformance, differential responses + between tenant contexts, credential confinement, resource saturation. This is + most of the surface and it should run without anyone remembering to run it. +- **Flag the irreducible.** Where a finding requires semantic judgement, the + facility's job is to set up the conditions, capture the evidence, and mark it + for review with a date — not to return green. +- **A passing suite is not proof of isolation.** It is proof that the attacks + we thought of did not work. State it that way in every report, because the + gap between those two sentences is where breaches live. + +## The failure this repo must avoid + +**A probe that has only ever passed is not evidence.** `rapp-postgres` verified +its own drift check by deliberately breaking the thing it watched and +confirming the check failed. Every probe here inherits that obligation: it must +be shown to fail against a known-bad fixture before it is trusted against a +real system. + +The second failure is subtler. A probe that starts passing because it was +weakened is worse than a deleted probe, because it reports safety. Probe +changes are therefore reviewed as security changes, and a probe that begins +passing after a change to *itself* rather than to the system is treated as a +finding. + +## Relationship to the rest of the estate + +``` +whitehat-security → risk-nexus → policy-nexus + finds triages publishes + (severity, (permanent, + disclosure) addressable) +``` + +It reads posture claims and canon from the repos that own them, attacks the +running systems those claims describe, and hands what it finds to `risk-nexus`. +It publishes nothing directly and fixes nothing. + +The first work is already specified and waiting: the E2 cross-tenant evidence +artifact, which is Tenancy Posture's highest-severity unowned gap, and the +noisy-neighbour characterisation, which the framework had to reword once +already because its first draft asserted something shared infrastructure cannot +provide. + +## What good looks like + +The estate knows which of its security claims have been tested, when, and by +what attacker model — and can tell the difference between a property that has +been verified and one that has merely never failed. diff --git a/README.md b/README.md index ac4e67a..4b421f4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ # whitehat-security -Automated white hat it-security, pen-testing and isolation-probing. \ No newline at end of file +Automated white hat IT-security, pen-testing and isolation-probing. + +The estate's **adversarial evidence facility**: it attacks our own systems, on +a schedule, to find out whether the security properties they claim are actually +true. + +It exists because a repo testing its own boundary grades its own homework. The +probes most worth having are the ones an author would not think to write. + +- **Independent by construction.** It does not take a declared posture as true, + and it is not owned by the repo whose canon it verifies. +- **It finds; it does not fix.** Findings route to `risk-nexus`, which owns + severity and disclosure. The repo that owns the defect owns the repair. +- **A pass means the attacks we tried did not work** — not that the boundary + holds. Reports say so. + +- Intent: `INTENT.md` +- Workplans: `workplans/` diff --git a/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md b/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md new file mode 100644 index 0000000..1054a30 --- /dev/null +++ b/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md @@ -0,0 +1,189 @@ +--- +id: WHITEHAT-WP-0001 +type: workplan +title: "Produce the adversarial evidence the Tenancy Posture ladders require" +domain: infotech +repo: whitehat-security +status: proposed +owner: the-custodian +topic_slug: whitehat-security +created: "2026-08-17" +updated: "2026-08-17" +--- + +# WHITEHAT-WP-0001 — cross-tenant evidence + +## Goal + +Produce, on a schedule, the adversarial evidence artifacts that NetKingdom's +*Tenancy Posture* standard requires and no repo can honestly produce about +itself — starting with the one the framework calls its highest-severity gap. + +Done means: a service claiming `E2` has been attacked as an adversary holding +its runtime credential, the attempt is recorded with a date and an attacker +model, and a failure routes to `risk-nexus` rather than to a log nobody reads. + +## The forcing case + +*Tenancy Posture* open question 3 has been unowned since the framework was +drafted. What it calls a tenant-boundary failure the security industry calls +**Broken Object Level Authorization** — OWASP API1, top of the API Security Top +10 since that list launched, and the most commonly exploited API vulnerability +in published assessments. + +The estate has no coverage for it. `rapp-postgres` runs fifteen adversarial +probes and every one targets the *consumer* boundary — service versus service. +None targets the tenant boundary *inside* a consumer, which is where the +framework says the residual risk actually lives. + +## Rules of engagement — T01, and nothing else starts first + +An automated facility that probes systems without written scope is +indistinguishable from the threat it models. This is the gating task and it is +not paperwork. + +- **Scope.** Which systems, which namespaces, which credentials. Explicitly: + build-mode environments now; production requires a separate, recorded + authorization. +- **Prohibited actions**, stated as hard rules rather than intentions: + no destructive operations against data the estate did not create for the + test; no exfiltration of real tenant data even as proof of a finding — + a count and a schema shape are proof enough; no denial-of-service against a + shared substrate outside a declared window, because the connection ceiling + means a saturation probe is an outage for every co-resident. +- **Credentials.** The facility holds leased credentials like any workload, + through the sanctioned OpenBao lane. It gets no standing privilege, and + notably **no `BYPASSRLS` and no superuser** — an attacker would not have them + and a probe holding them proves nothing. +- **Attribution.** Every probe connection is identifiable as a probe in + `pg_stat_activity` and in logs, so an operator investigating an anomaly can + tell us from a real adversary in seconds. +- **Abort.** How a run is stopped, by whom, and what state it leaves behind. + +**Output:** `docs/rules-of-engagement.md`, reviewed by the operator personally. +This is exactly the class of thing `risk-nexus`'s escalation duty exists for. + +## Tasks + +### T01 — Rules of engagement +As above. Gates everything. + +### T02 — The attacker model per axis + +What the adversary is assumed to hold, so a probe is judged against a threat +rather than against taste. Drawn from *Tenancy Posture* §4.3, which already +distinguishes them: + +| Axis | Adversary holds | Probe answers | +|---|---|---| +| E1/E2 | A legitimate runtime credential and the ability to make ordinary requests as tenant A | Can it reach tenant B's rows? | +| E3 | The above, plus SQL execution on the connection | Can it re-`SET` the tenant GUC and read across? | +| E4 | A leaked per-tenant credential | Can it address another tenant's substrate at all? | +| P1/P2 | A co-resident consumer behaving badly within its own allowance | What degradation do neighbours experience? | +| R | A copy of a backup taken before an erasure | Is the erased data still readable? | + +**Output:** `docs/attacker-model.md`. Note that the E3 row exists because the +framework corrected itself: E3 stops accident, not compromise, and a probe that +only tested accident would report a strength E3 does not have. + +### T03 — Differential cross-tenant harness (the E2 artifact) + +The core technique: run the same request as two tenants and compare. + +- Provision two disposable tenants against a target service. +- Exercise its surface as tenant A; attempt every object identifier observed + from tenant B's context. +- Assert: B receives a denial or an empty result. **A well-formed 200 + containing A's data is the finding**, and the harness must be built to notice + that rather than to notice errors — both of this estate's real regressions + produced ordinary-looking responses, a 403 and a 404, and nothing alerted. +- Capture evidence as a count and a schema shape, never as tenant data (T01). + +**Acceptance:** run against `tenant-engine` and `audit-core`, both of which +currently claim `E2`. The artifact is the run record, not a green tick. + +### T04 — Prove the probes fail + +A probe that has only ever passed is not evidence. + +Build known-bad fixtures — a service with a deliberately missing tenant +predicate — and confirm each probe fails against them. `rapp-postgres` verified +its drift check this way, by re-pinning to a bad digest and confirming exit 1; +the same discipline applies here and is not optional. + +**Acceptance:** every probe in T03 demonstrated failing before any of them is +trusted passing. + +### T05 — RLS conformance under attack (the E3 artifact) + +`rapp-postgres` ADR-0003 supplies an `rls_conformance` view and a template, and +states plainly that the platform's guarantee is **detection, not prevention** — +a table created by a later migration ships without a policy until something +notices. This repo is that something. + +- Query the conformance view on a cadence; any row is a finding. +- Attack what the view cannot see: a session that sets no GUC must read + nothing; a session with another tenant's value must see nothing; an insert + attributed to another tenant must be refused. +- Attempt the documented bypasses: a `SECURITY DEFINER` function owned by the + table owner, and a role holding `BYPASSRLS`. + +**Cadence is the deliverable here, not a detail.** For a detection-based +control the interval between runs *is* the exposure window, and ADR-0003 leaves +the number to this repo. Set it, and state the resulting window in the record. + +### T06 — Noisy-neighbour characterisation (the P1/P2 artifact) + +The framework had to reword this artifact once already: its first draft +required proof that a saturating consumer "does not breach" another's +allowance, which shared infrastructure cannot provide. + +What is achievable and therefore what this produces: a recorded baseline of +per-consumer resource usage; a run in which one consumer saturates its declared +allowance; evidence that the governance controls **bind**; and the degradation +co-residents experience, **measured and written down** rather than asserted +acceptable. + +Runs inside a declared window per T01 — on a single-node rail with a six- +consumer connection ceiling, a saturation probe is an outage if run carelessly. + +### T07 — Reporting into risk-nexus + +Findings leave this repo in one direction. A run produces: what was attempted, +under which attacker model, when, against which posture claim, and the outcome. +It carries no severity — that is `risk-nexus`'s. + +A **passing** run is also reported. "The attacks we thought of did not work" is +the honest claim, and recording it dated is what lets anyone see how stale the +assurance has become. + +## Sequencing + +T01 gates all. T02 shapes T03/T05/T06. T04 gates trusting any of them. T07 can +follow T03. + +## Risks + +**The facility becomes the threat.** Mitigated by T01, and by holding no +standing privilege. + +**Probes weaken silently.** A probe that starts passing after a change to +itself rather than to the system is a finding, not a fix. Probe changes are +reviewed as security changes. + +**Green is mistaken for safe.** Every report states that a pass means the +attacks attempted did not work, not that the boundary holds. + +**It drifts into fixing things.** The boundary in INTENT is load-bearing: +findings route out, work does not come in. + +## Open questions + +1. **Owner confirmation.** Proposed `the-custodian`, deliberately not + NetKingdom, whose framework this verifies. Needs the operator's yes. +2. **Where probes run from.** In-cluster gives realistic network position; + outside gives independence from the substrate under test. Probably both, + eventually; pick one to start. +3. **Does a service get told it is being probed?** Announced runs are easier to + operate; unannounced ones test the alerting too. Build mode probably + announced, production probably not — which is itself a T01 decision.