# pre-bounty

Map a bug-bounty scope and rank targets by payout, crowding, and freshness. Use to size up a program or decide where to hunt before committing time.

- **Kind:** skill
- **Source:** https://github.com/forefy/.context
- **Page:** https://forefy.com/skills/18c3e623-cf91-4992-a51c-d83815c1147c
- **API (JSON + files):** https://forefy.com/api/asr/18c3e623-cf91-4992-a51c-d83815c1147c

---

## SKILL.md

---
name: pre-bounty
description: Map a bug-bounty scope and rank targets by payout, crowding, and freshness. Use to size up a program or decide where to hunt before committing time.
---

# Pre-bounty: scope recon & target prioritization

## The core thesis

Most hunters converge on whatever is cheapest to start testing. That means the
crowd is an artifact of the **setup barrier**, not of where the bugs are. So the
edge is systematic: rank the scope by

> **opportunity ≈ (payout ceiling × freshness) ÷ crowd**, with **setup difficulty
> acting as a moat** - a hard-to-reproduce environment keeps competitors out, so
> it is a *positive* when paired with a high ceiling.

The whole skill exists to compute that ranking from real program data and show it
in a way the user can act on. A high max-payout asset that is trivial to set up
and already swept (lots of resolved reports) is a *worse* target than a modest
one nobody has tooled up for. Make that legible.

## Inputs this skill accepts

Any of, in order of preference:
- A **program URL** - `hackerone.com/<program>`, `bugcrowd.com/<program>`,
  `app.intigriti.com/...`, `yeswehack.com/...`, or a self-hosted `/security` /
  `security.txt` / VDP page.
- A **pasted scope table or asset list** (domains, mobile apps, repos, APIs).
- A rough **description of API/repo access** the user already has.

If you only get a program name, construct the URL. If the platform page is
JavaScript-rendered (HackerOne, Bugcrowd, Intigriti all are), **use the browser
tools to read it** - `WebFetch` returns an empty shell for these. `read_page` /
`get_page_text` on the policy and scope tabs is the reliable path.

## Workflow

Work the five stages in order. Stages 1–3 are parallelizable - fire the fetches
and searches together.

### 1. Gather the scope (the signal-richest step)
Pull and record, per asset:
- **Asset name, type** (domain / mobile / desktop / API / source / other) and
  whether it is **in or out of scope**.
- **Payout tier** - most programs tag assets into tiers (HackerOne
  Core/Non-core; Bugcrowd P1–P5 targets; others "critical eligible" vs not).
  Capture the **max reward** reachable per asset - this is the ceiling.
- **Resolved-report count / share per asset** if the platform shows it. This is
  your single best **crowd proxy** - an asset with 40% of all resolved reports
  is picked-over; one with <2% is open. HackerOne shows this on the scope table;
  Bugcrowd/Intigriti show submission stats less granularly (note when missing).
- **Last-updated date** of each scope entry → **freshness**. Recently added or
  rescoped assets have had fewer eyes.
- **Program-wide reward table** (per-severity bounty ranges + averages) and the
  **severity mix** of resolved reports if shown.

See `references/sourcing.md` for exactly where each platform surfaces these.

### 2. Mine bug history
- Check the program's **hacktivity / disclosed reports**. Many programs disclose
  nothing publicly - say so plainly and fall back to the crowd proxy.
- Web-search **public CVEs and researcher writeups** for the target's products.
  The recurring bug *class* tells you what actually lands (e.g. trust-boundary
  RCE, config-precedence, deep-link/IPC on native clients). Record specific CVE
  IDs where found.
- Note **remediation / dedup signals** - if the policy says a bug class is under
  a wholesale fix, reports there will close as duplicates. Flag those assets as
  saturated regardless of payout.

### 3. Extract the boundary gotchas
The fine print is where hunters waste days. Capture:
- **Adjacent in/out pairs** - cases where a near-identical bug is in-scope on one
  asset and out on another (e.g. first-party MCP in-scope vs OSS MCP out;
  first-party connector in vs third-party-in-directory out).
- **Excluded vulnerability classes** (DoS, clickjacking on non-sensitive pages,
  missing cookie flags, dependency confusion, self-XSS, rate-limiting, etc.).
- **Platform-standard deviations** and payout nerfs - especially
  "one bounty per systemic issue" and discretionary PII-leak severity, which
  kill the "report many instances" strategy.
- **Testing constraints** (e.g. leaked-key "authenticate then immediately
  deauth only", required `X-HackerOne-Handle` header / researcher email).

Render this as a compact **in ✅ / out ❌ table** - see the output spec.

### 4. Score & rank every asset
Apply the rubric in `references/scoring.md`. For each asset produce:
`{ setup tier + time estimate, crowd, payout ceiling, freshness, ROI verdict,
opportunity score 0–100 }`, then sort best→worst. Verdict buckets:
**Prime · Good · Recon · Skip · Dead**. Be honest that the two highest-ceiling
assets are often near the bottom because the crowd is already there - surfacing
that inversion is the point.

### 5. Deliver
Produce the artifacts below. Lead with the ranking; it is what the user asked for
even when they phrased it as "analyze the scope".

## Output spec

**The output is tables + the two widgets. Prose is connective tissue, not the
product.** Put every fact that fits in a cell *in a cell* - never restate in a
paragraph what a table or widget already shows. See "Prose discipline" below for
the hard budget; violating it is the most common failure of this skill.

Deliver, in this order:

1. **Program header - one line.** `bounty since · total paid · paid/90d ·
   resolved · response-efficiency · # in-scope assets`, then a single sentence on
   how hunted the program is. No more.
2. **Boundary gotchas - a table.** The `in ✅ / out ❌` adjacent pairs as a
   two-column table. Follow with **at most 3–4 bullets** for the
   strategy-killing nerfs (systemic-dedup, testing constraints, saturated
   classes) - one line each, no sub-bullets, no paragraphs.
3. **Severity economics - a table.** Columns: `severity · share of resolved ·
   avg bounty · range`, one row per severity. Then **one** italic sentence:
   "where the crits actually land", grounded in the CVE corpus - noting crit
   rates are usually ~1% and the money is repeatable Highs.
4. **Ranked list widget, best→worst** - rank, verdict chip, payout ceiling,
   setup time, crowd %, freshness, and a one-line "why" per asset. Render with
   the ranked-list widget in `references/sankey.md`. The widget carries the
   per-asset detail; do **not** narrate the list row by row afterward.
5. **Ranked Sankey widget** - the three-stage `asset → replication setup → ROI
   verdict` flow, assets ordered best (top) → worst (bottom), thread width =
   report volume, labels carrying payout ceiling + freshness. Fill the one data
   array in the `references/sankey.md` template - do not hand-roll new diagram
   code. Render via the `visualize` `show_widget` tool (call its `read_me` once
   first, as that tool requires).
6. **Verdict - a tight closer under both widgets.** Only what the tables can't
   say: name the **top 1–3 picks** (one clause each on *why now*), state the
   **inversion** in one sentence (which high-ceiling assets sit at the bottom and
   why), and flag any **technique-development** target. Cap this whole section at
   ~120 words.

### Prose discipline (the point of this update)
- **Tables and widgets are load-bearing; prose only connects them.** If a
  sentence repeats a number or verdict already in a cell/chip, cut it.
- **Budget:** header = 1 line; each table gets ≤1 lead-in line and ≤1 follow-up
  line; the closing verdict ≤120 words. No section is a paragraph block.
- **No row-by-row narration** of the ranked list - that is the widget's job.
- **One sources line** at the very end (CVE/writeup links), not inline essays.
- When in doubt, move the sentence into a table cell or delete it.

## Notes on judgment
- **Estimate, but flag estimates.** Setup-time and opportunity scores are
  your synthesis; the payout ceiling, crowd counts, and dates are hard data from
  the program. Keep the two visibly separate so the user can trust the spine.
- **Scale to the ask.** "Quick take on this program" → the ranked list is
  enough. "Full workup" → all four artifacts plus the CVE history.
- **Stay in authorized-recon lane.** This skill reads public program data and
  public vulnerability history to prioritize; it does not test, exploit, or
  probe live targets. Reproduction/testing happens later, under program rules.
- This skill pairs with a reporting skill for the *next* phase (filing a found
  bug). Don't try to do both; hand off.

## references

```

```

## references/sankey.md

# Ranked Sankey + ranked-list templates

Two copy-paste widgets for the `visualize` `show_widget` tool. **Call the tool's
`read_me` once before your first `show_widget`** (it requires it) - modules
`["diagram","data_viz"]` are enough. Then fill the single `A = [...]` data array
in each template and render. Do not hand-author new diagram code per run - edit
the array only.

Shared conventions:
- One object per **in-scope** asset (drop out-of-scope ones or list them in prose).
- `v` = report volume / crowd proxy (thread width). If the program exposes no
  per-asset count, substitute an accessibility-based proxy and say so in prose.
- `r` = ROI verdict bucket: `Prime | Good | Recon | Skip | Dead`.
- `s` = setup tier key: `Trivial | Moderate | Hard`.
- `core` = 1 if top payout tier, else 0. `d` = short freshness date e.g. `"Jul '26"`.
- `fresh` = 1 to highlight a recent (e.g. current-year) rescope in green.
- `pick` = 1 to bold the label for a standout. Order the array **best→worst**.
- Colors are fixed so every run reads the same:
  `Prime #639922 · Good #378ADD · Recon #BA7517 · Skip #E24B4A · Dead #9A9A90`.
- Adjust the `$10k`/`$5k` ceiling strings to the actual program's top/half tier.

## Template 1 - ranked list (best→worst)

```html
<h2 style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;">Bug-bounty assets ranked best to worst by opportunity.</h2>
<div id="rk" style="width:100%;max-width:760px;margin:0 auto;padding:0.5rem 0;"></div>
<script>
(function(){
 const rows=[
  {r:1,n:'API & SDKs',v:'Prime',sc:92,m:'Top tier $$$ · ~2–4 hrs · 5% crowd · fresh',w:'Fresh top-tier + low barrier = get there first'},
  {r:2,n:'Native clients',v:'Prime',sc:87,m:'Top tier $$$ · 1–2 days · 7% crowd · stale',w:'Biggest setup moat at the top ceiling'}
  // ...one row per asset, ordered best->worst. v = verdict bucket, sc = 0-100 score
 ];
 const chip={Prime:['#C0DD97','#27500A'],Good:['#B5D4F4','#0C447C'],Recon:['#FAC775','#633806'],Skip:['#F7C1C1','#791F1F'],Dead:['#D3D1C7','#444441']};
 const bar={Prime:'#639922',Good:'#378ADD',Recon:'#BA7517',Skip:'#E24B4A',Dead:'#9A9A90'};
 let h='<div style="display:flex;align-items:center;gap:10px;padding:0 0 6px;font-size:11.5px;font-weight:500;color:var(--text-muted);"><div style="width:26px;"></div><div style="flex:1;">asset · setup · payout · crowd · freshness</div><div style="width:132px;text-align:right;">opportunity</div></div>';
 rows.forEach(function(o){
  const c=chip[o.v],b=bar[o.v];
  h+='<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid var(--border);">'
   +'<div style="width:26px;height:26px;flex:none;border-radius:13px;background:'+c[0]+';color:'+c[1]+';font-size:12.5px;font-weight:500;display:flex;align-items:center;justify-content:center;">'+o.r+'</div>'
   +'<div style="flex:1;min-width:0;">'
    +'<div style="font-size:14px;color:var(--text-primary);"><span style="font-weight:500;">'+o.n+'</span> <span style="font-size:11px;color:'+c[1]+';background:'+c[0]+';border-radius:10px;padding:1px 7px;margin-left:4px;">'+o.v+'</span></div>'
    +'<div style="font-size:12px;color:var(--text-secondary);margin-top:2px;">'+o.m+'</div>'
    +'<div style="font-size:11.5px;color:var(--text-muted);margin-top:1px;">'+o.w+'</div>'
   +'</div>'
   +'<div style="width:132px;flex:none;display:flex;align-items:center;gap:7px;">'
    +'<div style="flex:1;height:8px;border-radius:4px;background:var(--surface-1);border:1px solid var(--border);overflow:hidden;"><div style="width:'+o.sc+'%;height:100%;background:'+b+';"></div></div>'
    +'<div style="width:22px;text-align:right;font-size:12px;color:var(--text-secondary);">'+o.sc+'</div>'
   +'</div>'
  +'</div>';
 });
 document.getElementById('rk').innerHTML=h;
})();
</script>
```

## Template 2 - ranked 3-stage Sankey (asset → setup → ROI)

Left column ordered best (top) → worst (bottom); setup and ROI nodes are
pre-ordered to minimize crossings for a rank-ordered left. Thread width = `v`.
Labels carry rank, volume, payout ceiling, and freshness date (green if `fresh`).

```html
<h2 style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;">Ranked Sankey of bug-bounty assets, best top to worst bottom, flowing through replication setup difficulty to ROI verdict; thread width is report volume.</h2>
<div id="sk" style="width:100%;padding:0.5rem 0;"></div>
<script>
(function(){
 const A=[
  {rk:1,n:'API & SDKs',v:20,s:'Moderate',r:'Prime',core:1,d:"Jul '26",fresh:1,pick:1},
  {rk:2,n:'Native clients',v:27,s:'Hard',r:'Prime',core:1,d:"May '25",pick:1}
  // ...one object per in-scope asset, ordered best->worst (rk = rank)
 ];
 const setups=[['Hard','Hard · 1–2 days'],['Moderate','Moderate · 2–4 hrs'],['Trivial','Trivial · under 15 min']];
 const rois=[['Prime','Prime - best ROI'],['Good','Good'],['Recon','Recon lane'],['Skip','Skip - crowded'],['Dead','Dead']];
 const rc={Skip:'#E24B4A',Dead:'#9A9A90',Recon:'#BA7517',Good:'#378ADD',Prime:'#639922'};
 const total=A.reduce((s,a)=>s+a.v,0)||1;
 const topPad=58,botPad=22,lx=316,c1=548,c2=756,bw=11,leftGap=5,gap=16,scale=1.3;
 const hOf=a=>Math.max(a.v*scale,14);
 let y=topPad;A.forEach(a=>{a.h=hOf(a);a.y0=y;y+=a.h+leftGap;});
 const leftEnd=y-leftGap;
 function lay(keys,f){let yy=topPad;const p={};keys.forEach(k=>{const h=A.filter(a=>f(a)===k).reduce((s,a)=>s+a.h,0);p[k]={y:yy,h:h,off:0};yy+=h+gap;});p.__end=yy-gap;return p;}
 const sp=lay(setups.map(x=>x[0]),a=>a.s);
 const rp=lay(rois.map(x=>x[0]),a=>a.r);
 A.forEach(a=>{a.sy=sp[a.s].y+sp[a.s].off;sp[a.s].off+=a.h;});
 A.forEach(a=>{a.ry=rp[a.r].y+rp[a.r].off;rp[a.r].off+=a.h;});
 const H=Math.ceil(Math.max(leftEnd,sp.__end,rp.__end)+botPad);
 function ribbon(x0,yA,x1,yB,h,c){const xc=(x0+x1)/2;return '<path d="M'+x0+','+yA+' C'+xc+','+yA+' '+xc+','+yB+' '+x1+','+yB+' L'+x1+','+(yB+h)+' C'+xc+','+(yB+h)+' '+xc+','+(yA+h)+' '+x0+','+(yA+h)+' Z" fill="'+c+'" fill-opacity="0.25"/>';}
 let s='';
 A.forEach(a=>{s+=ribbon(lx+bw,a.y0,c1,a.sy,a.h,rc[a.r]);});
 A.forEach(a=>{s+=ribbon(c1+bw,a.sy,c2,a.ry,a.h,rc[a.r]);});
 setups.forEach(k=>{const p=sp[k[0]];if(!p.h)return;s+='<rect x="'+c1+'" y="'+p.y+'" width="'+bw+'" height="'+p.h+'" rx="2" fill="#B4B2A9"/>';s+='<text x="'+c1+'" y="'+(p.y-6)+'" font-size="12.5" font-weight="500" fill="var(--text-primary)">'+k[1]+'</text>';});
 A.forEach(a=>{
  s+='<rect x="'+lx+'" y="'+a.y0+'" width="'+bw+'" height="'+a.h+'" rx="2" fill="'+rc[a.r]+'"/>';
  const ty=a.y0+a.h/2;
  const cap=a.core?'$10k':'$5k';
  const capF=a.core?'var(--text-primary)':'var(--text-muted)';
  const dF=a.fresh?'#639922':'var(--text-muted)';
  s+='<text x="'+(lx-8)+'" y="'+ty+'" text-anchor="end" dominant-baseline="middle" font-size="12.5" fill="var(--text-primary)">'
    +'<tspan fill="var(--text-muted)" font-weight="500">'+a.rk+'  </tspan>'
    +'<tspan font-weight="'+(a.pick?500:400)+'">'+a.n+'</tspan>'
    +'<tspan fill="var(--text-secondary)" font-weight="400">  '+a.v+'</tspan>'
    +'<tspan fill="var(--text-muted)">  ·  </tspan><tspan fill="'+capF+'" font-weight="'+(a.core?500:400)+'">'+cap+'</tspan>'
    +'<tspan fill="var(--text-muted)">  ·  </tspan><tspan fill="'+dF+'" font-weight="'+(a.fresh?500:400)+'">'+a.d+'</tspan>'
    +'</text>';
 });
 rois.forEach(k=>{const p=rp[k[0]];if(!p.h)return;const cnt=A.filter(a=>a.r===k[0]).reduce((x,a)=>x+a.v,0);const pct=Math.round(cnt/total*100);const c=rc[k[0]];s+='<rect x="'+c2+'" y="'+p.y+'" width="'+bw+'" height="'+p.h+'" rx="2" fill="'+c+'"/>';const ty=p.y+p.h/2;s+='<text x="'+(c2+bw+10)+'" y="'+(ty-7)+'" dominant-baseline="middle" font-size="13.5" font-weight="500" fill="var(--text-primary)">'+k[1]+'</text>';s+='<text x="'+(c2+bw+10)+'" y="'+(ty+11)+'" dominant-baseline="middle" font-size="11.5" fill="var(--text-secondary)">'+cnt+' reports · '+pct+'%</text>';});
 s+='<text x="'+(lx+bw)+'" y="26" text-anchor="end" font-size="11.5" font-weight="500" fill="var(--text-muted)">rank · asset · volume · ceiling · updated</text>';
 s+='<text x="'+(lx+bw)+'" y="42" text-anchor="end" font-size="10.5" fill="var(--text-muted)">top = best ROI · green date = fresh · $10k = top tier</text>';
 s+='<text x="'+c1+'" y="30" font-size="11.5" font-weight="500" fill="var(--text-muted)">replication setup</text>';
 s+='<text x="'+c2+'" y="30" font-size="11.5" font-weight="500" fill="var(--text-muted)">ROI verdict</text>';
 document.getElementById('sk').innerHTML='<svg viewBox="0 0 1020 '+H+'" width="100%" role="img" aria-label="Ranked Sankey best to worst: asset to setup to ROI verdict" style="max-width:1020px;display:block;margin:0 auto;font-family:var(--font-sans)">'+s+'</svg>';
})();
</script>
```

## Notes
- Tune only the `A` / `rows` arrays, the ceiling strings, and the `fresh`
  highlight rule. Geometry constants are calibrated for ~14 assets; for many
  more, lower `scale` or raise the viewBox width.
- Empty setup/ROI nodes auto-hide (`if(!p.h)return`), so you can drop buckets a
  given program doesn't need.
- Keep width = crowd. Swapping width to score loses the "crowd sits at the low
  barrier" story that makes the diagram land.

## references/scoring.md

# Scoring & ranking rubric

Turn the gathered signals into one **opportunity score (0–100)** and a **verdict
bucket** per asset, then sort best→worst. The score is a synthesis - label it as
such - but it is driven by the hard signals below.

## The four axes

### 1. Payout ceiling (the dominant sort key)
The max reward reachable on the asset. Normalize across the program's own tiers:
- Top tier (HackerOne Core / Bugcrowd P1 full payout / "critical eligible"): **high**.
- Half tier (Non-core / reduced): **medium**.
- Reputation-only / no bounty: **low**.
Ceiling does most of the ranking - but never on its own (see the inversion).

### 2. Crowd (inverse - less is better)
Best signal: resolved-report **share** for the asset. Rough bands:
- ≥15% of program reports → **swept** (heavy penalty)
- 5–15% → **busy**
- 1–5% → **open**
- <1% with a live payout → **wide open** (bonus)
When per-asset counts are missing, proxy from accessibility: browser-only > CLI >
desktop app > mobile app > jailbreak/root-gated, in descending crowd.

### 3. Setup / replication difficulty (a MOAT, not a cost)
How hard it is to stand up a faithful test/repro environment. This is the axis
the skill exists to weaponize: a hard rig **thins the field**, so it *raises*
opportunity when the ceiling is high. Tier it with a time estimate:
- **Trivial · <15 min** - open a browser, `npm i`, clone a public repo.
- **Moderate · 2–4 hrs** - reverse a browser extension, stand up an SDK against a
  staging host, recon tooling, read a codebase deeply.
- **Hard · 1–2 days** - mobile RE (rooted Android + Frida/objection; jailbroken
  or repackaged iOS), desktop/Electron internals (`app.asar` unpack, IPC audit),
  local MCP/connector/extension harnesses, custom protocol emulation.
Map asset types to tiers by what a faithful repro actually requires, not by the
product's surface familiarity.

### 4. Freshness
Last scope-update / addition date. Newer = fewer eyes. Anything rescoped within
the last few months is a freshness **bonus**; year-plus-stale is neutral-to-mild
penalty (more prior trampling).

## Penalties (apply after the axes)
- **Dedup graveyard** - asset's dominant bug class is under a remediation project
  or is the program's most-reported class → heavy penalty even at high ceiling.
- **Informative-only surface** - scope note says common findings are
  working-as-intended / Informative → near-zero.
- **Testing constraints** so tight the asset is barely actionable (e.g.
  authenticate-then-deauth-only) → mild penalty.
- **No/low payout** on a self-hosted VDP → deprioritize vs paid peers.

## Composing the score
There is no magic formula - reason it through, but keep this shape:
`score ≈ ceiling_weight·ceiling + freshness − crowd + moat_bonus(if ceiling high)
− penalties`. Anchor with these reference points so runs stay consistent:
- **90+** Prime: high ceiling, open/wide-open, fresh, real setup moat.
- **70–89** Good: high ceiling but easier setup (so more reachable), or a strong
  moat capped by a lower ceiling.
- **45–69** Recon: skill-gated (recon/code-review) lanes, mid ceiling, mid crowd.
- **20–44** Skip: high ceiling but swept/dedup - the classic inversion.
- **<20** Dead: informative-only, static, or zero-report abandoned surface.

## Verdict buckets → colors (keep consistent with the Sankey)
`Prime #639922 (green) · Good #378ADD (blue) · Recon #BA7517 (amber) ·
Skip #E24B4A (red) · Dead #9A9A90 (gray)`.

## The inversion to always surface
Sort by score and you will usually find the **two highest-ceiling assets near the
bottom** because they are trivial to start and already swept. Name this explicitly
- "ceiling ≠ opportunity" - because it is the non-obvious insight the whole
exercise produces. Likewise call out any asset that is prime-difficulty but
sub-top ceiling as a **technique-development** target: prove the bug class there
cheaply, then pivot the same technique to a top-ceiling asset where it
recategorizes upward.

## references/sourcing.md

# Sourcing scope & signals per platform

Goal: fill, for every asset, `{name, type, in_scope, payout_ceiling_tier,
resolved_report_share, last_updated, testing_constraints}` plus the program-wide
`{reward_table, severity_mix, disclosure_policy, exclusions}`.

The platform policy/scope pages are JS-rendered SPAs. `WebFetch` returns an empty
shell - **use the browser** (`preview_start` → `navigate` → `get_page_text` /
`read_page`). Read both the **policy/overview** tab and the **scope** tab; they
carry different data.

## HackerOne - `hackerone.com/<program>`
- **Scope table** (`/policy_scopes` or the Scope tab): each asset row has
  Asset name, Type, Coverage (in/out), **Max severity**, **Bounty eligibility**,
  **Last update** date, and a **Resolved Reports** count + percent. That percent
  is the crowd proxy - record it verbatim.
- **Core vs Non-core** appears as a `[Core]` / `[Non-core]` tag in the asset
  description. Core = the high reward band (top ceiling). Map Core→top ceiling,
  Non-core→~half.
- **Overview / Rewards summary**: per-severity average bounty + "% submissions",
  and the Low/Medium/High/Critical reward ranges for Core and Non-core.
- **Program stats block**: total bounties paid, reports resolved, response
  efficiency, avg time-to-bounty/resolution.
- **Hacktivity** tab: publicly disclosed reports if any (often empty → note it).
- **Policy body**: program rules, out-of-scope classes, platform-standard
  deviations, required researcher email / `X-HackerOne-Handle` header,
  research-preview/beta downgrades, remediation-project dedup notes.

## Bugcrowd - `bugcrowd.com/<program>` (or `/engagements/...`)
- Scope is grouped by target; reward is driven by the **VRT (Vulnerability
  Rating Taxonomy)** and a **P1–P5** payout table. Capture the max payout per
  target as the ceiling; targets are often flagged as in-scope vs out.
- Per-asset resolved-count is usually **not** exposed - record crowd as unknown
  and lean harder on freshness + CVE history.
- Read the brief's "Focus areas", "Out of scope", and reward-range sections.

## Intigriti - `app.intigriti.com/programs/<org>/<program>`
- Tiered severity bounty table (Exceptional/Critical/High/Medium/Low). Domains
  and endpoints listed with in/out tags and tier caps.
- Public submission stats are limited; treat crowd as coarse.

## YesWeHack / self-hosted VDP / `security.txt`
- Read the program page or `/.well-known/security.txt`. Self-hosted VDPs often
  have **no bounty** (reputation only) and **no crowd data** - the ranking then
  leans on payout=none (deprioritize vs paid), freshness, and setup moat.

## Bug-history search patterns
Run these web searches (adapt product nouns to the target):
- `<product> CVE <year> writeup` and `<product> vulnerability disclosed report`
- `<product> <component> RCE OR auth bypass OR IDOR OR SSRF writeup`
- `<program> hackerone disclosed` (surfaces third-party writeups even when the
  program's own hacktivity is private)
- vendor advisory pages / GitHub Security Advisories for the in-scope repos.

Extract: specific **CVE IDs**, the recurring **bug class**, and whether a fix /
remediation project makes that class a **dedup risk**.

## What to do when data is missing
- No per-asset crowd count → say so, and proxy crowd from asset *accessibility*
  (a browser-only web app attracts more hunters than a jailbreak-gated mobile
  app) plus total program report volume.
- No reward table → rank on freshness + setup moat + bug-class value only, and
  state that payouts are unconfirmed.
Never silently invent numbers - a labeled estimate beats a fake data point.

