Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bbdiscover

Automated discovery of self-hosted bug bounty and vulnerability disclosure programs, the ones companies run themselves rather than through HackerOne, Bugcrowd, Intigriti, or YesWeHack.

Everything found is scored, deduplicated against a SQLite state file, and pushed to a Google Sheet. Because state persists, every run tells you only what is genuinely new since last time.


What it does

Source What it finds Cost
security_txt Sweeps a domain list for /.well-known/security.txt (RFC 9116) free
diodb disclose.io open program database, ~2,400 entries free
urlscan urlscan.io search for security.txt, date sorted free key
gcse Google Programmable Search, dork list with dateRestrict 100 q/day free
ctwatch.py Certificate transparency, catches security.* / vdp.* hosts free

The core filter is an inversion: anything whose policy or contact points at a platform is discarded. What remains is your target set.


Setup

mkdir -p ~/bbdiscover && cd ~/bbdiscover
# drop bbdiscover.py, ctwatch.py, run.sh, requirements.txt, config.example.yaml here
pip install -r requirements.txt --break-system-packages
cp config.example.yaml config.yaml
mkdir -p lists out

Get a domain list:

curl -sL https://tranco-list.eu/top-1m.csv.zip -o /tmp/t.zip
unzip -p /tmp/t.zip | cut -d, -f2 > lists/tranco.txt

Google Sheets

  1. console.cloud.google.com → new project → enable Google Sheets API
  2. Create a Service Account, add a JSON key, save it as service-account.json
  3. Create a blank Sheet, copy the ID from the URL: docs.google.com/spreadsheets/d/THIS_PART/edit
  4. Share the Sheet with the service account email (Editor). This is the step everyone forgets, and without it you get a 403.
  5. Put sheet_id and google_creds in config.yaml

Two tabs are written: all_programs (everything tracked) and new_30d (only what appeared in the last 30 days, sorted by score).

Set your contact address in the UA constant at the top of bbdiscover.py before running the sweep. You are making a lot of requests, and a real contact in the User-Agent is what keeps you off blocklists.


Usage

# Quick start, no crawling, populates ~1,300 programs in about 30 seconds
python3 bbdiscover.py --sources diodb

# Test the sweep on a small list first
head -500 lists/tranco.txt > /tmp/t.txt
python3 bbdiscover.py --sources security_txt --domains /tmp/t.txt --limit 500

# Full run
python3 bbdiscover.py --sources security_txt,diodb,urlscan --concurrency 150

# Only programs that pay and have real safe harbor
python3 bbdiscover.py --sources diodb --min-score 12

Useful flags: --no-enrich (skip fetching policy pages, much faster), --include-platform (keep HackerOne/Bugcrowd entries), --min-score N, --quiet (for cron).

Certificate transparency

python3 ctwatch.py                  # stream certstream, writes lists/candidates.txt
python3 ctwatch.py --poll acme.com  # one-shot crt.sh poll, cron friendly

Then feed the results back in:

cut -d, -f1 lists/candidates.txt > /tmp/ct.txt
python3 bbdiscover.py --sources security_txt --domains /tmp/ct.txt

Cron

0 3 * * 1   /home/syed/bbdiscover/run.sh >> /home/syed/bbdiscover/cron.log 2>&1

A 200k-domain sweep at concurrency 150 takes roughly 40 minutes. Weekly is plenty; security.txt files do not appear that fast.


Scoring

Higher means more worth your time.

Signal Points
Self-hosted (not on a platform) +3
Pays a bounty (BBP) +4
Monetary reward language in policy +2
Full safe harbor +3 (partial +1, none -2)
Hall of fame / acknowledgements page +1
Hiring: field present (staffed team, faster triage) +1
Policy URL reachable +1 (dead -4)
Expired Expires: field -1
Coordinated disclosure requires NDA -1

Anything at 12 or above is self-hosted, pays, and has real legal cover. That is where to start.


Notes and caveats

The sweep is noisy by design. A security.txt with a Contact: but no Policy: is still logged, scored low, and marked contact-only. Those are often informal setups with no authorization language. Do not treat them as open season.

Verify authorization before testing anything. A page saying "email us if you find something" is not a scope grant. Read the policy, confirm it names in-scope assets and includes non-prosecution language, and archive the policy text as of the date you tested. program_type=VDP means no money, only credit.

Rate limiting. Default concurrency is 120. Going much higher gets you rate-limited by shared hosts and CDNs, and the results get worse, not better.

crt.sh is unreliable. ctwatch.py --poll retries with backoff and falls back to Certspotter. The public certstream server also goes down regularly; if you depend on streaming, self-host certstream-server-go.

diodb is community maintained, so entries can be stale. The policy_status column carries the upstream alive/dead flag, and enrichment re-checks the URL live, so dead links get penalized automatically.

About

Self-hosted bug bounty / VDP program discovery with CT-log real-time watch and Google Sheets output

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages