Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

bbscope

bbscope is a tool for bug bounty hunters that aggregates scope data from multiple bug bounty platforms into a single, queryable interface. It supports HackerOne, Bugcrowd, Intigriti, YesWeHack, and Immunefi.

What it does

  • Polls all major bug bounty platforms and fetches program scopes
  • Tracks changes — new targets, removed targets, scope updates — across poll cycles
  • Stores everything in PostgreSQL for querying, searching, and diffing
  • Normalizes messy scope entries using AI (optional, OpenAI-compatible)
  • Serves a web UI with search, filtering, stats, and a REST API
  • Extracts specific target types (wildcards, domains, URLs, IPs, CIDRs) ready for recon tools
  • Works as a Go library — import pkg/polling, pkg/platforms, or pkg/storage into your own tools

Modes of operation

ModeCommandDescription
Printbbscope pollFetch and print scopes to stdout
Databasebbscope poll --dbFetch, store, and print changes
Webbbscope serveFull web UI + REST API + background polling

Quick example

# Print all HackerOne in-scope targets
bbscope poll h1 --user your_user --token your_token

# Poll all platforms, store in DB, print changes
bbscope poll --db

# Extract wildcards for subdomain enumeration
bbscope db get wildcards

# Start the web interface
bbscope serve

Installation

Requires Go 1.24+:

go install github.com/sw33tLie/bbscope/v2@latest

Prebuilt binaries

Download from GitHub Releases. Binaries are available for Linux, macOS, and Windows (amd64/arm64).

Docker

docker pull ghcr.io/sw33tlie/bbscope:latest
docker run --rm ghcr.io/sw33tlie/bbscope:latest poll h1 --user x --token y

Build from source

git clone https://github.com/sw33tLie/bbscope.git
cd bbscope
go build -o bbscope .

Configuration

bbscope reads configuration from ~/.bbscope.yaml by default. Override with --config path/to/file.yaml.

Config file

hackerone:
  username: "your_h1_username"
  token: "your_h1_api_token"

bugcrowd:
  email: "you@example.com"
  password: "your_password"
  otpsecret: "YOUR_TOTP_SECRET"

intigriti:
  token: "your_intigriti_token"

yeswehack:
  email: "you@example.com"
  password: "your_password"
  otpsecret: "YOUR_TOTP_SECRET"

db:
  url: "postgres://user:pass@localhost:5432/bbscope?sslmode=disable"

ai:
  api_key: "sk-..."
  model: "gpt-4.1-mini"
  # provider: "openai"      # default
  # endpoint: ""             # custom OpenAI-compatible endpoint
  # max_batch: 0             # items per API call (0 = auto)
  # max_concurrency: 0       # parallel API calls (0 = auto)
  # proxy: ""                # HTTP proxy for AI calls

Global CLI flags

These flags apply to all commands:

FlagDescription
--configConfig file path (default ~/.bbscope.yaml)
--proxyHTTP proxy URL for platform requests
--loglevelLog level: debug, info, warn, error, fatal
--debug-httpLog full HTTP requests and responses

Environment variables

For the web server (bbscope serve), credentials are read from environment variables instead of the config file. See Self-Hosting for the full list.

The OPENAI_API_KEY environment variable is used as a fallback when ai.api_key is not set in the config file.

Quick Start

1. Set up credentials

Create ~/.bbscope.yaml with your platform credentials:

hackerone:
  username: "your_username"
  token: "your_api_token"

See Configuration for all platforms.

2. Print scopes (no database)

# All configured platforms
bbscope poll

# Just HackerOne, only bug bounty programs
bbscope poll h1 -b

# Custom output: target + description + program URL
bbscope poll -o tdu

3. Track changes with a database

Set up PostgreSQL and add the connection string to your config:

db:
  url: "postgres://user:pass@localhost:5432/bbscope?sslmode=disable"

Then poll with --db:

# First run populates the database silently
bbscope poll --db

# Subsequent runs print only changes (new/removed/updated targets)
bbscope poll --db

4. Query the database

# View stats
bbscope db stats

# Search for a target
bbscope db find "example.com"

# Extract wildcards for recon
bbscope db get wildcards

# Recent changes
bbscope db changes

5. Enable AI normalization (optional)

Add an OpenAI API key to your config:

ai:
  api_key: "sk-..."
bbscope poll --db --ai

This cleans up messy scope entries (e.g., "*.example.com (main site)" becomes *.example.com) and caches results in the database to avoid redundant API calls.

Polling Scopes

The bbscope poll command fetches program scopes from bug bounty platforms.

Basic usage

# Poll all configured platforms
bbscope poll

# Poll a specific platform
bbscope poll h1
bbscope poll bc
bbscope poll it
bbscope poll ywh
bbscope poll immunefi

Flags

FlagShortDefaultDescription
--dbfalseSave results to PostgreSQL and print changes
--aifalseEnable AI normalization (requires --db and API key)
--concurrency5Concurrent program fetches per platform
--categoryallFilter by scope category (wildcard, url, cidr, etc.)
--bbp-only-bfalseOnly programs with monetary rewards
--private-only-pfalseOnly private programs
--oosfalseInclude out-of-scope elements
--output-otuOutput flags: t=target, d=description, c=category, u=program URL
--delimiter-d" "Delimiter for output fields
--sinceOnly print changes since RFC3339 timestamp (requires --db)

Platform-specific flags

Each platform subcommand accepts inline credentials, useful for one-off runs without a config file:

# HackerOne
bbscope poll h1 --user your_user --token your_token

# Bugcrowd
bbscope poll bc --email you@example.com --password pass --otp-secret SECRET

# Bugcrowd public-only (no auth)
bbscope poll bc --public-only

# Intigriti
bbscope poll it --token your_token

# YesWeHack
bbscope poll ywh --email you@example.com --password pass --otp-secret SECRET

Database mode

With --db, bbscope tracks scope state across runs:

  • First run: Populates the database silently (no change output).
  • Subsequent runs: Prints only what changed since last poll.
  • Safety check: If a platform returns 0 programs but the database has >10, the sync is aborted to prevent accidental data loss.
# First run — silent population
bbscope poll --db

# Second run — prints changes
bbscope poll --db
# 🆕  h1  https://hackerone.com/example  *.new-target.com
# ❌  bc  https://bugcrowd.com/example  removed-target.com

Output formatting

The -o flag controls which fields are printed (non-DB mode):

# Target only
bbscope poll -o t

# Target + description + category + program URL
bbscope poll -o tdcu

# Tab-delimited for piping
bbscope poll -o tdu -d $'\t'

Filtering by category

# Only wildcard targets
bbscope poll --category wildcard

# Only mobile apps
bbscope poll --category android,ios

# Multiple categories
bbscope poll --category wildcard,url,cidr

Available categories: wildcard, url, cidr, android, ios, ai, hardware, blockchain, binary, code, other.

Database Commands

The bbscope db command group provides tools for querying and managing the scope database.

All commands require a database connection, configured via db.url in the config file or --db-url flag.

Stats

View per-platform statistics:

bbscope db stats

Outputs a table with program counts, in-scope targets, and out-of-scope targets per platform.

Changes

View recent scope changes:

# Last 50 changes (default)
bbscope db changes

# Last 200 changes
bbscope db changes --limit 200

Print

Dump the full scope database with filters:

# All in-scope targets
bbscope db print

# Include out-of-scope
bbscope db print --oos

# Filter by platform
bbscope db print --platform h1

# Filter by program
bbscope db print --program "https://hackerone.com/example"

# JSON output
bbscope db print --format json

# CSV output
bbscope db print --format csv

# Custom text output
bbscope db print -o tdu -d $'\t'

# Only changes since a date
bbscope db print --since 2025-01-01T00:00:00Z

Find

Full-text search across current and historical scope entries:

bbscope db find "example.com"

Results tagged [historical] are targets that were previously in scope but have since been removed.

Add custom targets

bbscope db add --target "*.custom.com" --category wildcard --program-url "https://example.com/program"

Ignore / Unignore programs

Ignored programs are skipped during polling:

# Ignore a program
bbscope db ignore --program-url "https://hackerone.com/example"

# Unignore
bbscope db unignore --program-url "https://hackerone.com/example"

# Include ignored programs in print output
bbscope db print --include-ignored

Shell

Open a psql shell connected to the bbscope database, with a schema reference printed:

bbscope db shell

Extracting Targets

The bbscope db get commands extract specific target types from the database, formatted for direct use with recon tools.

Wildcards

# Standard wildcard extraction
bbscope db get wildcards

# Aggressive mode: extracts root domains even from subdomains
bbscope db get wildcards --aggressive

# Filter by platform
bbscope db get wildcards --platform h1

Wildcards are deduplicated and sorted. Shared hosting / cloud provider domains are automatically filtered out (e.g., *.amazonaws.com, *.herokuapp.com, *.azurewebsites.net, and ~20 others).

Piping to recon tools

# Subdomain enumeration
bbscope db get wildcards | subfinder -silent | httpx -silent

# Aggressive mode for broader coverage
bbscope db get wildcards --aggressive | subfinder -silent

Domains

bbscope db get domains

Returns non-URL, non-IP targets that contain a dot (e.g., app.example.com).

URLs

bbscope db get urls

Returns targets starting with http:// or https://.

IPs

bbscope db get ips

Extracts IP addresses, including from URL targets.

CIDRs

bbscope db get cidrs

Returns CIDR ranges and IP ranges from scope entries.

Common flags

All db get subcommands support:

FlagShortDefaultDescription
--platformFilter by platform name
--output-otOutput flags
--delimiter-d" "Delimiter

Downloading Reports

The bbscope reports command downloads your vulnerability reports from bug bounty platforms as Markdown files.

HackerOne

# Download all your reports
bbscope reports h1 --output-dir ./reports

# Preview what would be downloaded (dry-run)
bbscope reports h1 --output-dir ./reports --dry-run

# Filter by program
bbscope reports h1 --output-dir ./reports --program google --program microsoft

# Filter by state (e.g., resolved, triaged, new, duplicate, informative, not-applicable, spam)
bbscope reports h1 --output-dir ./reports --state resolved --state triaged

# Filter by severity
bbscope reports h1 --output-dir ./reports --severity critical --severity high

# Combine filters
bbscope reports h1 --output-dir ./reports --program google --state resolved --severity critical

# Overwrite existing files
bbscope reports h1 --output-dir ./reports --overwrite

Authentication

Credentials can be provided via CLI flags or config file:

# CLI flags
bbscope reports h1 --user your_username --token your_api_token --output-dir ./reports
# ~/.bbscope.yaml
hackerone:
  username: "your_username"
  token: "your_api_token"

Output structure

Reports are saved as Markdown files organized by program:

reports/
└── h1/
    ├── google/
    │   ├── 123456_XSS_in_login_page.md
    │   └── 123457_IDOR_in_user_profile.md
    └── microsoft/
        └── 234567_SSRF_in_webhook_handler.md

Each file contains a metadata table (ID, program, state, severity, weakness, asset, bounty, CVE IDs, timestamps) followed by the vulnerability information and impact sections.

Dry-run output

The --dry-run flag prints a table of matching reports without downloading:

ID       PROGRAM    STATE     SEVERITY  CREATED                    TITLE
123456   google     resolved  high      2024-01-15T10:30:00.000Z   XSS in login page
123457   google     triaged   critical  2024-02-20T14:00:00.000Z   IDOR in user profile

Flags

FlagShortDescription
--output-dirOutput directory for downloaded reports (required)
--programFilter by program handle(s)
--stateFilter by report state(s)
--severityFilter by severity level(s)
--dry-runList reports without downloading
--overwriteOverwrite existing report files

How it works

  1. List phase: fetches all report summaries from the HackerOne API (/v1/hackers/me/reports), paginated at 100 per page. Filters are applied server-side using Lucene query syntax.
  2. Download phase: 10 parallel workers fetch full report details (/v1/hackers/reports/{id}) and write them as Markdown files.
  3. Skip logic: existing files are skipped unless --overwrite is set.
  4. Rate limiting: HTTP 429 responses trigger a 60-second backoff. Other transient errors are retried up to 3 times with a 2-second delay.

Note: The HackerOne Hacker API may not return draft reports or reports where you are a collaborator but not the primary reporter. If your downloaded count is lower than your dashboard total, this is likely the cause.

Output Formatting

Output flags (-o)

The -o flag controls which fields are included in the output. Flags can be combined:

FlagField
tTarget (hostname, URL, IP, etc.)
dTarget description
cCategory (wildcard, url, cidr, etc.)
uProgram URL

Examples

# Target only (default for db get)
bbscope poll -o t

# Target + program URL (default for poll)
bbscope poll -o tu

# All fields
bbscope poll -o tdcu

Delimiter (-d)

Set the separator between fields (default: space):

# Tab-delimited
bbscope poll -o tdu -d $'\t'

# Comma-delimited
bbscope poll -o tdu -d ","

# Pipe-delimited
bbscope poll -o tdu -d "|"

Output formats (db print)

The db print command supports structured output formats:

# Plain text (default)
bbscope db print

# JSON
bbscope db print --format json

# CSV
bbscope db print --format csv

Change output (–db mode)

When polling with --db, changes are printed with emoji prefixes:

🆕  h1  https://hackerone.com/program  *.new-target.com
❌  bc  https://bugcrowd.com/program  removed-target.com
🔄  it  https://intigriti.com/program  updated-target.com
❌ Program removed: https://hackerone.com/old-program

With AI normalization enabled, normalized variants show as:

🆕  h1  https://hackerone.com/program  *.example.com (main site) -> *.example.com

Database Setup

bbscope uses PostgreSQL to store program scopes and track changes over time.

Requirements

  • PostgreSQL 14+ (16 recommended)

Creating the database

createdb bbscope

Or via psql:

CREATE DATABASE bbscope;

Connection string

Add to ~/.bbscope.yaml:

db:
  url: "postgres://user:password@localhost:5432/bbscope?sslmode=disable"

Or pass via flag:

bbscope db stats --db-url "postgres://user:password@localhost:5432/bbscope?sslmode=disable"

Or via environment variable (used by the web server):

export DB_URL="postgres://user:password@localhost:5432/bbscope?sslmode=disable"

Schema auto-migration

bbscope automatically creates all tables and indexes on first connection. There’s no manual migration step. The schema is idempotent — safe to run against an existing database.

Docker

For local development, a quick PostgreSQL instance:

docker run -d \
  --name bbscope-db \
  -e POSTGRES_DB=bbscope \
  -e POSTGRES_USER=bbscope \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  postgres:16-alpine

Then:

db:
  url: "postgres://bbscope:secret@localhost:5432/bbscope?sslmode=disable"

Schema & Change Tracking

Tables

programs

Stores one row per program across all platforms.

ColumnTypeDescription
idSERIALPrimary key
platformTEXTPlatform name (h1, bc, it, ywh, immunefi)
handleTEXTPlatform-specific program handle
urlTEXTUnique program URL
first_seen_atTIMESTAMPWhen the program was first polled
last_seen_atTIMESTAMPLast successful poll
strictINTEGERWhether scope changes should be treated strictly
disabledINTEGER1 if program was removed from the platform
is_ignoredINTEGER1 if user has ignored this program

targets_raw

Raw scope entries as received from the platform.

ColumnTypeDescription
idSERIALPrimary key
program_idINTEGERFK to programs
targetTEXTRaw target string
categoryTEXTNormalized category
descriptionTEXTPlatform-provided description
in_scopeINTEGER1 = in scope, 0 = out of scope
is_bbpINTEGER1 = bug bounty program
first_seen_atTIMESTAMPWhen this target was first seen
last_seen_atTIMESTAMPLast time this target was present

Unique constraint: (program_id, category, target).

targets_ai_enhanced

AI-normalized variants of raw targets. Linked to targets_raw rows.

scope_changes

Change log — one row per detected change (addition, removal, or update).

ColumnTypeDescription
occurred_atTIMESTAMPWhen the change was detected
program_urlTEXTProgram URL
platformTEXTPlatform name
target_normalizedTEXTNormalized target
target_rawTEXTRaw target string
target_ai_normalizedTEXTAI-normalized variant (if applicable)
categoryTEXTTarget category
in_scopeINTEGERScope status
change_typeTEXTadded, removed, or updated

Change detection

When bbscope poll --db runs, it compares the freshly fetched scope against what’s stored in the database:

  • Added: Target exists in the poll but not in the DB.
  • Removed: Target exists in the DB but not in the current poll.
  • Updated: Target exists in both but properties changed (scope status, category, description).

Changes are logged to scope_changes and printed to stdout.

Safety mechanisms

  • Scope wipe protection: If an upsert would remove all targets from a program, the update is aborted (ErrAbortingScopeWipe). This prevents broken pollers from wiping real data.
  • Platform-level safety: If a platform returns 0 programs but the database has >10, the entire platform sync is skipped.
  • Program sync: Programs no longer returned by the platform are marked disabled, not deleted. Their historical data remains queryable.

Querying & Searching

CLI queries

# Full-text search
bbscope db find "example.com"

# Dump all in-scope targets
bbscope db print

# Filter by platform
bbscope db print --platform h1

# Filter by program
bbscope db print --program "https://hackerone.com/example"

# Changes since a date
bbscope db print --since 2025-01-01T00:00:00Z

# JSON output for scripting
bbscope db print --format json

Target extraction

The db get subcommands extract cleaned, deduplicated targets ready for recon:

bbscope db get wildcards          # *.example.com
bbscope db get wildcards --aggressive  # root domains via publicsuffix
bbscope db get domains            # app.example.com
bbscope db get urls               # https://app.example.com/api
bbscope db get ips                # 1.2.3.4
bbscope db get cidrs              # 10.0.0.0/8

See Extracting Targets for details.

Direct SQL

For advanced queries, use the built-in shell:

bbscope db shell

This opens psql with the schema printed for reference. Example queries:

-- Programs with most in-scope targets
SELECT p.url, COUNT(*) as target_count
FROM programs p
JOIN targets_raw t ON t.program_id = p.id
WHERE t.in_scope = 1
GROUP BY p.url
ORDER BY target_count DESC
LIMIT 20;

-- Recent wildcard additions
SELECT sc.occurred_at, sc.program_url, sc.target_raw
FROM scope_changes sc
WHERE sc.change_type = 'added'
  AND sc.category = 'wildcard'
ORDER BY sc.occurred_at DESC
LIMIT 50;

-- Programs added in the last 7 days
SELECT url, platform, first_seen_at
FROM programs
WHERE first_seen_at > NOW() - INTERVAL '7 days'
ORDER BY first_seen_at DESC;

REST API

When running the web server (bbscope serve), the same data is available via the REST API. See REST API.

Self-Hosting

The web interface is started with bbscope serve. It provides a full UI for browsing scopes, viewing changes, and querying targets, plus a REST API.

Local development

bbscope serve --dev --listen :8080

The --dev flag enables development mode (e.g., no caching).

Flags

FlagDefaultDescription
--listen:8080Address to listen on
--devfalseDevelopment mode
--poll-interval6Hours between background poll cycles
--domainlocalhostDomain for canonical URLs and sitemap

Environment variables

The web server reads platform credentials from environment variables:

VariableDescription
DB_URLPostgreSQL connection string
DOMAINPublic domain name
POLL_INTERVALHours between poll cycles
H1_USERNAMEHackerOne username
H1_TOKENHackerOne API token
BC_EMAILBugcrowd email
BC_PASSWORDBugcrowd password
BC_OTPBugcrowd TOTP secret
BC_PUBLIC_ONLYSet to any value for Bugcrowd public-only mode
IT_TOKENIntigriti token
YWH_EMAILYesWeHack email
YWH_PASSWORDYesWeHack password
YWH_OTPYesWeHack TOTP secret
OPENAI_API_KEYOpenAI API key for AI normalization
OPENAI_MODELModel name (default: gpt-4.1-mini)

Pages

PathDescription
/Landing page
/programsPaginated program listing with search and filters
/program/{platform}/{handle}Program detail: scope tables, recon links, change timeline
/updatesScope changes feed
/statsCharts: programs by platform, assets by type
/apiInteractive API explorer
/docsBuilt-in feature guide
/debugServer uptime, AI status, poller status per platform
/sitemap.xmlAuto-generated sitemap

Debug endpoint

The /debug page shows:

  • Server uptime
  • AI normalization status (enabled/disabled)
  • Total target count
  • Per-platform poller status: last run time, duration, success/failure/skipped

Docker Deployment

The recommended way to deploy the web interface is with Docker Compose. The setup includes PostgreSQL, the bbscope web server, and Caddy as a reverse proxy with automatic HTTPS.

Quick start

cd website/
cp .env.example .env
# Edit .env with your credentials
docker compose up -d

Architecture

Internet → Caddy (443/80) → bbscope-web (8080) → PostgreSQL (5432)

Three services:

  1. postgres (PostgreSQL 16 Alpine) — data storage with health checks and a persistent volume
  2. bbscope-web — the bbscope web server, polls platforms on a schedule
  3. caddy (Caddy 2 Alpine) — reverse proxy with automatic TLS certificate management

Environment variables

Create a .env file from the template:

cp .env.example .env

Edit with your values. At minimum, set:

  • POSTGRES_PASSWORD — database password
  • DOMAIN — your public domain (used by Caddy for HTTPS)
  • At least one platform’s credentials

Cloudflare DNS challenge

For wildcard certificates or when port 80 isn’t available for HTTP challenges, use the Cloudflare DNS challenge variant:

docker compose -f docker-compose.cloudflare.yml up -d

This builds a custom Caddy image with the Cloudflare DNS plugin. Set CF_API_TOKEN in your .env file.

Updating

docker compose pull
docker compose up -d

Or rebuild from source:

docker compose up -d --build

Logs

# All services
docker compose logs -f

# Just the web server
docker compose logs -f bbscope-web

# Just the poller output
docker compose logs -f bbscope-web | grep "Poller:"

Data persistence

PostgreSQL data is stored in a named volume (bbscope-pgdata). To back up:

docker compose exec postgres pg_dump -U bbscope bbscope > backup.sql

REST API

The web server exposes a REST API under /api/v1/. CORS is enabled for all origins.

Programs

List all programs

GET /api/v1/programs

Returns all programs with their scope data. Response is cached for 5 minutes.

Query parameters:

ParamDescription
rawSet to true to return raw (non-AI-normalized) scope data

Get a single program

GET /api/v1/programs/{platform}/{handle}

Returns full scope data for a specific program.

Targets

Extract specific target types across all programs.

GET /api/v1/targets/wildcards
GET /api/v1/targets/domains
GET /api/v1/targets/urls
GET /api/v1/targets/ips
GET /api/v1/targets/cidrs

Query parameters:

ParamDescription
scopein (default), out, or all
platformFilter by platform name
typeFilter by target category
rawtrue for raw (non-AI) data
formattext (default) or json

Examples

# All in-scope wildcards as plain text
curl https://your-instance.com/api/v1/targets/wildcards

# HackerOne wildcards as JSON
curl "https://your-instance.com/api/v1/targets/wildcards?platform=h1&format=json"

# Out-of-scope URLs
curl "https://your-instance.com/api/v1/targets/urls?scope=out"

Find

GET /api/v1/find

Find programs whose scope matches a given hostname or domain. Automatically expands to root domain matching (e.g. aaa.example.com will also match programs scoping bbb.example.com via the shared root example.com). Cloud provider domains (e.g. azurewebsites.net, herokuapp.com) are excluded from root domain expansion to avoid false positives.

Query parameters:

ParamDescription
qSearch query (hostname, domain, etc.) — required

Examples

# Find programs with example.com in scope
curl "https://your-instance.com/api/v1/find?q=example.com"

# Find via subdomain — expands to root domain matching
curl "https://your-instance.com/api/v1/find?q=sub.example.com"

Updates

GET /api/v1/updates

Returns paginated scope changes.

Query parameters:

ParamDescription
sinceTime filter: today, yesterday, 7d, 30d, 90d, 1y, or an ISO date (YYYY-MM-DD)
pagePage number (default: 1)

Example

# Changes in the last 7 days
curl "https://your-instance.com/api/v1/updates?since=7d"

Background Poller

When running bbscope serve, a background poller automatically polls all configured platforms on a schedule.

How it works

  1. On startup, the poller runs immediately.
  2. After each cycle, it waits for the configured interval (default: 6 hours).
  3. All platforms are polled concurrently.
  4. After each cycle, the web cache is invalidated so the UI reflects fresh data.

Configuration

Set the interval via flag or environment variable:

# Via flag
bbscope serve --poll-interval 12

# Via environment variable (Docker)
POLL_INTERVAL=12

Monitoring

The /debug endpoint shows the status of each platform’s poller:

  • Last run: When the poller last ran for this platform
  • Duration: How long the poll took
  • Status: Success, failure, or skipped (no credentials configured)

Behavior differences from CLI

AspectCLI (bbscope poll --db)Web (background poller)
Platform orderSequentialConcurrent (all at once)
Change outputPrinted to stdoutLogged to server logs
Credentials sourceConfig fileEnvironment variables
AuthenticationOnce at startupRe-authenticated each cycle
Error handlingReturns first errorLogs errors, continues

HackerOne

Authentication

HackerOne uses API token authentication (HTTP Basic Auth).

Generate a token at hackerone.com/settings/api_token.

Config file

hackerone:
  username: "your_username"
  token: "your_api_token"

CLI flags

bbscope poll h1 --user your_username --token your_api_token

Environment variables (web server)

H1_USERNAME=your_username
H1_TOKEN=your_api_token

What it fetches

  • All programs you have access to (public + private if invited)
  • In-scope and out-of-scope targets with categories and descriptions
  • Paginated via the HackerOne API v1 (/v1/hackers/programs)

Filtering

# Only bug bounty programs
bbscope poll h1 -b

# Only private programs
bbscope poll h1 -p

Platform name

Used in database records and API responses: h1

Bugcrowd

Authentication

Bugcrowd supports three authentication modes:

1. Full authentication (email + password + OTP)

Performs a full login flow with TOTP two-factor authentication.

bugcrowd:
  email: "you@example.com"
  password: "your_password"
  otpsecret: "YOUR_BASE32_TOTP_SECRET"

CLI flags:

bbscope poll bc --email you@example.com --password pass --otp-secret SECRET

2. Token authentication

Use a session token directly:

bbscope poll bc --token "your_session_token"

3. Public-only mode (no auth)

Fetches only publicly visible programs without any credentials:

bbscope poll bc --public-only

Environment variable for the web server:

BC_PUBLIC_ONLY=1

Environment variables (web server)

BC_EMAIL=you@example.com
BC_PASSWORD=your_password
BC_OTP=YOUR_TOTP_SECRET

Or for public-only:

BC_PUBLIC_ONLY=1

Notes

  • Bugcrowd rate-limits requests to 1 per second.
  • The poller includes WAF ban detection — if a ban is detected, it logs a warning.

Platform name

Used in database records and API responses: bc

Intigriti

Authentication

Intigriti uses a bearer token.

Config file

intigriti:
  token: "your_intigriti_token"

CLI flags

bbscope poll it --token your_token

Environment variables (web server)

IT_TOKEN=your_token

What it fetches

  • All accessible programs via the Intigriti researcher API
  • Two-pass scope processing to accurately detect BBP status from tier values
  • In-scope and out-of-scope targets with categories

Platform name

Used in database records and API responses: it

YesWeHack

Authentication

YesWeHack uses email + password + TOTP.

Config file

yeswehack:
  email: "you@example.com"
  password: "your_password"
  otpsecret: "YOUR_TOTP_SECRET"

CLI flags

bbscope poll ywh --email you@example.com --password pass --otp-secret SECRET

Or with a bearer token:

bbscope poll ywh --token "your_bearer_token"

Environment variables (web server)

YWH_EMAIL=you@example.com
YWH_PASSWORD=your_password
YWH_OTP=YOUR_TOTP_SECRET

What it fetches

  • All accessible programs via the YesWeHack API
  • Paginated program listing
  • In-scope and out-of-scope targets with categories

Platform name

Used in database records and API responses: ywh

Immunefi

Authentication

Immunefi requires no authentication. All data is scraped from the public Immunefi website.

Usage

bbscope poll immunefi

No configuration needed. Works out of the box.

Notes

  • Fetches data from the public Immunefi bug bounty listings.
  • Uses exponential backoff for rate limiting (HTTP 429 responses).
  • Parses React Server Components (RSC) responses from the Immunefi website.

Platform name

Used in database records and API responses: immunefi

AI Normalization — Overview

Bug bounty platforms often have messy scope entries. Targets might look like:

  • *.example.com (main website) instead of *.example.com
  • https://app.example.com/api/v2 - REST API instead of https://app.example.com/api/v2
  • All subdomains of example.com instead of *.example.com

AI normalization uses an LLM (OpenAI-compatible API) to clean these up automatically.

What it does

  1. Cleans up entries — strips descriptions, comments, and formatting artifacts from target strings.
  2. Handles wildcards — converts “All subdomains of X” to *.X.
  3. Classifies scope intent — determines if a messy entry is a wildcard, URL, domain, etc.
  4. Normalizes categories — maps platform-specific category names to unified ones.
  5. Caches results — stores AI outputs in the targets_ai_enhanced database table. Only new/changed targets are sent to the API on subsequent polls, avoiding redundant API calls and costs.

How it looks

In the web UI, the program detail page has an “AI / Raw” toggle to switch between views.

In the CLI with --db, changes show the mapping:

🆕  h1  https://hackerone.com/program  *.example.com (main site) -> *.example.com

Requirements

  • A database (--db flag)
  • An OpenAI-compatible API key

See Configuration for setup.

AI Normalization — Configuration

Config file

ai:
  api_key: "sk-..."
  model: "gpt-4.1-mini"       # default
  # provider: "openai"         # default
  # endpoint: ""               # custom OpenAI-compatible endpoint
  # max_batch: 0               # items per API call (0 = auto)
  # max_concurrency: 0         # parallel API calls (0 = auto)
  # proxy: ""                  # HTTP proxy for AI API calls

Environment variable fallback

If ai.api_key is not set in the config, bbscope falls back to the OPENAI_API_KEY environment variable.

For the web server, use:

OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4.1-mini

Usage

CLI

bbscope poll --db --ai

The --ai flag only works with --db. Without a database, there’s nowhere to store the normalized results.

Web server

If OPENAI_API_KEY is set, AI normalization is automatically enabled for the background poller. Check the /debug page to confirm.

Custom endpoints

For self-hosted or alternative OpenAI-compatible APIs (e.g., Ollama, vLLM, Azure OpenAI), set the endpoint:

ai:
  api_key: "your-key"
  endpoint: "http://localhost:11434/v1"
  model: "llama3"

Cost considerations

  • Only new or changed targets are sent to the API. Previously normalized targets are loaded from the database cache.
  • The default model (gpt-4.1-mini) is cost-effective for this use case.
  • Batch size and concurrency can be tuned with max_batch and max_concurrency if needed.

Using bbscope as a Library

bbscope’s packages under pkg/ are designed to be importable by other Go projects. You can use them to build custom tools without depending on the CLI.

Install

go get github.com/sw33tLie/bbscope/v2@latest

Packages

PackageImportPurpose
pollingpkg/pollingHigh-level orchestrator: poll a platform, upsert to DB, track changes
platformspkg/platformsPlatform interface + implementations for H1, BC, IT, YWH, Immunefi
storagepkg/storagePostgreSQL storage layer: upsert, query, search, change tracking
targetspkg/targetsExtract wildcards, domains, URLs, IPs, CIDRs from scope entries
scopepkg/scopeCore types (ProgramData, ScopeElement) and category normalization
aipkg/aiAI normalization interface and OpenAI implementation

Quick example

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/sw33tLie/bbscope/v2/pkg/platforms"
    h1 "github.com/sw33tLie/bbscope/v2/pkg/platforms/hackerone"
)

func main() {
    ctx := context.Background()
    poller := h1.NewPoller("your_user", "your_token")

    handles, err := poller.ListProgramHandles(ctx, platforms.PollOptions{})
    if err != nil {
        log.Fatal(err)
    }

    for _, h := range handles {
        pd, err := poller.FetchProgramScope(ctx, h, platforms.PollOptions{})
        if err != nil {
            log.Printf("error: %s: %v", h, err)
            continue
        }
        for _, s := range pd.InScope {
            fmt.Printf("%s  %s  %s\n", pd.Url, s.Category, s.Target)
        }
    }
}

Polling Package

import "github.com/sw33tLie/bbscope/v2/pkg/polling"

The polling package provides the high-level orchestrator that both the CLI and web server use. It handles the full lifecycle of polling a single platform: listing programs, fetching scopes concurrently, AI normalization with DB caching, upserting to the database, and tracking changes.

API

PollPlatform

func PollPlatform(ctx context.Context, cfg PlatformConfig) (*PlatformResult, error)

Polls a single platform end-to-end. The caller decides how to loop over multiple platforms (sequentially, concurrently, etc.).

PlatformConfig

type PlatformConfig struct {
    Poller      platforms.PlatformPoller  // required
    Options     platforms.PollOptions     // category/bounty/private filters
    DB          *storage.DB              // required
    Concurrency int                      // defaults to 5 if <= 0
    Normalizer  ai.Normalizer            // optional; nil = skip AI
    Log         Logger                   // optional; nil = no logging

    // Called per-program after upsert (from worker goroutines).
    // Nil = no callback.
    OnProgramDone func(programURL string, changes []storage.Change, isFirstRun bool)
}

PlatformResult

type PlatformResult struct {
    PolledProgramURLs     []string         // URLs of all successfully polled programs
    ProgramChanges        []storage.Change // per-program changes accumulated
    RemovedProgramChanges []storage.Change // changes from program sync (disabled programs)
    IsFirstRun            bool             // true if DB had 0 programs for this platform
    Errors                []error          // non-fatal errors (fetch failures, DB errors)
}

Logger

type Logger interface {
    Infof(format string, args ...interface{})
    Warnf(format string, args ...interface{})
    Errorf(format string, args ...interface{})
    Debugf(format string, args ...interface{})
}

Plug in any logger. Both logrus and stdlib log.Printf wrappers satisfy this interface.

Example: poll HackerOne with streaming output

package main

import (
    "context"
    "fmt"
    "log"

    h1 "github.com/sw33tLie/bbscope/v2/pkg/platforms/hackerone"
    "github.com/sw33tLie/bbscope/v2/pkg/polling"
    "github.com/sw33tLie/bbscope/v2/pkg/storage"
)

type myLogger struct{}
func (myLogger) Infof(f string, a ...interface{})  { log.Printf("[INFO] "+f, a...) }
func (myLogger) Warnf(f string, a ...interface{})  { log.Printf("[WARN] "+f, a...) }
func (myLogger) Errorf(f string, a ...interface{}) { log.Printf("[ERROR] "+f, a...) }
func (myLogger) Debugf(f string, a ...interface{}) {}

func main() {
    db, _ := storage.Open("postgres://user:pass@localhost/bbscope?sslmode=disable")
    defer db.Close()

    poller := h1.NewPoller("user", "token")

    result, err := polling.PollPlatform(context.Background(), polling.PlatformConfig{
        Poller:      poller,
        DB:          db,
        Concurrency: 10,
        Log:         myLogger{},
        OnProgramDone: func(url string, changes []storage.Change, isFirstRun bool) {
            if !isFirstRun {
                for _, c := range changes {
                    fmt.Printf("[%s] %s %s\n", c.ChangeType, c.ProgramURL, c.TargetRaw)
                }
            }
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Polled %d programs, %d changes\n",
        len(result.PolledProgramURLs), len(result.ProgramChanges))
}

What PollPlatform does internally

  1. Checks if this is the first run (DB program count == 0)
  2. Loads ignored programs from the DB
  3. Calls ListProgramHandles() on the platform
  4. Safety check: aborts if 0 handles returned but DB has >10 programs
  5. Runs a worker pool (Concurrency goroutines) that for each program:
    • Fetches scope via FetchProgramScope()
    • Skips ignored programs
    • Builds TargetItem list from scope elements
    • Applies AI normalization (loads cached enhancements, only normalizes new items)
    • Calls BuildEntries() and UpsertProgramEntries()
    • Calls LogChanges() (skipped on first run)
    • Calls OnProgramDone callback if set
  6. Calls SyncPlatformPrograms() to mark removed programs as disabled
  7. Logs changes for removed programs (skipped on first run)
  8. Returns PlatformResult with all accumulated data

Platforms Package

import "github.com/sw33tLie/bbscope/v2/pkg/platforms"

The platforms package defines the interface that all platform pollers implement, plus shared types.

PlatformPoller interface

type PlatformPoller interface {
    Name() string
    Authenticate(ctx context.Context, cfg AuthConfig) error
    ListProgramHandles(ctx context.Context, opts PollOptions) ([]string, error)
    FetchProgramScope(ctx context.Context, handle string, opts PollOptions) (scope.ProgramData, error)
}
MethodDescription
Name()Returns the platform identifier (h1, bc, it, ywh, immunefi)
Authenticate()Performs platform-specific login. Some platforms (H1, Immunefi) are no-ops
ListProgramHandles()Returns all program handles (identifiers) matching the filter options
FetchProgramScope()Fetches full scope data for a single program

Types

type AuthConfig struct {
    Username  string
    Email     string
    Password  string
    Token     string
    OtpSecret string
    Proxy     string
}

type PollOptions struct {
    BountyOnly  bool
    PrivateOnly bool
    Categories  string  // comma-separated or "all"
}

Platform implementations

Import the specific platform package:

import (
    h1  "github.com/sw33tLie/bbscope/v2/pkg/platforms/hackerone"
    bc  "github.com/sw33tLie/bbscope/v2/pkg/platforms/bugcrowd"
    it  "github.com/sw33tLie/bbscope/v2/pkg/platforms/intigriti"
    ywh "github.com/sw33tLie/bbscope/v2/pkg/platforms/yeswehack"
    imm "github.com/sw33tLie/bbscope/v2/pkg/platforms/immunefi"
)

Creating pollers

// HackerOne — credentials passed at construction
poller := h1.NewPoller("username", "api_token")

// Bugcrowd — authenticate after construction
poller := &bc.Poller{}
err := poller.Authenticate(ctx, platforms.AuthConfig{
    Email: "...", Password: "...", OtpSecret: "...",
})

// Bugcrowd public-only (no auth)
poller := bc.NewPollerPublicOnly()

// Intigriti — authenticate with token
poller := it.NewPoller()
err := poller.Authenticate(ctx, platforms.AuthConfig{Token: "..."})

// YesWeHack — authenticate after construction
poller := &ywh.Poller{}
err := poller.Authenticate(ctx, platforms.AuthConfig{
    Email: "...", Password: "...", OtpSecret: "...",
})

// Immunefi — no auth needed
poller := imm.NewPoller()

Example: fetch scope from multiple platforms

ctx := context.Background()

pollers := []platforms.PlatformPoller{
    h1.NewPoller("user", "token"),
    imm.NewPoller(),
}

opts := platforms.PollOptions{BountyOnly: true}

for _, p := range pollers {
    handles, _ := p.ListProgramHandles(ctx, opts)
    for _, h := range handles {
        pd, _ := p.FetchProgramScope(ctx, h, opts)
        fmt.Printf("%s: %d in-scope targets\n", pd.Url, len(pd.InScope))
    }
}

Storage Package

import "github.com/sw33tLie/bbscope/v2/pkg/storage"

The storage package provides the PostgreSQL persistence layer. It handles schema creation, scope upserts with change detection, querying, and searching.

Opening a connection

db, err := storage.Open("postgres://user:pass@localhost/bbscope?sslmode=disable")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

Open automatically creates all tables and indexes if they don’t exist.

Key types

TargetItem

Input type for building entries:

type TargetItem struct {
    URI         string
    Category    string
    Description string
    InScope     bool
    IsBBP       bool
    Variants    []TargetVariant  // AI-normalized variants
}

Change

Represents a detected scope change:

type Change struct {
    OccurredAt         time.Time
    ProgramURL         string
    Platform           string
    Handle             string
    TargetNormalized   string
    TargetRaw          string
    TargetAINormalized string
    Category           string
    InScope            bool
    IsBBP              bool
    ChangeType         string  // "added", "removed", "updated"
}

Core operations

Upserting scope data

// Build entries from target items
entries, err := storage.BuildEntries(programURL, platform, handle, items)

// Upsert and get changes
changes, err := db.UpsertProgramEntries(ctx, programURL, platform, handle, entries)

UpsertProgramEntries compares the new entries against what’s in the database and returns the diff as []Change. It also updates timestamps and handles additions/removals/updates atomically.

Syncing removed programs

changes, err := db.SyncPlatformPrograms(ctx, "h1", polledProgramURLs)

Marks programs not in polledProgramURLs as disabled and returns removal changes.

Logging changes

err := db.LogChanges(ctx, changes)

Persists changes to the scope_changes table for later querying.

Querying

// List entries with filters
entries, err := db.ListEntries(ctx, storage.ListOptions{
    Platform: "h1",
    InScope:  true,
})

// Full-text search
results, err := db.SearchTargets(ctx, "example.com")

// Recent changes (limit, since, until — use zero time for no filter)
changes, err := db.ListRecentChanges(ctx, 50, time.Time{}, time.Time{})

// Statistics
stats, err := db.GetStats(ctx)

Program management

// Get program count
count, err := db.GetActiveProgramCount(ctx, "h1")

// Get ignored programs
ignored, err := db.GetIgnoredPrograms(ctx, "h1")

// AI enhancements cache
enhancements, err := db.ListAIEnhancements(ctx, programURL)

Normalization helpers

// Normalize a target string (lowercase, strip ports, etc.)
normalized := storage.NormalizeTarget(raw, category)

// Build cache key for AI enhancement lookups
key := storage.BuildTargetCategoryKey(target, category)

// Aggressive transform: extract root domain via publicsuffix
root := storage.AggressiveTransform(target)

Targets Package

import "github.com/sw33tLie/bbscope/v2/pkg/targets"

The targets package extracts specific target types from scope entries. It handles deduplication, sorting, and filtering.

Functions

All functions take []storage.Entry and return []string:

// Wildcards — with automatic filtering of shared hosting domains
wildcards := targets.CollectWildcardsSorted(entries, aggressive)

// Domains — non-URL, non-IP targets containing a dot
domains := targets.CollectDomains(entries)

// URLs — targets starting with http:// or https://
urls := targets.CollectURLs(entries)

// IPs — IP addresses, including extracted from URLs
ips := targets.CollectIPs(entries)

// CIDRs — CIDR ranges and IP ranges
cidrs := targets.CollectCIDRs(entries)

Out-of-scope variants

Each function has an OOS counterpart:

oosWildcards := targets.CollectOOSWildcards(entries)
oosDomains   := targets.CollectOOSDomains(entries)
oosURLs      := targets.CollectOOSURLs(entries)
oosIPs       := targets.CollectOOSIPs(entries)
oosCIDRs     := targets.CollectOOSCIDRs(entries)

Wildcard filtering

CollectWildcardsSorted automatically filters out wildcards for shared hosting and cloud provider domains that would generate too many false positives:

  • *.amazonaws.com, *.cloudfront.net
  • *.azurewebsites.net, *.azure.com
  • *.herokuapp.com, *.netlify.app
  • *.shopify.com, *.myshopify.com
  • And ~15 more

Aggressive mode

When aggressive is true, root domains are extracted via the public suffix list. For example, app.staging.example.com becomes *.example.com.

Subdomain tool normalization

// Clean a scope string for use with subdomain enumeration tools
cleaned := targets.NormalizeForSubdomainTools(scopeEntry)

Strips prefixes like *., http://, trailing paths, and other artifacts.

Example

db, _ := storage.Open(dbURL)
defer db.Close()

entries, _ := db.ListEntries(ctx, storage.ListOptions{InScope: true})

wildcards := targets.CollectWildcardsSorted(entries, false)
for _, w := range wildcards {
    fmt.Println(w)
}

Burp Suite Integration

bbscope includes a Burp Suite script that checks whether HTTP request targets are in scope by querying the bbscope REST API.

Setup

  1. Start the bbscope web server (or use a hosted instance).
  2. In Burp Suite, load the script from website/find-scope-burp-action.java.
  3. Configure the script to point to your bbscope instance URL.

How it works

The script intercepts HTTP requests in Burp and checks each hostname against the bbscope API’s wildcard/domain list. If the target matches an in-scope entry, it’s flagged accordingly.

This is useful for passive scope validation during testing — you can see at a glance whether a target you’re interacting with is actually in scope for a bug bounty program.

Requirements

  • A running bbscope web server with the REST API accessible
  • Programs polled and stored in the database

Architecture

Project structure

bbscope/
├── cmd/                    # CLI commands (Cobra)
│   ├── root.go             # Root command, global flags
│   ├── poll.go             # bbscope poll
│   ├── poll_*.go           # Platform-specific poll subcommands
│   ├── db.go               # bbscope db
│   ├── db_*.go             # DB subcommands (stats, changes, find, etc.)
│   ├── serve.go            # bbscope serve
│   └── dev.go              # bbscope dev
├── pkg/                    # Library packages (importable)
│   ├── platforms/          # Platform interface + implementations
│   │   ├── platform.go     # PlatformPoller interface
│   │   ├── hackerone/
│   │   ├── bugcrowd/
│   │   ├── intigriti/
│   │   ├── yeswehack/
│   │   ├── immunefi/
│   │   └── dev/
│   ├── polling/            # Shared polling orchestrator
│   │   └── polling.go
│   ├── storage/            # PostgreSQL persistence
│   │   ├── storage.go      # DB operations
│   │   ├── types.go        # Entry, Change, UpsertEntry, etc.
│   │   ├── normalize.go    # Target normalization
│   │   ├── transform.go    # Aggressive transforms
│   │   └── extra.go        # Additional queries
│   ├── scope/              # Core types and category normalization
│   ├── targets/            # Target extraction (wildcards, domains, etc.)
│   ├── ai/                 # AI normalization
│   ├── whttp/              # HTTP client wrapper (retryablehttp)
│   └── otp/                # TOTP generation
├── website/                # Web server
│   ├── pkg/core/           # Server core (routes, handlers, poller)
│   ├── static/             # CSS, JS, images
│   ├── docker-compose.yml
│   └── Dockerfile
├── internal/
│   └── utils/              # Logging setup
└── docs/                   # This documentation (mdBook)

Data flow

CLI polling (bbscope poll --db)

Platform API
    ↓ ListProgramHandles()
    ↓ FetchProgramScope() × N (concurrent workers)
    ↓
pkg/polling.PollPlatform()
    ↓ AI normalize (optional, with DB cache)
    ↓ BuildEntries()
    ↓ UpsertProgramEntries() → changes
    ↓ LogChanges()
    ↓ OnProgramDone callback → printChanges()
    ↓
    ↓ SyncPlatformPrograms() → removed program changes
    ↓
stdout (change output)

Web server (bbscope serve)

Background goroutine (every N hours)
    ↓ buildPollers() from env vars
    ↓ For each platform (concurrent):
    │   polling.PollPlatform()
    ↓ invalidateProgramsCache()

HTTP requests
    ↓ /api/v1/* → query DB → JSON response (cached)
    ↓ /programs, /program/* → query DB → HTML (gomponents)

Key design decisions

  • Platform interface: All platforms implement PlatformPoller. Adding a new platform means implementing 4 methods.
  • Shared orchestrator: pkg/polling contains the polling logic used by both CLI and web server, avoiding duplication.
  • Change detection in DB: UpsertProgramEntries does an atomic compare-and-update, returning only what changed.
  • Safety checks: Multiple layers prevent accidental data loss (scope wipe detection, platform-level 0-program check).
  • AI caching: Normalized targets are stored in targets_ai_enhanced so only new/changed targets hit the API.
  • Category unification: Platform-specific category names are mapped to a unified set in pkg/scope.