[2.6.5] - 2026-05-01
Summary
Blue-Tap 2.6.5 brings a global, modular dry-run capability across every command. A single root-level --dry-run flag (or $BLUE_TAP_DRY_RUN=1) makes any command print the resolved plan and exit without touching hardware or sending packets — destructive modules are previewed without their CONFIRM=yes gate and session writes are skipped. Future modules inherit this for free: the framework Invoker short-circuits any module that doesn't opt into a richer dry-run path. The fuzzer's existing seed-replay dry-run is preserved (it remains the canonical opt-in via supports_dry_run = True). The release also ships a TOML-based user config loader, a static dependency graph for registered modules, three new bundled playbooks, a third-party plugin template, OBEX-style import/export for the on-disk fuzz corpus, and copy-paste remediation hints in blue-tap doctor.
Added — Global dry-run
- Root
--dry-runflag onblue-tap. Honored by every workflow command (discover,recon,vulnscan,exploit,dos,extract,fuzz,auto,fleet,run-playbook,run). $BLUE_TAP_DRY_RUN=1environment-variable equivalent for CI usage and to scope dry-run acrossrun-playbookstep re-invocations.RunContext.dry_runfield threaded fromInvoker.invoke(..., dry_run=True)into every module call. Single source of truth — modules readctx.dry_run, never raw flags.Module.supports_dry_runclass attribute (defaultFalse). WhenFalse,Invokershort-circuits with a synthesized "planned" envelope (module_outcome="not_applicable",module_data["dry_run"]=True) before instantiating the module — so every existing module inherits dry-run automatically. When a module sets it toTrue, theInvokercallsrun()withctx.dry_run=Trueand the module owns the dry-run path (e.g.fuzzing.engineuses MockTransport).- Destructive-gate bypass in dry-run. Destructive modules (KNOB, BIAS, BLUFFS, CTKD, all DoS checks, fuzzing) preview cleanly without
CONFIRM=yes— the synthesized envelope still surfacesdestructive=trueso the operator sees the danger label. - Session-write skipping.
Invoker.invoke_with_logging(..., dry_run=True)no longer persists the run envelope to the session log — a no-op preview is not worth saving. dry_run_plannedadded to the canonical CLI event taxonomy.- Hardware-direct command guards for every command that bypasses
Invoker:adapter list/info/up/down/reset/set-name/set-class/firmware-install/firmware-init/firmware-spoof/firmware-set/firmware-status/firmware-dump/connection-inspect/connections,spoof,doctor,report,fuzz cve,fuzz replay,fuzz minimize,fuzz corpus generate/minimize/import, andfuzz crashes replay. Each prints "would do X" and exits cleanly without opening raw HCI sockets, probing the environment, or writing seed files. - Playbook executor propagation.
run-playbookscopesBLUE_TAP_DRY_RUN=1across each step's re-enteredmake_contextcall (try/finally so the prior value is always restored). Target/HCI resolution is short-circuited to a placeholder so dry-run never blocks on the interactive picker. - Fuzz back-compat.
fuzz campaign --dry-runandfuzz benchmark --dry-runkeep working with their existing semantics; both now also OR-honor the root flag and env var. Seed-replay byte-level reproducibility invariant preserved. - Per-protocol fuzz dry-run.
fuzz sdp-deep,l2cap-sig,rfcomm-raw,ble-att,ble-smp,bnep,obex, andat-deeproute through a single_run_via_engine()seam that swaps the real transport forMockTransportwhen dry-run is active — adding a new per-protocol subcommand inherits dry-run without further wiring. - Dry-run wall-clock cap.
fuzz campaign --duration 1h --dry-runwould otherwise loopMockTransportfor the full hour. The engine now caps dry-run runs to 5 seconds and 100 iterations (whichever comes first). Both caps are env-overridable viaBT_TAP_DRY_RUN_MAX_DURATION_SECONDSandBT_TAP_DRY_RUN_MAX_ITERATIONSfor CI tuning.
Added — User config loader
~/.config/blue-tap/config.toml— operators no longer have to repeat--hci hci0 -s mysessionon every invocation. Resolution order:--config /path/to/file.toml→$BLUE_TAP_CONFIG→$XDG_CONFIG_HOME/blue-tap/config.toml→~/.config/blue-tap/config.toml. CLI flags always override config values (Click'sdefault_mapprecedence). No file present → behaviour identical to prior versions.- Schema (minimal):
[default]section withhciandsessionkeys. Unknown sections or keys raiseConfigErrorat load time with the offending path and key name in the message — no silent typos. Validation lives at the file boundary; the rest of the CLI consumes the parsedBlueTapConfigdataclass.
Added — Module dependency graph
blue_tap.framework.registry.dependency_graph— best-effort static graph of which other registered modules each module imports, computed lazily from theentry_point-referenced source file plus every.pyin the same package.blue-tap info <module>is the first consumer and now listsDepends on:andUsed by:per module.- Detection limits surfaced in code, not hidden. Only
from blue_tap.modules.<family>.<name>[.subpath]imports count; string-based dispatch (importlib.import_module, entry points,getattr) is invisible.try/except ImportErrorandif TYPE_CHECKINGimports still count as potential runtime dependencies. Self-imports inside the same package are excluded.
Added — Plugin template
examples/plugin-template/— minimal working example of a third-party Blue-Tap plugin. Copy the directory, rename the package, edit the module body, andpip install .to register an out-of-tree module. Demonstrates theblue_tap.modulesentry-point group, theModulesubclass shape, family/outcome wiring, and theRunEnvelopereturn contract.pyproject.tomlpinsblue-tap >= 2.6.5so plugin authors get a clear failure if they install against an older release.
Added — Bundled playbooks
ble-assessment.yaml— BLE-only sweep. Advertisement scan → GATT enumeration → BLE-specific CVE checks. ~5 minutes, low risk (active GATT probing only after a target is selected).dos-campaign.yaml— Structured denial-of-service and resilience testing. Discovery → vulnscan → full DoS check series with per-check recovery wait. ~10 minutes, high risk (may render target unresponsive; requires--yes).post-exploit-data.yaml— Post-pairing extraction. Re-enumerate SDP → pull contacts (PBAP) → pull messages (MAP) → pull files (OPP/OBEX) → AT-channel responses. ~15 minutes, medium risk (requires existing bond).
Added — Fuzz corpus archive support
Corpus.export_to_tarball(output_path, protocol=None)— bundles the on-disk corpus into a gzipped tarball whose layout mirrorsbase_direxactly (top-level entries are protocol directories, each with*.binseeds and an optionalinteresting/subdirectory). RaisesFileNotFoundErrorif the corpus directory doesn't exist;ValueErrorif a non-existent protocol is requested. Returns{output_path, size_bytes, seeds_exported, protocols}.Corpus.import_from_tarball(tarball_path)— counterpart importer. Rejects path-traversal entries (.., absolute paths) before extraction and usestarfile'sfilter="data"mode as defence-in-depth. Returns{seeds_imported, protocols, size_bytes}.fuzz corpus export TARBALL [-p PROTOCOL]andfuzz corpus import TARBALL— CLI surfaces for the above. Both honour--dry-run(preview the operation without writing or extracting). Failed seed-generation passes now log via the standardloggingchannel instead of swallowing the exception silently.
Added — blue-tap doctor remediation hints
→ fix:lines under every missing tool, service, or capability limitation. The hint is a copy-paste shell command (sudo apt install bluez bluez-tools,sudo systemctl enable --now bluetooth,systemctl --user enable --now pipewire pipewire-pulse, etc.). Hint dictionaries (TOOL_FIX_HINTS,SERVICE_FIX_HINTS) live alongside the detector so adding a new check naturally pairs with adding its remediation.limitation_hintsdict added todetect_profile_environment()output (alongside the existingtools,services,adapters,obex,summarykeys — backward compatible). Each capability limitation message maps to its remediation command (empty string when no automatic fix is meaningful).
Tests
tests/test_dry_run_rollout.py(17 tests) — framework hooks,Invokershort-circuit, destructive-gate bypass,supports_dry_runopt-in, env-var propagation, per-family smoke tests across discovery / reconnaissance / assessment / exploitation / fuzzing.tests/test_config_loader.py— TOML loader: precedence chain, unknown-key rejection with path in error message, malformed TOML, missing file, env-var override.tests/test_dependency_graph.py— graph builder: known-good imports counted, string-based dispatch ignored, self-imports excluded, lazy build, cache stability.tests/test_fuzz_corpus_io.py— tarball round-trip: layout preservation, path-traversal rejection, single-protocol filter, missing-source error.tests/test_doctor_fix_hints.py— every documented tool/service has a hint; hints are non-empty strings;limitation_hintsdict shape.tests/test_playbook_dispatch.py— three new bundled playbooks parse cleanly, list underrun-playbook --list, and dispatch the expected step sequence.tests/test_plugin_template_smoke.py— the bundled plugin template installs, registers its module, and produces a validRunEnvelope.- Test counts: 456 → 581 passing, 0 failing. Lint (
ruff) clean.