| 1 | # SEVERITY-GATE - The Hard Gate |
| 2 | |
| 3 | This is the gate every candidate must pass before it can become a finding report. It encodes Zion's |
| 4 | P1 "holy-shit" impact taxonomy as **eight impact families**. For each family it states the **exact |
| 5 | oracle** that counts as PROVEN, and the **v1 overclaim traps** that do NOT count. |
| 6 | |
| 7 | **The binding rule:** |
| 8 | |
| 9 | > Cyclops MUST NOT open a finding report unless the candidate is on a *demonstrable path* to one of |
| 10 | > the eight families below, **and** the family's oracle has fired (per [[OPERATING-CORE#3-proof-labels-the-four-states-a-candidate-can-be-in|proof labels]]). |
| 11 | > Anything below this bar - hardening notes, missing headers, blind reachability, info leaks without |
| 12 | > impact - goes to the [[../04-cleared-axes/README|cleared-axes log]], **never** to a report. |
| 13 | |
| 14 | "Demonstrable path" means: at SOURCE_CONFIRMED time you can name the endpoint of the chain and it is |
| 15 | one of these families. If the best honest endpoint is "attacker can read an object they shouldn't" - |
| 16 | that is not on this list, and you stop and log it. |
| 17 | |
| 18 | --- |
| 19 | |
| 20 | ## Family 1 - Remote Code Execution (the crown) |
| 21 | |
| 22 | Covers: Unauth RCE, Pre-Auth RCE, Wormable RCE, RCE via Deserialization / File-Upload / |
| 23 | Template-Injection (SSTI) / Command-Injection / SQLi-to-RCE, Reverse Shell, Web Shell. |
| 24 | |
| 25 | **PROVEN (oracle):** code we chose runs in the target process. The artifact is one of: |
| 26 | - A callback to our listener (`curl`/DNS/TCP) from the target host, fired by our payload. |
| 27 | - `id` / `uname -a` / `hostname` output returned through the exploit channel. |
| 28 | - A file we wrote to the target FS from inside the target process (`/tmp/cyclops-$RAND`). |
| 29 | - For wormable: the above **plus** a demonstrated propagation primitive (the same payload reaches the |
| 30 | next host with no new auth). |
| 31 | |
| 32 | **Does NOT count (v1 traps):** |
| 33 | - "The deserializer accepts our object" - until a gadget chain actually executes, this is |
| 34 | SOURCE_CONFIRMED at best. *Reaching* `pickle.loads` / `yaml.load` / `ObjectInputStream` is a |
| 35 | hypothesis; a popped shell is the finding. |
| 36 | - "SSTI: `{{7*7}}` reflected `49`" - that is template *evaluation*, not yet RCE. The finding is |
| 37 | `{{ cycler.__init__.__globals__.os.popen('id') }}` (or the engine's equivalent gadget) returning |
| 38 | command output. Expression eval alone goes no further than SOURCE_CONFIRMED. |
| 39 | - "The upload endpoint accepts a `.php`/`.jsp`" - until the shell is *reached and executes*, you have |
| 40 | an upload, not RCE. Prove the webroot path is served and the handler runs. |
| 41 | - "Command string is built from user input" - concatenation is a hypothesis; you need the injected |
| 42 | command's *output* or callback. |
| 43 | |
| 44 | --- |
| 45 | |
| 46 | ## Family 2 - Container / VM / Sandbox / Hypervisor Escape & Orchestrator Takeover |
| 47 | |
| 48 | Covers: container breakout, VM/sandbox/hypervisor escape, Kubernetes / Docker-socket takeover, |
| 49 | kernel / bootloader / firmware compromise. |
| 50 | |
| 51 | **PROVEN (oracle):** |
| 52 | - **Container/sandbox escape:** a read or write on the **host** filesystem or host process namespace |
| 53 | from inside the container (e.g. our canary file at `/host-canary` created from the container; a |
| 54 | process visible in the host PID namespace; reading the host's `/etc/shadow` via an escaped mount). |
| 55 | - **Docker-socket / K8s takeover:** scheduling a pod/container we control on a node we should not |
| 56 | control, proven by command output from that node, **or** reading a cluster secret |
| 57 | (`/var/run/secrets/kubernetes.io/serviceaccount/token`) that grants cross-namespace action we then |
| 58 | demonstrate. |
| 59 | - **Hypervisor escape:** guest-to-host code execution proven by an artifact on the host. |
| 60 | - **Kernel/firmware:** a controlled kernel crash with attacker-controlled fault (ASan/KASAN/oops with |
| 61 | our pattern in RIP/registers), or a privilege transition to ring-0 capability demonstrated. |
| 62 | |
| 63 | **Does NOT count:** |
| 64 | - "We are root *inside* the container." That is the starting position, not an escape. |
| 65 | - "The container has `CAP_SYS_ADMIN` / a mounted socket." That is a misconfig *observation*; the |
| 66 | finding is the demonstrated breakout using it. |
| 67 | - "Node-to-node lateral movement is theoretically possible." Demonstrate the second node or it is |
| 68 | SOURCE_CONFIRMED. |
| 69 | |
| 70 | --- |
| 71 | |
| 72 | ## Family 3 - Cloud / Identity Account Takeover & Privilege Escalation |
| 73 | |
| 74 | Covers: cloud (AWS/Azure/GCP) account takeover, IAM priv-esc to admin, cross-tenant / tenant-isolation |
| 75 | break, SSO/SAML/OAuth bypass + ATO. |
| 76 | |
| 77 | **PROVEN (oracle):** |
| 78 | - **Cloud ATO:** credentials (access key, session token, service-account JSON) retrieved **and** used |
| 79 | to perform an action we were not entitled to, proven by the API response (e.g. |
| 80 | `sts:GetCallerIdentity` returning a role we hijacked, then a `s3:ListBucket` on a bucket we should |
| 81 | not see - against *our own* canary resources where possible). |
| 82 | - **IAM priv-esc:** starting principal A, end as admin/role B, proven by an action only B can take. |
| 83 | - **Cross-tenant break:** reading or mutating **tenant B's** data while authenticated as **tenant A** |
| 84 | - with both tenants being *our own* planted accounts holding canary data. The artifact is tenant |
| 85 | A's session returning tenant B's canary. |
| 86 | - **SSO/SAML/OAuth bypass:** a forged/relayed assertion or bypassed check that logs us in as a |
| 87 | *different* user (our second canary user), proven by that user's authenticated session. |
| 88 | |
| 89 | **Does NOT count (the biggest v1 trap):** |
| 90 | - **"Injection reachable" ≠ "ATO completes."** A JWT with `alg:none` *accepted* by a debug endpoint |
| 91 | is not ATO until you forge a token for a *privileged* user and perform a privileged action with it. |
| 92 | - "The OAuth `redirect_uri` allows an open redirect" - that is a redirect; ATO needs the *token/code* |
| 93 | captured and exchanged for a session. |
| 94 | - "IDOR lets me read another user's profile" - cross-object read of low-value data is **not** a |
| 95 | tenant-isolation break unless it crosses a tenant trust boundary into material data. Most IDOR is |
| 96 | cleared-axes, not a finding. (This is the exact class v1 over-reported.) |
| 97 | - "SAML response is not signed-checked in this code branch" - prove the forged assertion yields a |
| 98 | session; a missing check that some upstream guard catches is DISPROVEN. |
| 99 | |
| 100 | --- |
| 101 | |
| 102 | ## Family 4 - Secret / Key / Repo / Build-Pipeline Compromise |
| 103 | |
| 104 | Covers: JWT signing key / API key / private key / production secret / CI-CD secret compromise; |
| 105 | source-repo / GitHub-org / Actions-runner / build-pipeline takeover. |
| 106 | |
| 107 | **PROVEN (oracle):** |
| 108 | - **Secret exfil:** the actual secret value retrieved through the exploit channel (the signing key |
| 109 | bytes, the live API key, the `.env` contents) - verified live where safe by a *minimal* benign use |
| 110 | (e.g. the API key returns `200` on a `whoami`-style endpoint; the JWT key validates a token we sign). |
| 111 | - **Repo/org takeover:** a write we land on a repo/org we should not control - a commit/branch/tag we |
| 112 | pushed to a protected target (against *our own* canary repo for replica proof), or a workflow we |
| 113 | caused to run with elevated `GITHUB_TOKEN` permissions. |
| 114 | - **Actions-runner / pipeline takeover:** code execution on the runner proven by an artifact (callback |
| 115 | from inside the CI job, or exfil of `secrets.*` / the `GITHUB_TOKEN` to our listener). See |
| 116 | [[../01-archetypes/cicd-supplychain]]. |
| 117 | |
| 118 | **Does NOT count:** |
| 119 | - "This key *looks* like a secret in the JS bundle" - a string that matches a regex is a hypothesis. |
| 120 | Prove it is live and grants access (minimal benign call), or it is cleared-axes. |
| 121 | - "The workflow uses `pull_request_target`" - that is a *risky pattern*; the finding is the actual |
| 122 | secret/token exfil it enables. Prove the injection executes in the privileged context. |
| 123 | - "An expired / revoked / sandboxed test key." Dead keys are not findings. |
| 124 | |
| 125 | --- |
| 126 | |
| 127 | ## Family 5 - Supply-Chain & Dependency Compromise |
| 128 | |
| 129 | Covers: supply-chain / dependency-confusion / registry / artifact / signing-key compromise. |
| 130 | |
| 131 | **PROVEN (oracle):** |
| 132 | - **Dependency confusion:** a package *we* published to a public registry under an internal name is |
| 133 | pulled and **executed** by the target's build/install (callback from the install/postinstall step, |
| 134 | proven against our own replica build). The artifact is the callback, not the namespace being claimable. |
| 135 | - **Artifact/registry poisoning:** a malicious artifact we placed is consumed and run downstream, |
| 136 | proven by execution in the consumer. |
| 137 | - **Signing-key compromise:** the signing key retrieved (Family 4 oracle) **and** a malicious artifact |
| 138 | signed-and-accepted by a verifier we don't control. |
| 139 | |
| 140 | **Does NOT count:** |
| 141 | - "This internal package name is unclaimed on npm/PyPI." Claimability is SOURCE_CONFIRMED. Publishing |
| 142 | a benign canary package and observing the *target's pipeline pull and run it* is the finding - and |
| 143 | only against in-scope / owned pipelines. **Never** squat a name a non-consenting third party builds. |
| 144 | - "The lockfile has a typo-squat-adjacent name." Adjacency is not consumption. |
| 145 | |
| 146 | --- |
| 147 | |
| 148 | ## Family 6 - Mass Data Exfil & Financial Impact |
| 149 | |
| 150 | Covers: mass PII / DB / payment-data exfil; financial theft / payment bypass / balance manipulation; |
| 151 | ransomware / data-destruction paths. |
| 152 | |
| 153 | **PROVEN (oracle):** |
| 154 | - **Mass exfil:** a query/endpoint returning bulk records across the authorization boundary, proven on |
| 155 | a replica seeded with canary PII - the artifact is the bulk canary set returned to an unprivileged |
| 156 | principal. (Live: prove the primitive with a `COUNT(*)` or a single canary row, never bulk-pull real |
| 157 | user data.) |
| 158 | - **Financial:** a state transition that moves value - balance increased, a payment accepted at the |
| 159 | wrong price, a refund issued without debit - proven by the post-state on a replica/canary account. |
| 160 | - **Data destruction/ransomware path:** a demonstrated unauthorized write/delete primitive on data we |
| 161 | planted, with the destructive step proven on a canary only. |
| 162 | |
| 163 | **Does NOT count:** |
| 164 | - "The endpoint *could* return many rows if the `WHERE` were dropped." Demonstrate it. |
| 165 | - "**Read DoS** / a slow query / a large response." A denial-of-service or resource-exhaustion read is |
| 166 | **not** RCE and not exfil. (Explicit v1 trap: do not dress up a DoS as a critical.) |
| 167 | - "The price is set client-side." Client-side observation is a hypothesis; prove the server accepts |
| 168 | the manipulated value and the order/state actually changes. |
| 169 | - "I can see *one* of my own orders." Not mass, not cross-boundary. |
| 170 | |
| 171 | --- |
| 172 | |
| 173 | ## Family 7 - SSRF / XXE / File-Access Chains to Critical |
| 174 | |
| 175 | Covers: SSRF→metadata / →internal-admin / →RCE; XXE→file-read; arbitrary file read of secrets; |
| 176 | arbitrary file write→RCE; path-traversal→secrets. |
| 177 | |
| 178 | **PROVEN (oracle):** |
| 179 | - **SSRF:** must terminate in impact, not just an outbound request. PROVEN = the metadata credential |
| 180 | retrieved (`169.254.169.254/latest/...` token *captured*), **or** an internal-admin action performed |
| 181 | via the SSRF, **or** SSRF→RCE chained to a Family-1 artifact. A bare OOB callback proves *blind |
| 182 | SSRF reachability* - that is SOURCE_CONFIRMED, not a finding on its own (see trap below). |
| 183 | - **XXE / arbitrary file read:** the *contents* of a real secret file retrieved through the channel - |
| 184 | our canary at `/etc/cyclops-canary` for replica proof, or a genuinely sensitive file |
| 185 | (`/etc/passwd` is a demo, a signing key / `.env` is the impact) on a permitted target. |
| 186 | - **Arbitrary file write→RCE:** the write lands somewhere that executes (cron, webroot, a `.so`/ |
| 187 | `.pth`/startup path) and the execution fires a Family-1 artifact. The write alone is not the finding; |
| 188 | the resulting execution is. |
| 189 | - **Path-traversal:** the traversal returns secret contents, per the file-read oracle. |
| 190 | |
| 191 | **Does NOT count (the canonical v1 trap):** |
| 192 | - **"Blind SSRF confirmed by a DNS callback"** with nothing behind it. Per Zion's standing rule, never |
| 193 | submit enumeration / blind SSRF standalone - prove the full impact chain (metadata cred, internal |
| 194 | admin, or RCE). Blind callback → cleared-axes or keep-pulling, not a report. |
| 195 | - "Traversal reads `/etc/passwd`." `/etc/passwd` is a *demonstration of the primitive*, not impact by |
| 196 | itself on a modern box. Pull a real secret to cross into a finding. |
| 197 | - "File write to a `/tmp` no one executes." A write with no execution sink is SOURCE_CONFIRMED. |
| 198 | |
| 199 | --- |
| 200 | |
| 201 | ## Family 8 - Exposed Critical Service & Trust-Infrastructure Compromise |
| 202 | |
| 203 | Covers: the "exposed service" family - Jupyter / Redis / Mongo / Elasticsearch / Jenkins / Grafana / |
| 204 | K8s-dashboard / Prometheus / Terraform-state / env-var exposure; **and** trust-infra compromise: |
| 205 | MFA / password-reset / session-hijack / WAF bypass to a critical action; AD / domain-admin / |
| 206 | golden-ticket / NTLM-relay; DNS / registrar / subdomain / email / BGP / CA / TLS-key compromise. |
| 207 | |
| 208 | **PROVEN (oracle):** |
| 209 | - **Exposed service:** not "the port is open" - the **unauthed privileged capability exercised**. For |
| 210 | Jupyter, a notebook executing `os.system` (Family-1 artifact). For Redis, an unauthed write we then |
| 211 | turn into RCE (module load / cron / authorized_keys) or a secret read. For Terraform-state, the |
| 212 | *secrets inside the state* retrieved. For an exposed `.env`, the secret retrieved **and** minimally |
| 213 | validated live. The artifact is the capability, not the banner. |
| 214 | - **Trust-infra:** the takeover demonstrated - a subdomain we now serve content on (dangling |
| 215 | CNAME claimed against *our own* canary), a password-reset flow yielding a *different* user's session |
| 216 | (our second canary user), an NTLM relay landing an authenticated action as the relayed principal, |
| 217 | a golden ticket authenticating as a domain principal in our lab forest. |
| 218 | |
| 219 | **Does NOT count:** |
| 220 | - "Redis/Mongo/ES is reachable without auth." Exposure is the start. The finding is the data read or |
| 221 | the RCE landed. (Many "exposed service" reports are really just a port scan result - that is |
| 222 | cleared-axes until the capability fires.) |
| 223 | - "Grafana is on the internet." Until you prove the unauthed read of a datasource secret or an |
| 224 | exploit, it is an observation. |
| 225 | - "The reset token is predictable-looking." Predict it and take over the canary account, or it is |
| 226 | SOURCE_CONFIRMED. |
| 227 | - "A WAF can be bypassed." A bypass that reaches a *non-impactful* endpoint is nothing; the bypass |
| 228 | must reach a critical action that then fires another family's oracle. |
| 229 | |
| 230 | --- |
| 231 | |
| 232 | ## Gate decision procedure (run this on every candidate) |
| 233 | |
| 234 | 1. **Name the endpoint.** Honestly, what is the *best* impact at the end of this chain? Write it down. |
| 235 | 2. **Map it to a family.** Is that endpoint one of Families 1-8? If no → [[../04-cleared-axes/README|cleared-axes]], stop. |
| 236 | 3. **Identify the oracle.** What single artifact would prove it? If you cannot name an artifact, the |
| 237 | candidate is not yet SOURCE_CONFIRMED - it is a guess. |
| 238 | 4. **Check it is novel, not a sibling-CVE.** Run the [[../06-methods/NOVELTY-DOCTRINE|novelty check]] |
| 239 | *before* deep work. |
| 240 | 5. **Drive the oracle on a replica.** Artifact → REPLICA_CONFIRMED. No artifact → DISPROVEN + log. |
| 241 | 6. **Only now** may a report exist. And only if in scope may a LIVE_CANARY_CONFIRMED follow. |
| 242 | |
| 243 | > If you find yourself writing a report whose impact section says "could", "may", "potentially", |
| 244 | > "an attacker might", or "this is likely exploitable" - you have failed the gate. Delete it and go |
| 245 | > get the artifact, or log it DISPROVEN. |