Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
9bb4222
Bump griffe from 1.15.0 to 2.1.0
dependabot[bot] Jul 8, 2026
9942c84
Bump websockets from 15.0.1 to 16.0
dependabot[bot] Jul 8, 2026
976596d
Bump pre-commit from 4.5.1 to 4.6.0
dependabot[bot] Jul 8, 2026
598e135
Bump pip from 26.0.1 to 26.1.2
dependabot[bot] Jul 8, 2026
fd81c36
Bump pytest-cov from 7.0.0 to 7.1.0
dependabot[bot] Jul 8, 2026
af6bf24
Bump mike from 2.1.3 to 2.2.0
dependabot[bot] Jul 8, 2026
84a1908
Bump idna from 3.11 to 3.18
dependabot[bot] Jul 8, 2026
211507f
Bump version to 3.0.1
liquidsec Jul 8, 2026
863c717
Merge pull request #3259 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
1f7cf39
Merge pull request #3260 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
0b68f81
Merge pull request #3261 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
31d55a8
Bump pytest-rerunfailures from 16.1 to 16.4
dependabot[bot] Jul 8, 2026
fe79d0e
Bump maturin from 1.13.3 to 1.14.1
dependabot[bot] Jul 8, 2026
40eaae0
Merge pull request #3269 from blacklanternsecurity/bump-3.0.1
liquidsec Jul 8, 2026
5039476
Merge pull request #3268 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
9459002
Merge pull request #3265 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
42ef254
Merge pull request #3263 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
fd13d99
fix virtualhost.finish() crash on None baseline_response
liquidsec Jul 8, 2026
e1d567a
Merge pull request #3266 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
b28371f
Merge pull request #3264 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
86d38ae
Merge pull request #3262 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 8, 2026
92f9d1f
Escape control characters in console output
liquidsec Jul 8, 2026
d40dce8
Update trufflehog
blsaccess Jul 9, 2026
9e0b534
Update nuclei
blsaccess Jul 9, 2026
c49685c
Merge pull request #3276 from blacklanternsecurity/update-trufflehog
liquidsec Jul 9, 2026
54a8a94
Merge pull request #3275 from blacklanternsecurity/update-nuclei
liquidsec Jul 9, 2026
6302f7f
Bump urllib3 from 2.6.3 to 2.7.0
dependabot[bot] Jul 9, 2026
2348514
Fix stale baddns config key in kitchen-sink preset
liquidsec Jul 9, 2026
328a966
Merge pull request #3277 from blacklanternsecurity/dependabot/uv/urll…
liquidsec Jul 9, 2026
dd7fb1d
Attribute automated docs update PRs to blsaccess
liquidsec Jul 9, 2026
622abcb
Merge pull request #3273 from blacklanternsecurity/virtualhost-finish…
liquidsec Jul 9, 2026
823b636
Point API-key docs to secrets.yml
liquidsec Jul 9, 2026
75bb4be
Fix wrong bbot.yml path in docs
liquidsec Jul 9, 2026
914416e
Sanitize traceback and stack text in ColoredFormatter
liquidsec Jul 9, 2026
503f2e9
Bump deepdiff from 8.6.1 to 8.6.2
dependabot[bot] Jul 9, 2026
96736e0
Bump tornado from 6.5.4 to 6.5.7
dependabot[bot] Jul 9, 2026
89e9f91
Bump starlette from 0.52.1 to 1.3.1
dependabot[bot] Jul 9, 2026
e82d732
Bump cryptography from 46.0.5 to 48.0.1
dependabot[bot] Jul 9, 2026
5d2d16b
Bump pymdown-extensions from 10.20.1 to 10.21.3
dependabot[bot] Jul 9, 2026
0a83067
Fix openssl_dev_headers dep check to require the header
liquidsec Jul 9, 2026
570feb3
Bump lxml from 6.0.2 to 6.1.0
dependabot[bot] Jul 9, 2026
012e53a
Bump urllib3 from 2.6.3 to 2.7.0
dependabot[bot] Jul 9, 2026
96b532f
Bump pip from 26.0.1 to 26.1.2
dependabot[bot] Jul 9, 2026
f845bc5
Bump requests from 2.32.5 to 2.33.0
dependabot[bot] Jul 9, 2026
9b0a241
Bump pytest from 8.4.2 to 9.0.3
dependabot[bot] Jul 9, 2026
30d349e
Merge pull request #3284 from blacklanternsecurity/dependabot/uv/deep…
liquidsec Jul 9, 2026
bcc6c34
Merge pull request #3285 from blacklanternsecurity/dependabot/uv/torn…
liquidsec Jul 9, 2026
9e05585
Merge branch 'dev' into dependabot/uv/cryptography-48.0.1
liquidsec Jul 9, 2026
d758411
Merge branch 'dev' into dependabot/uv/requests-2.33.0
liquidsec Jul 9, 2026
d5d0efe
Merge branch 'dev' into readme-api-keys-secrets
liquidsec Jul 9, 2026
bc02960
Merge branch 'dev' into dependabot/uv/starlette-1.3.1
liquidsec Jul 9, 2026
c40828f
Merge branch 'dev' into dependabot/uv/lxml-6.1.0
liquidsec Jul 9, 2026
73fb3ef
Merge branch 'dev' into dependabot/uv/pymdown-extensions-10.21.3
liquidsec Jul 9, 2026
a1af697
Merge pull request #3274 from blacklanternsecurity/sanitize-console-o…
liquidsec Jul 9, 2026
c279a4c
Merge pull request #3281 from blacklanternsecurity/docs-updater-author
liquidsec Jul 9, 2026
7b203b2
[create-pull-request] automated change
liquidsec Jul 9, 2026
d496f33
Merge pull request #3287 from blacklanternsecurity/dependabot/uv/cryp…
liquidsec Jul 9, 2026
1d19fd2
Merge pull request #3293 from blacklanternsecurity/dependabot/uv/pyte…
liquidsec Jul 9, 2026
5cf74aa
Merge pull request #3282 from blacklanternsecurity/medusa-libssl-dep
liquidsec Jul 9, 2026
e1e6004
Merge pull request #3280 from blacklanternsecurity/kitchen-sink-baddn…
liquidsec Jul 9, 2026
b22fcc2
Merge pull request #3286 from blacklanternsecurity/dependabot/uv/star…
liquidsec Jul 9, 2026
5992e8b
Merge pull request #3292 from blacklanternsecurity/dependabot/uv/requ…
liquidsec Jul 9, 2026
ed981b3
Merge pull request #3290 from blacklanternsecurity/dependabot/uv/urll…
liquidsec Jul 9, 2026
0cbb82b
Merge pull request #3291 from blacklanternsecurity/dependabot/uv/pip-…
liquidsec Jul 9, 2026
69081e5
Merge pull request #3289 from blacklanternsecurity/dependabot/uv/lxml…
liquidsec Jul 9, 2026
ed653fa
Merge pull request #3288 from blacklanternsecurity/dependabot/uv/pymd…
liquidsec Jul 9, 2026
cb695bd
Merge pull request #3283 from blacklanternsecurity/readme-api-keys-se…
liquidsec Jul 9, 2026
57f7312
Merge pull request #3294 from blacklanternsecurity/update-docs
liquidsec Jul 9, 2026
481972f
Update trufflehog
blsaccess Jul 10, 2026
69ec06a
Merge pull request #3295 from blacklanternsecurity/update-trufflehog
liquidsec Jul 10, 2026
ec65fcd
Bump werkzeug from 3.1.5 to 3.1.8
dependabot[bot] Jul 12, 2026
a4d4f5f
Bump ansible-runner from 2.4.2 to 2.4.3
dependabot[bot] Jul 12, 2026
3582d30
Bump mkdocs-material from 9.7.1 to 9.7.6
dependabot[bot] Jul 12, 2026
194ed5e
Bump xxhash from 3.6.0 to 3.8.1
dependabot[bot] Jul 12, 2026
3e3c0ce
Bump deepdiff from 8.6.2 to 9.1.0
dependabot[bot] Jul 12, 2026
3283d71
Bump mkdocstrings-python from 2.0.2 to 2.0.5
dependabot[bot] Jul 12, 2026
1dcf271
Bump regex from 2026.1.15 to 2026.7.10
dependabot[bot] Jul 12, 2026
ff56df0
Bump dnspython from 2.7.0 to 2.8.0
dependabot[bot] Jul 12, 2026
4e4b1bb
Bump ruff from 0.15.20 to 0.15.21
dependabot[bot] Jul 12, 2026
c82545b
Merge pull request #3298 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
2d0d79d
Merge pull request #3299 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
7626fdc
Merge pull request #3302 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
ca7dddf
Merge pull request #3307 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
01ffb31
Merge pull request #3306 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
c2557c2
Merge pull request #3303 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
a720794
Merge pull request #3305 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
c8ed27a
Merge pull request #3304 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
6f62e97
Merge pull request #3300 from blacklanternsecurity/dependabot/uv/dev/…
liquidsec Jul 12, 2026
5ac89e7
restore cloudcheck tag propagation to URL/OPEN_TCP_PORT children
liquidsec Jul 15, 2026
cb4c626
waf_bypass: demote non-IP DNS result log from warning to verbose
liquidsec Jul 15, 2026
be396f2
waf_bypass: parallelize bypass checks in finish() up to 100 by default
liquidsec Jul 15, 2026
1d8c67e
waf_bypass: distinguish direct vs neighbor bypass candidates in logs
liquidsec Jul 15, 2026
ebcf658
waf_bypass: lower neighbor_cidr default from /24 to /28
liquidsec Jul 15, 2026
8903230
waf-bypass preset: match module default (neighbor_cidr 28)
liquidsec Jul 15, 2026
1e01051
Merge pull request #3309 from blacklanternsecurity/cloudcheck-cloud-t…
liquidsec Jul 17, 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 .github/workflows/docs_updater.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ jobs:
base: dev
title: "Automated Docs Update"
body: "This is an automated pull request to update the documentation."
committer: blsaccess <info@blacklanternsecurity.com>
author: blsaccess <info@blacklanternsecurity.com>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ For more information, see [Targets](https://www.blacklanternsecurity.com/bbot/St

Similar to Amass or Subfinder, BBOT supports API keys for various third-party services such as SecurityTrails, etc.

The standard way to do this is to enter your API keys in **`~/.config/bbot/bbot.yml`**. Note that multiple API keys are allowed:
The standard way to do this is to enter your API keys in **`~/.config/bbot/secrets.yml`**. Note that multiple API keys are allowed:
```yaml
modules:
shodan_dns:
Expand Down
11 changes: 10 additions & 1 deletion bbot/core/config/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path
from contextlib import suppress

from ..helpers.misc import mkdir, error_and_exit
from ..helpers.misc import mkdir, error_and_exit, make_printable
from ..multiprocess import SHARED_INTERPRETER_STATE
from ...logger import colorize, loglevel_mapping, GzipRotatingFileHandler

Expand All @@ -25,6 +25,15 @@ class ColoredFormatter(logging.Formatter):

def format(self, record):
colored_record = copy(record)
# escape control characters in scan-derived text so they can't corrupt the terminal
colored_record.msg = make_printable(colored_record.getMessage())
colored_record.args = None
if colored_record.exc_info and not colored_record.exc_text:
colored_record.exc_text = self.formatException(colored_record.exc_info)
if colored_record.exc_text:
colored_record.exc_text = make_printable(colored_record.exc_text)
if colored_record.stack_info:
colored_record.stack_info = make_printable(colored_record.stack_info)
levelname = colored_record.levelname
levelshort = loglevel_mapping.get(levelname, "INFO")
colored_record.levelname = colorize(f"[{levelshort}]", level=levelname)
Expand Down
1 change: 0 additions & 1 deletion bbot/core/event/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,6 @@ def _minimize(self):
# release container slots; lazy-init pattern means None == empty
self._dns_children = None
self._raw_dns_records = None
self._resolved_hosts = None

def clone(self):
# Create a shallow copy of the event first
Expand Down
6 changes: 4 additions & 2 deletions bbot/core/helpers/depsinstaller/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,14 +453,16 @@ def _core_dep_satisfied(self, command):
For special entries like openssl_dev_headers, use a custom check.
"""
if command == "openssl_dev_headers":
# check for openssl headers by looking for the pkg-config file or header
# look for the actual header. the openssl binary is NOT a proxy: many minimal
# images (e.g. python:3.11-slim) ship openssl but not the -dev headers, and
# anything that compiles against libssl needs the header.
return any(
Path(p).exists()
for p in [
"/usr/include/openssl/ssl.h",
"/usr/local/include/openssl/ssl.h",
]
) or bool(self.parent_helper.which("openssl"))
)
return bool(self.parent_helper.which(command))

async def install_core_deps(self):
Expand Down
15 changes: 15 additions & 0 deletions bbot/core/helpers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2999,3 +2999,18 @@ def is_printable(s):
# Exclude control characters that break display/printing
s = set(s)
return all(ord(c) >= 32 or c in "\t\n\r" for c in s)


_control_char_re = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f]")


def make_printable(s):
"""
Escape non-printable control characters so a string is safe to write to a terminal.

Keeps tab, newline, and carriage return; renders other control bytes (e.g. 0x0e Shift Out) as \\xNN.
Inverse of is_printable().
"""
if not isinstance(s, str):
s = smart_decode(s)
return _control_char_re.sub(lambda m: f"\\x{ord(m.group()):02x}", s)
3 changes: 3 additions & 0 deletions bbot/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ def log_to_stderr(msg, level="INFO", logname=True):
"""
Print to stderr with BBOT logger colors
"""
from bbot.core.helpers.misc import make_printable

levelname = level.upper()
if not any(x in sys.argv for x in ("-s", "--silent")):
msg = make_printable(msg)
levelshort = f"[{loglevel_mapping.get(level, 'INFO')}]"
levelshort = f"{colorize(levelshort, level=levelname)}"
if levelname == "CRITICAL" or levelname.startswith("HUGE"):
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/nuclei.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class nuclei(BaseModule):
}

class Config(BaseModuleConfig):
version: str = Field("3.9.0", description="nuclei version")
version: str = Field("3.11.0", description="nuclei version")
tags: str = Field("", description="execute a subset of templates that contain the provided tags")
templates: str = Field("", description="template or template directory paths to include in the scan")
severity: str = Field("", description="Filter based on severity field available in the template.")
Expand Down
3 changes: 3 additions & 0 deletions bbot/modules/output/stdout.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ async def handle_text(self, event, event_json):
else:
event_str = self.human_event_str(event)

# escape control characters so they can't corrupt the terminal
event_str = self.helpers.make_printable(event_str)

# color findings: severity picks the hue, confidence dims the brightness
if event.type == "FINDING" and isinstance(event.data, dict) and self.use_color:
event_str = self._colorize_finding(event_str, event)
Expand Down
2 changes: 1 addition & 1 deletion bbot/modules/trufflehog.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class trufflehog(BaseModule):
}

class Config(BaseModuleConfig):
version: str = Field("3.95.5", description="trufflehog version")
version: str = Field("3.95.9", description="trufflehog version")
config: str = Field("", description="File path or URL to YAML trufflehog config")
only_verified: bool = Field(True, description="Only report credentials that have been verified")
concurrency: int = Field(8, description="Number of concurrent workers")
Expand Down
12 changes: 8 additions & 4 deletions bbot/modules/virtualhost.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,10 @@ async def finish(self):

self.verbose(f"FINISH METHOD: Starting wildcard check for {host}")
baseline_response = await self._get_baseline_response(event, host, host_ip)
if not baseline_response:
self.debug(f"FINISH METHOD: Failed to get baseline response for {host}, skipping wordcloud check")
self.wordcloud_tried_hosts.add(host)
continue
if not await self._wildcard_canary_check(
host_parsed_url.scheme, host_parsed_url.netloc, event, host_ip, baseline_response
):
Expand All @@ -1015,10 +1019,10 @@ async def finish(self):

async def filter_event(self, event):
if (
"cdn-cloudflare" in event.tags
or "cdn-imperva" in event.tags
or "cdn-akamai" in event.tags
or "cdn-cloudfront" in event.tags
"cloudflare" in event.tags
or "imperva" in event.tags
or "akamai" in event.tags
or "cloudfront" in event.tags
):
self.debug(f"Not processing URL {event.url} because it's behind a WAF or CDN, and that's pointless")
return False
Expand Down
27 changes: 19 additions & 8 deletions bbot/modules/waf_bypass.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ class Config(BaseModuleConfig):
similarity_threshold: float = Field(0.90, description="Similarity threshold for content matching")
search_ip_neighbors: bool = Field(True, description="Also check IP neighbors of the target domain")
neighbor_cidr: int = Field(
24,
28,
ge=24,
le=31,
description="CIDR mask (24-31) used for neighbor enumeration when search_ip_neighbors is true",
)
max_concurrent_checks: int = Field(
100,
ge=1,
description="Maximum number of concurrent bypass-attempt HTTP checks in finish()",
)

meta = {
"description": "Detects potential WAF bypasses",
Expand Down Expand Up @@ -77,15 +82,15 @@ async def handle_event(self, event):
self.cloud_ips.add(ip_str)
self.debug(f"Added cloud-ip {ip_str} to cloud_ips")
else:
self.warning(f"DNS resolution for {domain} returned non-IP result: {ip_str}")
self.verbose(f"DNS resolution for {domain} returned non-IP result: {ip_str}")
else:
self.warning(f"DNS resolution failed for {domain}")

# Detect WAF/CDN protection based on tags
provider_name = None
if "cdn-cloudflare" in event.tags or "waf-cloudflare" in event.tags:
if "cloudflare" in event.tags:
provider_name = "CloudFlare"
elif "cdn-imperva" in event.tags:
elif "imperva" in event.tags:
provider_name = "Imperva"

is_protected = provider_name is not None
Expand Down Expand Up @@ -189,8 +194,10 @@ async def finish(self):
continue

if ip not in waf_ips: # And IP isn't a known WAF IP
ip_bypass_candidates[ip] = domain
self.debug(f"Added potential bypass IP {ip} from domain {domain}")
# first attribution wins so logs are stable across scans
if ip not in ip_bypass_candidates:
ip_bypass_candidates[ip] = f"{domain} (direct)"
self.debug(f"Added potential bypass IP {ip} from domain {domain}")

# if we have IP neighbors searching enabled, and the IP isn't a cloud IP, we can add the IP neighbors to our list of potential bypasses
if self.search_ip_neighbors and ip not in self.cloud_ips:
Expand Down Expand Up @@ -221,7 +228,9 @@ async def finish(self):
self.debug(
f"Added Neighbor IP ({ip} -> {neighbor_ip_str}) as potential bypass IP derived from {domain}"
)
ip_bypass_candidates[neighbor_ip_str] = domain
ip_bypass_candidates[neighbor_ip_str] = (
f"{domain} (neighbor of {ip}/{self.neighbor_cidr})"
)
else:
self.debug(f"IP {ip} is in WAF IPS so we don't check as potential bypass")

Expand All @@ -245,7 +254,9 @@ async def finish(self):
)

self.debug(f"about to start {len(coros)} coroutines")
async for completed in self.helpers.as_completed(coros):
async for completed in self.helpers.as_completed(
coros, max_concurrent=int(self.config.get("max_concurrent_checks") or 100)
):
result = await completed
if result:
confirmed_bypasses.append(result)
Expand Down
2 changes: 0 additions & 2 deletions bbot/presets/kitchen-sink.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ include:

config:
modules:
baddns:
enable_references: True
dnsbrute:
recursive_mutations: true
dnscommonsrv:
Expand Down
2 changes: 1 addition & 1 deletion bbot/presets/waf-bypass.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ config:
waf_bypass:
similarity_threshold: 0.90
search_ip_neighbors: true
neighbor_cidr: 24
neighbor_cidr: 28
69 changes: 69 additions & 0 deletions bbot/test/test_step_1/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,75 @@ async def test_cli_no_color(monkeypatch):
assert os.environ.get("NO_COLOR") == "1"


@pytest.mark.asyncio
async def test_cli_console_control_chars(monkeypatch, capsys):
import logging
from bbot.logger import log_to_stderr
from bbot.core.config.logger import ColoredFormatter

monkeypatch.delenv("NO_COLOR", raising=False)

# scan-derived text carrying the bytes that flip a terminal into its line-drawing charset:
# 0x0e (Shift Out) and an "ESC ( 0" designate-line-drawing sequence
dirty = "matched banner: \x1b(0\x0edeadbeef"

# the stderr log formatter escapes control chars before adding its own colors
formatter = ColoredFormatter("%(levelname)s %(name)s: %(message)s")
record = logging.LogRecord(
name="bbot.modules.nuclei",
level=logging.INFO,
pathname=__file__,
lineno=1,
msg="%s",
args=(dirty,),
exc_info=None,
)
formatted = formatter.format(record)
assert "\x0e" not in formatted
assert "\x1b(0" not in formatted
assert "\\x0e" in formatted and "\\x1b(0" in formatted
# BBOT's own ANSI color codes survive (escaping happens on the message, before colorizing)
assert "\x1b[" in formatted

# same protection on the early bootstrap path
log_to_stderr(dirty, level="INFO")
out, err = capsys.readouterr()
assert "\x0e" not in err and "\x1b(0" not in err
assert "\\x0e" in err

# tracebacks and stack info are also sanitized (an exception carrying scan-derived
# bytes must not reach the terminal unescaped via logger.exception / exc_info=True)
try:
raise ValueError(dirty)
except ValueError:
exc_record = logging.LogRecord(
name="bbot.modules.nuclei",
level=logging.ERROR,
pathname=__file__,
lineno=1,
msg="boom",
args=None,
exc_info=sys.exc_info(),
)
formatted = formatter.format(exc_record)
assert "\x0e" not in formatted and "\x1b(0" not in formatted
assert "\\x0e" in formatted and "ValueError" in formatted

stack_record = logging.LogRecord(
name="bbot.modules.nuclei",
level=logging.ERROR,
pathname=__file__,
lineno=1,
msg="boom",
args=None,
exc_info=None,
)
stack_record.stack_info = f"Stack: {dirty}"
formatted = formatter.format(stack_record)
assert "\x0e" not in formatted and "\x1b(0" not in formatted
assert "\\x0e" in formatted


@pytest.mark.asyncio
async def test_cli_reset_config(monkeypatch, caplog, tmp_path):
from bbot.core import CORE
Expand Down
11 changes: 11 additions & 0 deletions bbot/test/test_step_1/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,17 @@ async def test_helpers_misc(helpers, scan, bbot_scanner, bbot_httpserver):
assert helpers.is_printable("4") is True
assert helpers.is_printable("asdf\x00") is False

# make_printable
assert helpers.make_printable("asdf") == "asdf"
assert helpers.make_printable("ドメイン.テスト") == "ドメイン.テスト"
# 0x0e (Shift Out) is the byte that flips a terminal into its line-drawing charset
assert helpers.make_printable("a\x0eb") == "a\\x0eb"
assert helpers.make_printable("\x00\x1b\x7f") == "\\x00\\x1b\x7f"
# tab, newline, and carriage return are preserved
assert helpers.make_printable("tab\tnl\ncr\r") == "tab\tnl\ncr\r"
# bytes are decoded before escaping
assert helpers.make_printable(b"a\x0eb") == "a\\x0eb"

# punycode
assert helpers.smart_encode_punycode("ドメイン.テスト") == "xn--eckwd4c7c.xn--zckzah"
assert helpers.smart_decode_punycode("xn--eckwd4c7c.xn--zckzah") == "ドメイン.テスト"
Expand Down
17 changes: 17 additions & 0 deletions bbot/test/test_step_1/test_validate_preset.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,23 @@ def test_validate_preset_interactsh_server_rejects_invalid():
assert repr(v) in errs[0].message


def test_bundled_presets_validate_clean():
"""Every YAML preset shipped under bbot/presets/ must pass validation."""
from pathlib import Path
import bbot
from bbot.scanner import validate_preset_file

presets_dir = Path(bbot.__file__).parent / "presets"
failures = {}
for preset_path in sorted(presets_dir.rglob("*.yml")):
errs = validate_preset_file(preset_path)
if errs:
failures[str(preset_path.relative_to(presets_dir))] = [str(e) for e in errs]
assert not failures, "bundled presets failed validation:\n" + "\n".join(
f" {name}:\n " + "\n ".join(msgs) for name, msgs in failures.items()
)


def test_defaults_yml_validates_against_schema():
"""BBOT's own defaults.yml must pass its own validator. Guards against the pydantic
schema (core/config/models.py) silently drifting from defaults.yml -- if a key is
Expand Down
Loading
Loading