- name
- triage-validation
- description
- Finding validation before writing any report — 7-Question Gate (all 7 questions), 4 pre-submission gates, always-rejected list, conditionally valid with chain table, CVSS 3.1 quick reference, severity decision guide, report title formula, 60-second pre-submit checklist. Use BEFORE writing any report. One wrong answer = kill the finding and move on. Saves N/A ratio.
# TRIAGE & VALIDATION
One wrong answer = STOP. Kill it. Move on.
> "N/A hurts your validity ratio. Informative is neutral. Only submit what passes all 7 questions."
---
## THE 7-QUESTION GATE
Ask IN ORDER. One wrong answer = STOP immediately.
---
### Q1: Can an attacker use this RIGHT NOW, step by step?
Complete this template:
```
1. Setup: I need [own account / another user's ID / no account]
2. Request: [exact HTTP method, URL, headers, body — copy-paste ready]
3. Result: I can [read / modify / delete] [exact data shown in response]
4. Impact: The real-world consequence is [account takeover / PII read / money stolen]
5. Cost: Time: [X minutes], Capital: [$0 / $X subscription required]
```
**If you CANNOT write step 2 as a real HTTP request → KILL IT.**
#### Capability-URL / GUID-only objects (anonymous carts, guest orders, share tokens)
When the object ID is a **UUID** and unauth R/W is proven (`credentials: omit`):
| Claim | Gate |
|-------|------|
| Unauth GET leaks guest email / cart contents by GUID | Survives Q1 if HTTP 200 + body proof |
| Unauth overwrite delivery address / DELETE cart | Survives — state change without session binding |
| Unauth POST paymentdetails 201 + GET paymentInfo (masked PAN/holder/expiry) | Survives — **same root cause** as cart GUID BAC; **merge** into one report, do not open a second ticket |
| Unauth `POST .../orders?cartId={guid}` returns **400 validation** (T&C/slot/address), never 401/403 | Survives as **supporting severity note only** — proves missing authz on placeOrder surface |
| Free order / paid goods without payment / T&C bypass | **Kill** unless placeOrder (or payment capture) succeeds end-to-end without victim payment — SPA `termsChecked` query often still server-rejected |
| "Mass customer dump" / "anyone's cart" | **Kill unless** GUID enumerable or you prove a leak primitive |
| Analytics/RUM (e.g. Datadog) sees cart GUID in browser → vendor | Secondary narrative only — **kill standalone** without proof third parties/attackers can read that data |
| Severity High vs Medium | **Default ship Medium** when UUID-only + single cart + no free order + no full PAN. High only if program critical scenarios (mass PII / free product) are met OR policy explicitly pays High for checkout BOLA. Optimistic self-score 7.5 AC:H often gets triaged to Medium — document entropy, do not claim brute-force |
| Hybris/OCC "guest cart by design" | Soft kill risk (Informative/N/A). Survive by centering **state-changing** ops (delivery overwrite, DELETE, paymentdetails) not mere read-by-token |
| Video PoC mandatory on form | **Process FAIL** until video attached — technical HTTP proof ≠ form Yes |
**Adversarial self-review (assume report wrong)** before submit for this class:
1. Kill free-order / full PAN / mass dump / UUID brute claims
2. Prefer **Medium** framing over High if Likelihood = secondary GUID disclosure
3. Prove **two contexts** (victim email ≠ attacker; empty cookie jar) — own-cart PoC dies on Q8
4. Drop OOS token-leak-to-third-party (RUM) as primary impact
5. One multi-brand report only
**Do not kill** solely because "attacker must know the GUID." Capability URL without binding cookie/HMAC is still broken object-level authorization. **Do kill** inflated impact that implies brute-forcing UUID v4, free-order without proof, or full PAN when only masked.
**Shared OCC multi-brand** (same Hybris stack, different baseSite): one finding, list all in-scope hosts; isolation of GUIDs across brands is expected, not a separate positive finding.
**Submit priority:** if user says **submit dulu**, freeze further T&C/register/XSS chains and package the validated GUID BAC first (see `report-writing` Intigriti submit package + `references/capability-url-cart-bac-intigriti.md`). After user says **submitted Medium** + **skip to other bounty** → cancel residual todos on that target; do not re-open High debate.
---
### Q2: Is the impact on the program's accepted impact list?
Go to the program page. Find "Vulnerability Types" or "Out of Scope."
Common tiers:
- **Critical**: Any-user ATO without interaction, RCE, SQLi with data exfil, admin auth bypass
- **High**: Mass PII exfil, privilege escalation, internal SSRF with data, stored XSS all users
- **Medium**: IDOR on specific user non-critical data, XSS on sensitive page requiring click
- **Low**: Non-sensitive info disclosure, clickjacking with PoC
**If your bug maps to a listed exclusion → KILL IT.**
#### Network-perspective programs (L1 / validator / "network safety")
Some programs score **Risk = Impact × Likelihood** with **Impact from a network perspective**. High/Critical need **network-wide** effects (halt, module theft, wrong rewards for all, full network control). Issues affecting **only one participant/node** usually **cap at Low or Medium**.
| Claim | Typical max under network bar |
|-------|-------------------------------|
| Unauth admin on one join node / secret leak on one host | **Medium** |
| Forge **this node's** vote / first-write-wins single validator | **Medium** |
| DNS SSRF requiring selected executor | **Medium** (High only if proven sensitive network-wide effect) |
| Wrong rewards for all / chain halt | High/Critical |
Do **not** promote single-node full compromise to High just because secrets + re-sign are scary. Read the program formula first (see `report-writing/references/network-perspective-severity.md`).
---
### Q3: Is the root cause in an in-scope asset?
Confirm:
- Vulnerable domain is on the in-scope list (not `*.internal.target.com`)
- It's a production asset (not staging/dev unless explicitly in scope)
- It's not a third-party service the company just uses (not Stripe, Salesforce, Google Auth)
**If out-of-scope → KILL IT.**
---
### Q4: Does it require privileged access that an attacker can't realistically get?
- "Admin can do X" = centralization risk = **KILL IT** (on 99% of programs)
- "Non-admin can do X that only admin should do" = valid
- "Requires physical access / MFA device" = usually invalid
- "Requires compromised victim account to work" = questionable, low severity at best
---
### Q5: Is this already known or accepted behavior?
Search:
1. Program's HackerOne/Bugcrowd disclosed reports: Ctrl+F endpoint name + bug class
2. GitHub issues on target repo: `is:issue label:security ENDPOINT_NAME`
3. Changelog/CHANGELOG.md — does it mention this behavior?
4. API docs / design docs — is it documented as intended?
**If acknowledged/design decision → KILL IT.**
---
### Q6: Can you prove impact beyond "technically possible"?
- XSS → show actual cookie theft or session hijack, not just `alert(1)` or `alert(document.domain)`
- SSRF → hit an internal endpoint that returns data, not just DNS ping
- SQLi → show actual data exfil from a real table, not just error message
- IDOR → show actual other-user's data in response, not just a 200 status code
**If you can only show "technically possible" → DOWNGRADE severity, not kill.**
---
### Q7: Is this a known-invalid bug class?
Check the NEVER SUBMIT list below. If it's on this list without a chain → **KILL IT.**
---
### Q8: Identity check — which session found this, and does it survive?
For any finding made under an authenticated hunt, record the answer to each:
```
1. Session ID: [12-char BBHUNT_SESSION_ID hash from audit.jsonl]
2. Identity: [low-priv user A / high-priv user B / API key / etc.]
3. Anonymous repro: Does the same request work with NO auth header?
4. Cross-identity: Does it work under session B with the same data scope?
5. Stale-cred repro: Does a logged-out / expired session still get the data?
```
Why this matters:
- **IDOR / BOLA**: must work with session A reading session B's data — if it
only works with no auth, that's "missing auth" not IDOR (different bug,
different severity).
- **Priv-esc**: must work with low-priv session reading high-priv data — if
both sessions can already see it, no bug.
- **Auth bypass**: must work *without* a valid session — if it stops working
when you log out, you've found a permissions issue, not a bypass.
- **Always check both directions**: a finding that only reproduces under
one identity is often a real, scoped permission boundary, not a vuln.
`audit.jsonl` entries are tagged with `session_id`. Re-run the request
under each identity and confirm the bug holds before writing the report.
This is the most common reason "confirmed IDOR" findings come back as N/A.
If you cannot answer the identity questions, treat the finding as unproven.
Blank answers auto-fail on auth-related findings.
---
### Q9: Deployment-Time / Setup-Only Window Check
If a vulnerability relies on a race condition or access-control bypass that occurs **strictly during the deployment or setup phase** of a contract/system (e.g. frontrunning initializers on a contract deployed without an atomic factory):
```
1. Live Status: Is the contract already initialized on-chain?
2. Post-Setup: Does the vulnerability persist after setup is complete?
3. Attacker Action: Does the exploit require timing a setup transaction?
```
- **If the contract/system is already initialized/configured on-chain:** The live impact is zero. An attacker cannot exploit it on the running target.
- **If the vulnerability is setup-only and has closed:** It is a **Low/Informational** design defect. It does not warrant a High/Critical submission.
- **Decision Rule:** Kill the finding for the active target if the setup window has already closed. Document only as informational code quality feedback.
---
---
## 4 PRE-SUBMISSION GATES
Run in sequence. ALL 4 must PASS.
### Gate 0: Reality Check (30 seconds)
```
[ ] Bug is REAL — confirmed with actual HTTP requests, not code reading alone
[ ] Bug is IN SCOPE — checked program scope page explicitly
[ ] Reproducible from scratch — can reproduce starting from fresh session
[ ] Evidence ready — screenshot, response body, or video
```
### Gate 1: Impact Validation (2 minutes)
```
[ ] Can answer: "What can attacker DO that they couldn't before?"
[ ] Answer is more than "see non-sensitive data" (unless program pays for info disclosure)
[ ] Real victim: another user's data, company's data, financial loss
[ ] Not relying on victim doing something unlikely
```
### Gate 2: Deduplication Check (5 minutes)
```
[ ] Searched HackerOne Hacktivity for this program + similar bug title/endpoint
[ ] Searched GitHub issues for target repo
[ ] Read most recent 5 disclosed reports for this program
[ ] Not a "known issue" in their changelog or public docs
[ ] Google: "TARGET_NAME ENDPOINT_NAME bug bounty"
```
### Gate 3: Report Quality (10 minutes)
```
[ ] Title: [Bug Class] in [Endpoint] allows [actor] to [impact]
[ ] Steps to Reproduce: copy-pasteable HTTP request
[ ] Evidence: screenshot/video of actual impact (not just 200 status)
[ ] Severity: matches CVSS 3.1 score AND program's severity definitions
[ ] Remediation: 1-2 sentences of concrete fix
[ ] NEVER used "could potentially" or "may allow"
```
---
## NEVER SUBMIT LIST
Submitting these destroys your validity ratio.
```
Missing CSP / HSTS / security headers
Missing SPF / DKIM / DMARC
GraphQL introspection alone (no auth bypass, no IDOR demonstrated)
Banner / version disclosure without working CVE exploit
Clickjacking on non-sensitive pages (no sensitive action PoC)
Tabnabbing
CSV injection (no actual code execution shown)
CORS wildcard (*) without credential exfil proof of concept
Logout CSRF
Self-XSS (only exploits own account)
Open redirect alone (no ATO or OAuth theft chain)
OAuth client_secret in mobile app (known, expected)
SSRF DNS callback only (no internal service access or data)
Host header injection alone (no password reset poisoning PoC)
Rate limit on non-critical forms (search, contact, login with Cloudflare)
Session not invalidated on logout
Concurrent sessions
Internal IP in error message
Mixed content
SSL weak ciphers
Missing HttpOnly / Secure cookie flags alone
Broken external links
Autocomplete on password fields
Pre-account takeover (usually — very specific conditions required)
Web3: "Open RPC proxy" on testnet endpoints (standard dApp architecture)
Web3: VUE_APP_*/NEXT_PUBLIC_*/REACT_APP_* env vars in JS bundle (client config by design)
Web3: CORS * on API with token-based auth (no credential forwarding possible)
Web3: WalletConnect Project ID "leaked" in frontend (public identifier by design)
Web3: "No rate limiting on RPC" when upstream provider handles limits
Web3: "Missing Authentication Token" on AWS API Gateway (means route not found, NOT auth missing)
```
---
## COMMON N/A CLASSES — KILL SIGNALS
These pass basic gut-check but consistently come back N/A. Each row has a **specific signal** that tells you to kill it *before* writing the report.
| Finding | Why it N/As | Kill signal — if you see this, stop |
|---|---|---|
| Reflected XSS | CSP blocks execution; sandbox context; no session access | Dalfox found `alert(1)` but no cookie in response; `Content-Security-Policy` header present |
| SSRF — DNS callback only | No internal data reached; programs require HTTP response with data | Interactsh/Collaborator got DNS ping but no HTTP reply with internal content |
| IDOR — own data only | Attacker == victim; no cross-account access proven | User ID in response matches your own test account |
| SQLi — error message only | WAF filtered or error is cosmetic; no data exfiltrated | Got DB error string but no actual table rows returned |
| CORS wildcard `*` | `*` blocks `withCredentials`; no PII actually exfiltrated | `Access-Control-Allow-Credentials: true` absent; credentialed request returns 403 |
| Web3 dApp "open RPC endpoint" | Testnet nodes are free+public; SPA must talk to node; upstream rate-limits exist | Endpoint is `/testnet/*`; provider on Free tier; same pattern as Uniswap/Aave |
| Web3 dApp "env vars in JS bundle" | `VUE_APP_*`/`NEXT_PUBLIC_*` are CLIENT config by framework design | No actual secret (no API key w/ write access, no private key); values publicly available elsewhere |
| Web3 dApp "CORS * on API" | Token-based auth (Bearer header) means no cross-origin credential theft | Auth is `Authorization: Bearer`; no `Allow-Credentials: true`; SPA needs CORS to function |
| Rate limit missing — non-sensitive endpoint | Program only pays for rate-limit on auth/payment/OTP surfaces | Endpoint handles search, contact form, or sits behind Cloudflare |
| Nuclei `info` template match | Version detection, not exploitation | Template severity is `info`; no CVE PoC executed against live service |
| MFA rate limit (no lockout) | Impact depends on OTP brute-force succeeding — it usually doesn't | 15 requests returned 200 but no OTP code was accepted |
| Open redirect alone | Redirect is informational without token theft chain | No OAuth `redirect_uri` parameter; no auth code or token in the redirected URL |
| Auth bypass — admin precondition | Requires compromised admin to trigger; attacker can't get there | "Admin can do X on behalf of user" — attacker must already be admin |
| XSS via `alert(document.domain)` | Not proof of session theft | PoC shows domain popup only; no `document.cookie` exfil, no event listener |
| SAML metadata exposed | Disclosure only — aids attack but is not standalone impact | No private key or signing cert extracted; metadata is publicly documented by IdP |
**Decision rule:** if your finding matches a kill signal → classify as `[INFORMATIONAL]`, do **not** run `/validate`, move on.
---
### SSRF — constrained loopback proxy / request-ID routing
A URL construction defect can look like SSRF even when the attacker controls only a numeric backend port and a path-like request ID. Validate the **actual request capability**, not string interpolation alone:
1. Identify the fixed host, attacker-controlled components (port/path/query/method/body/headers), and any server-side allowlist of active backends.
2. Test parser/normalization behavior separately: a framework `:path` parameter plus encoded `../` may be decoded and normalized by the HTTP client before the upstream fetch. Record the exact final URL observed by a controlled loopback listener.
3. Confirm response semantics: status codes, JSON-only parsing, response reflection, redirect behavior, and whether a non-200 response is discarded.
4. Map namespace boundaries: `127.0.0.1` inside a container is normally that container, not the Docker host; `ipc: host` does not establish host networking. Do not claim host-local access without `network_mode: host` or equivalent proof.
5. Inventory only compatible targets under the fixed path prefix. A GET-only proxy with a fixed prefix is **not** arbitrary SSRF, port scanning, arbitrary HTTP-method access, or cloud-metadata access unless each additional capability is demonstrated.
**Novelty / duplicate gate:** Before packaging a constrained loopback primitive, compare it against every historical finding's root cause, surface, and blast radius. If a prior report already covers unauthenticated exposure of the same MLNode/control-plane surface and SSRF-class callback/proxy behavior, a newly discovered route is supporting evidence or a report update—not a second submission—unless it crosses a distinct authorization boundary or proves materially independent impact.
**Safe wording:** “unauthenticated callers can cause a loopback GET to an attacker-selected port and reflect successful JSON under a fixed path constraint.”
**Never claim without direct evidence:** arbitrary host/network SSRF, Docker-host access, raw-response exfiltration, secret/metadata access, RCE, network-wide impact, or High/Critical severity.
## MIXED-DNS ROUTE-CONFUSION FINDINGS
For VPN/proxy code that resolves one hostname to multiple addresses, trace the full pipeline: resolver order → vector-wide route decision → filtering/partitioning → per-address connection attempts → firewall enforcement. A scalar `any(excluded) => direct` decision is a candidate only if the unchanged mixed vector is later attempted under that decision.
Require production reachability and connector evidence before claiming clearnet exposure. A routing-unit test proves policy collapse, not packets leaving the default interface. When filtering Rust tests, verify output says `running 1 test`; `--exact` with an incomplete module path can return success after running zero tests.
Detailed checklist and worked NymVPN case: `references/mixed-dns-route-confusion.md`.
## CONDITIONALLY VALID — CHAIN REQUIRED
Build the chain first, prove it works end to end, THEN report.
| Standalone Finding | Chain Required | Valid Result |
|---|---|---|
| Open redirect | + OAuth redirect_uri → auth code theft | ATO (Critical) |
| Clickjacking | + sensitive action + working PoC | Medium |
| CORS wildcard | + credentialed request exfils user PII | High |
| CSRF | + sensitive action (transfer funds, change email, delete account) | High |
| Rate limit bypass | + OTP/reset token brute force succeeds | Medium/High |
| SSRF DNS-only | + internal service access + data returned | Medium |
| Host header injection | + password reset email uses injected host | High |
| Prompt injection | + reads other user's data (IDOR) | High |
| S3 bucket listing | + JS bundles contain API keys or OAuth secrets | Medium/High |
| Self-XSS | + CSRF to trigger it on victim without their knowledge | Medium |
| Subdomain takeover | + OAuth redirect_uri registered at that subdomain | Critical |
View on GitHub