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, orpkg/storageinto your own tools
Modes of operation
| Mode | Command | Description |
|---|---|---|
bbscope poll | Fetch and print scopes to stdout | |
| Database | bbscope poll --db | Fetch, store, and print changes |
| Web | bbscope serve | Full 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
Go install (recommended)
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:
| Flag | Description |
|---|---|
--config | Config file path (default ~/.bbscope.yaml) |
--proxy | HTTP proxy URL for platform requests |
--loglevel | Log level: debug, info, warn, error, fatal |
--debug-http | Log 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
| Flag | Short | Default | Description |
|---|---|---|---|
--db | false | Save results to PostgreSQL and print changes | |
--ai | false | Enable AI normalization (requires --db and API key) | |
--concurrency | 5 | Concurrent program fetches per platform | |
--category | all | Filter by scope category (wildcard, url, cidr, etc.) | |
--bbp-only | -b | false | Only programs with monetary rewards |
--private-only | -p | false | Only private programs |
--oos | false | Include out-of-scope elements | |
--output | -o | tu | Output flags: t=target, d=description, c=category, u=program URL |
--delimiter | -d | " " | Delimiter for output fields |
--since | Only 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
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:
| Flag | Short | Default | Description |
|---|---|---|---|
--platform | Filter by platform name | ||
--output | -o | t | Output 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
| Flag | Short | Description |
|---|---|---|
--output-dir | Output directory for downloaded reports (required) | |
--program | Filter by program handle(s) | |
--state | Filter by report state(s) | |
--severity | Filter by severity level(s) | |
--dry-run | List reports without downloading | |
--overwrite | Overwrite existing report files |
How it works
- 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. - Download phase: 10 parallel workers fetch full report details (
/v1/hackers/reports/{id}) and write them as Markdown files. - Skip logic: existing files are skipped unless
--overwriteis set. - 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:
| Flag | Field |
|---|---|
t | Target (hostname, URL, IP, etc.) |
d | Target description |
c | Category (wildcard, url, cidr, etc.) |
u | Program 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.
| Column | Type | Description |
|---|---|---|
id | SERIAL | Primary key |
platform | TEXT | Platform name (h1, bc, it, ywh, immunefi) |
handle | TEXT | Platform-specific program handle |
url | TEXT | Unique program URL |
first_seen_at | TIMESTAMP | When the program was first polled |
last_seen_at | TIMESTAMP | Last successful poll |
strict | INTEGER | Whether scope changes should be treated strictly |
disabled | INTEGER | 1 if program was removed from the platform |
is_ignored | INTEGER | 1 if user has ignored this program |
targets_raw
Raw scope entries as received from the platform.
| Column | Type | Description |
|---|---|---|
id | SERIAL | Primary key |
program_id | INTEGER | FK to programs |
target | TEXT | Raw target string |
category | TEXT | Normalized category |
description | TEXT | Platform-provided description |
in_scope | INTEGER | 1 = in scope, 0 = out of scope |
is_bbp | INTEGER | 1 = bug bounty program |
first_seen_at | TIMESTAMP | When this target was first seen |
last_seen_at | TIMESTAMP | Last 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).
| Column | Type | Description |
|---|---|---|
occurred_at | TIMESTAMP | When the change was detected |
program_url | TEXT | Program URL |
platform | TEXT | Platform name |
target_normalized | TEXT | Normalized target |
target_raw | TEXT | Raw target string |
target_ai_normalized | TEXT | AI-normalized variant (if applicable) |
category | TEXT | Target category |
in_scope | INTEGER | Scope status |
change_type | TEXT | added, 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
| Flag | Default | Description |
|---|---|---|
--listen | :8080 | Address to listen on |
--dev | false | Development mode |
--poll-interval | 6 | Hours between background poll cycles |
--domain | localhost | Domain for canonical URLs and sitemap |
Environment variables
The web server reads platform credentials from environment variables:
| Variable | Description |
|---|---|
DB_URL | PostgreSQL connection string |
DOMAIN | Public domain name |
POLL_INTERVAL | Hours between poll cycles |
H1_USERNAME | HackerOne username |
H1_TOKEN | HackerOne API token |
BC_EMAIL | Bugcrowd email |
BC_PASSWORD | Bugcrowd password |
BC_OTP | Bugcrowd TOTP secret |
BC_PUBLIC_ONLY | Set to any value for Bugcrowd public-only mode |
IT_TOKEN | Intigriti token |
YWH_EMAIL | YesWeHack email |
YWH_PASSWORD | YesWeHack password |
YWH_OTP | YesWeHack TOTP secret |
OPENAI_API_KEY | OpenAI API key for AI normalization |
OPENAI_MODEL | Model name (default: gpt-4.1-mini) |
Pages
| Path | Description |
|---|---|
/ | Landing page |
/programs | Paginated program listing with search and filters |
/program/{platform}/{handle} | Program detail: scope tables, recon links, change timeline |
/updates | Scope changes feed |
/stats | Charts: programs by platform, assets by type |
/api | Interactive API explorer |
/docs | Built-in feature guide |
/debug | Server uptime, AI status, poller status per platform |
/sitemap.xml | Auto-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:
- postgres (PostgreSQL 16 Alpine) — data storage with health checks and a persistent volume
- bbscope-web — the bbscope web server, polls platforms on a schedule
- 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 passwordDOMAIN— 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:
| Param | Description |
|---|---|
raw | Set 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:
| Param | Description |
|---|---|
scope | in (default), out, or all |
platform | Filter by platform name |
type | Filter by target category |
raw | true for raw (non-AI) data |
format | text (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:
| Param | Description |
|---|---|
q | Search 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:
| Param | Description |
|---|---|
since | Time filter: today, yesterday, 7d, 30d, 90d, 1y, or an ISO date (YYYY-MM-DD) |
page | Page 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
- On startup, the poller runs immediately.
- After each cycle, it waits for the configured interval (default: 6 hours).
- All platforms are polled concurrently.
- 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
| Aspect | CLI (bbscope poll --db) | Web (background poller) |
|---|---|---|
| Platform order | Sequential | Concurrent (all at once) |
| Change output | Printed to stdout | Logged to server logs |
| Credentials source | Config file | Environment variables |
| Authentication | Once at startup | Re-authenticated each cycle |
| Error handling | Returns first error | Logs 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.comhttps://app.example.com/api/v2 - REST APIinstead ofhttps://app.example.com/api/v2All subdomains of example.cominstead of*.example.com
AI normalization uses an LLM (OpenAI-compatible API) to clean these up automatically.
What it does
- Cleans up entries — strips descriptions, comments, and formatting artifacts from target strings.
- Handles wildcards — converts “All subdomains of X” to
*.X. - Classifies scope intent — determines if a messy entry is a wildcard, URL, domain, etc.
- Normalizes categories — maps platform-specific category names to unified ones.
- Caches results — stores AI outputs in the
targets_ai_enhanceddatabase 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 (
--dbflag) - 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_batchandmax_concurrencyif 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
| Package | Import | Purpose |
|---|---|---|
polling | pkg/polling | High-level orchestrator: poll a platform, upsert to DB, track changes |
platforms | pkg/platforms | Platform interface + implementations for H1, BC, IT, YWH, Immunefi |
storage | pkg/storage | PostgreSQL storage layer: upsert, query, search, change tracking |
targets | pkg/targets | Extract wildcards, domains, URLs, IPs, CIDRs from scope entries |
scope | pkg/scope | Core types (ProgramData, ScopeElement) and category normalization |
ai | pkg/ai | AI 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
- Checks if this is the first run (DB program count == 0)
- Loads ignored programs from the DB
- Calls
ListProgramHandles()on the platform - Safety check: aborts if 0 handles returned but DB has >10 programs
- Runs a worker pool (
Concurrencygoroutines) that for each program:- Fetches scope via
FetchProgramScope() - Skips ignored programs
- Builds
TargetItemlist from scope elements - Applies AI normalization (loads cached enhancements, only normalizes new items)
- Calls
BuildEntries()andUpsertProgramEntries() - Calls
LogChanges()(skipped on first run) - Calls
OnProgramDonecallback if set
- Fetches scope via
- Calls
SyncPlatformPrograms()to mark removed programs as disabled - Logs changes for removed programs (skipped on first run)
- Returns
PlatformResultwith 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)
}
| Method | Description |
|---|---|
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
- Start the bbscope web server (or use a hosted instance).
- In Burp Suite, load the script from
website/find-scope-burp-action.java. - 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/pollingcontains the polling logic used by both CLI and web server, avoiding duplication. - Change detection in DB:
UpsertProgramEntriesdoes 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_enhancedso only new/changed targets hit the API. - Category unification: Platform-specific category names are mapped to a unified set in
pkg/scope.