Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
143556b
Make validation+coercion a precondition for bake() (inversion)
ausmaster Jun 5, 2026
a41da70
Migrate tests to the validate-before-bake contract
ausmaster Jun 5, 2026
12fa9c7
Lets... get rid of validation .from_dict() cause validation and coerc…
ausmaster Jun 5, 2026
df10fa9
Cleanly split preset validation between from_dict and validate().
ausmaster Jun 5, 2026
c32332e
Add 'recursive' adjective; fix easter egg triggers
liquidsec Jun 6, 2026
f3fe87f
Clean up comments; log coercion errors instead of silencing
liquidsec Jun 6, 2026
2d1b1a9
Migrate stale module names in test.conf to current names
liquidsec Jun 6, 2026
c5dd67e
Fix module test config leak from DEFAULT_CONFIG singleton
liquidsec Jun 6, 2026
ae1d212
Remove dead config_overrides from aspnet_bin_exposure test
liquidsec Jun 7, 2026
eaff230
Accept list[str] for api_key config; don't stringify collections duri…
liquidsec Jun 7, 2026
5494e16
Coerce PathLike to str for union fields during config coercion
liquidsec Jun 7, 2026
c263de3
Remove 'github' template from test config_overrides
liquidsec Jun 7, 2026
9473828
Fix gowitness test: use deps.behavior instead of force_deps
liquidsec Jun 7, 2026
c91cd6d
Fix nuclei test: move interactsh_disable to top-level config
liquidsec Jun 7, 2026
d0ec063
Update easter egg dedication; fix shodan_dns test config key
liquidsec Jun 7, 2026
3e13150
Use log_to_stderr for easter egg, matching golden_gus
liquidsec Jun 7, 2026
2c3819a
Remove nonexistent 'wordlist' config from webbrute_shortnames test
liquidsec Jun 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bbot/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ async def _main():
preset._default_internal_modules = []

# Bake a temporary copy of the preset so that flags correctly enable their associated modules before listing them
preset.validate()
preset = preset.bake()

# --list-modules
Expand Down Expand Up @@ -156,6 +157,7 @@ async def _main():
print(row)
return

preset.validate()
baked_preset = preset.bake()

# --current-preset / --current-preset-full
Expand Down
12 changes: 10 additions & 2 deletions bbot/core/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from __future__ import annotations

import os
from typing import Annotated, Any, Literal, Optional

from pydantic import BaseModel, BeforeValidator, ConfigDict, field_validator
Expand Down Expand Up @@ -223,7 +224,9 @@ def coerce_value(value, accepted):
if "str" in accepted and not (accepted & _COLLECTION_NAMES):
if is_raw:
return value
return None if value is None else str(value)
if value is None or isinstance(value, (list, dict, set)):
return value
return str(value)
if accepted == frozenset({"bool"}):
v = _yaml_scalar(value) if is_raw else value
if isinstance(v, bool):
Expand All @@ -237,7 +240,11 @@ def coerce_value(value, accepted):
if low in _FALSE_WORDS:
return False
return v
return _yaml_scalar(value) if is_raw else value
if is_raw:
return _yaml_scalar(value)
if "str" in accepted and isinstance(value, os.PathLike):
return str(value)
return value


def coerce_config(config, index, prefix=""):
Expand Down Expand Up @@ -462,6 +469,7 @@ class BBOTConfig(BaseModel):
aggregate: Optional[bool] = None
dnsresolve: Optional[bool] = None
cloudcheck: Optional[bool] = None
unarchive: Optional[bool] = None

# URL handling
url_querystring_remove: Optional[bool] = None
Expand Down
2 changes: 2 additions & 0 deletions bbot/core/helpers/names_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@
"rapid_unscheduled",
"raving",
"reckless",
"recursive",
"reductive",
"ripped",
"ruthless",
Expand Down Expand Up @@ -720,6 +721,7 @@
"theoden",
"theon",
"theresa",
"thetechromancer",
"thomas",
"tiffany",
"timothy",
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/bevigil.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class bevigil(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="BeVigil OSINT API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="BeVigil OSINT API Key", sensitive=True, mandatory=True)
urls: bool = Field(False, description="Emit URLs in addition to DNS_NAMEs")

base_url = "https://osint.bevigil.com/api"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/bufferoverrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class BufferOverrun(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="BufferOverrun API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="BufferOverrun API key", sensitive=True, mandatory=True)
commercial: bool = Field(False, description="Use commercial API")

base_url = "https://tls.bufferover.run/dns"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/builtwith.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class builtwith(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Builtwith API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Builtwith API key", sensitive=True, mandatory=True)
redirects: bool = Field(True, description="Also look up inbound and outbound redirects")

base_url = "https://api.builtwith.com"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/c99.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class c99(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="c99.nl API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="c99.nl API key", sensitive=True, mandatory=True)

base_url = "https://api.c99.nl"
ping_url = f"{base_url}/randomnumber?key={{api_key}}&between=1,100&json"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/censys_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class censys_dns(censys):
}

class Config(BaseModuleConfig):
api_key: str = Field(
api_key: str | list[str] = Field(
"", description="Censys.io API Key in the format of 'key:secret'", sensitive=True, mandatory=True
)
max_pages: int = Field(5, description="Maximum number of pages to fetch (100 results per page)")
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/censys_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class censys_ip(censys):
}

class Config(BaseModuleConfig):
api_key: str = Field(
api_key: str | list[str] = Field(
"", description="Censys.io API Key in the format of 'key:secret'", sensitive=True, mandatory=True
)
dns_names_limit: int = Field(
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/chaos.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class chaos(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Chaos API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Chaos API key", sensitive=True, mandatory=True)

base_url = "https://dns.projectdiscovery.io/dns"
ping_url = f"{base_url}/example.com"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/dehashed.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class dehashed(subdomain_enum):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="DeHashed API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="DeHashed API Key", sensitive=True, mandatory=True)

target_only = True

Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/fullhunt.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class fullhunt(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="FullHunt API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="FullHunt API Key", sensitive=True, mandatory=True)

base_url = "https://fullhunt.io/api/v1"

Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/git_clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class git_clone(github):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Github token", sensitive=True)
api_key: str | list[str] = Field("", description="Github token", sensitive=True)
output_folder: str = Field(
"",
description="Folder to clone repositories to. If not specified, cloned repositories will be deleted when the scan completes, to minimize disk usage.",
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/github_codesearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class github_codesearch(github, subdomain_enum):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Github token", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Github token", sensitive=True, mandatory=True)
limit: int = Field(100, description="Limit code search to this many results")

github_raw_url = "https://raw.githubusercontent.com/"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/github_org.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class github_org(github):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Github token", sensitive=True)
api_key: str | list[str] = Field("", description="Github token", sensitive=True)
include_members: bool = Field(True, description="Enumerate organization members")
include_member_repos: bool = Field(False, description="Also enumerate organization members' repositories")

Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/github_usersearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class github_usersearch(github, subdomain_enum):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Github token", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Github token", sensitive=True, mandatory=True)

async def handle_event(self, event):
self.verbose("Searching for users with emails matching in scope domains")
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/github_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class github_workflows(github):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Github token", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Github token", sensitive=True, mandatory=True)
num_logs: int = Field(1, description="For each workflow fetch the last N successful runs logs (max 100)")
output_folder: str = Field("", description="Folder to download workflow logs and artifacts to")

Expand Down
4 changes: 3 additions & 1 deletion bbot/modules/gitlab_com.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ class gitlab_com(GitLabBaseModule):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="GitLab access token (for gitlab.com/org only)", sensitive=True)
api_key: str | list[str] = Field(
"", description="GitLab access token (for gitlab.com/org only)", sensitive=True
)

# This is needed because we are consuming SOCIAL events, which aren't in scope
scope_distance_modifier = 2
Expand Down
4 changes: 3 additions & 1 deletion bbot/modules/gitlab_onprem.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class gitlab_onprem(GitLabBaseModule):
# Optional GitLab access token (only required for gitlab.com, but still
# supported for on-prem installations that expose private projects).
class Config(BaseModuleConfig):
api_key: str = Field("", description="GitLab access token (for self-hosted instances only)", sensitive=True)
api_key: str | list[str] = Field(
"", description="GitLab access token (for self-hosted instances only)", sensitive=True
)

# Allow accepting events slightly beyond configured max distance so we can
# discover repos on neighbouring infrastructure.
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/hunterio.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class hunterio(subdomain_enum_apikey):
meta = {"description": "Query hunter.io for emails", "created_date": "2022-04-25", "author": "@TheTechromancer"}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Hunter.IO API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Hunter.IO API key", sensitive=True, mandatory=True)

base_url = "https://api.hunter.io/v2"
ping_url = f"{base_url}/account?api_key={{api_key}}"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/ip2location.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class IP2Location(BaseModule):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="IP2location.io API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="IP2location.io API Key", sensitive=True, mandatory=True)
lang: str = Field(
"",
description="Translation information(ISO639-1). The translation is only applicable for continent, country, region and city name.",
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/ipstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Ipstack(BaseModule):
meta = {"description": "Query IPStack's GeoIP API", "created_date": "2022-11-26", "author": "@tycoonslive"}

class Config(BaseModuleConfig):
api_key: str = Field("", description="IPStack GeoIP API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="IPStack GeoIP API Key", sensitive=True, mandatory=True)

scope_distance_modifier = 1
_priority = 2
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/leakix.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class leakix(subdomain_enum_apikey):
flags = ["safe", "subdomain-enum", "passive"]

class Config(BaseModuleConfig):
api_key: str = Field("", description="LeakIX API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="LeakIX API Key", sensitive=True, mandatory=True)

meta = {
"description": "Query leakix.net for subdomains",
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/otx.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class otx(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="OTX API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="OTX API key", sensitive=True, mandatory=True)

base_url = "https://otx.alienvault.com"

Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/postman.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class postman(postman):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Postman API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Postman API Key", sensitive=True, mandatory=True)

reject_wildcards = False

Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/postman_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Config(BaseModuleConfig):
"",
description="Folder to download postman workspaces to. If not specified, downloaded workspaces will be deleted when the scan completes, to minimize disk usage.",
)
api_key: str = Field("", description="Postman API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Postman API Key", sensitive=True, mandatory=True)

scope_distance_modifier = 2

Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/securitytrails.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class securitytrails(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="SecurityTrails API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="SecurityTrails API key", sensitive=True, mandatory=True)

base_url = "https://api.securitytrails.com/v1"
ping_url = f"{base_url}/ping?apikey={{api_key}}"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/shodan_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class shodan_dns(shodan):
meta = {"description": "Query Shodan for subdomains", "created_date": "2022-07-03", "author": "@TheTechromancer"}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Shodan API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Shodan API key", sensitive=True, mandatory=True)

base_url = "https://api.shodan.io"

Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/shodan_enterprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class shodan_enterprise(BaseModule):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Shodan API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Shodan API Key", sensitive=True, mandatory=True)
in_scope_only: bool = Field(
True, description="Only query in-scope IPs. If False, will query up to distance 1."
)
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/subdomainradar.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class SubdomainRadar(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="SubDomainRadar.io API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="SubDomainRadar.io API key", sensitive=True, mandatory=True)
group: Literal["fast", "medium", "deep"] = Field(
"fast", description="The enumeration group to use. Choose from fast, medium, deep"
)
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/trickest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Trickest(subdomain_enum_apikey):
meta = {"description": "Query Trickest's API for subdomains", "author": "@amiremami", "created_date": "2024-07-27"}

class Config(BaseModuleConfig):
api_key: str = Field("", description="Trickest API key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="Trickest API key", sensitive=True, mandatory=True)

base_url = "https://api.trickest.io/solutions/v1/public/solution/a7cba1f1-df07-4a5c-876a-953f178996be"
ping_url = f"{base_url}/dataset"
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/virustotal.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class virustotal(subdomain_enum_apikey):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="VirusTotal API Key", sensitive=True, mandatory=True)
api_key: str | list[str] = Field("", description="VirusTotal API Key", sensitive=True, mandatory=True)

base_url = "https://www.virustotal.com/api/v3"
api_page_iter_kwargs = {"json": False, "next_key": lambda r: r.json().get("links", {}).get("next", "")}
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/wpscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class wpscan(BaseModule):
}

class Config(BaseModuleConfig):
api_key: str = Field("", description="WPScan API Key", sensitive=True)
api_key: str | list[str] = Field("", description="WPScan API Key", sensitive=True)
enumerate: str = Field(
"vp,vt,cb,dbe", description="Enumeration Process see wpscan help documentation (default: vp,vt,cb,dbe)"
)
Expand Down
Loading
Loading