v2.6.2 Bug Fixes
[2.6.2] - 2026-04-17
Summary
Blue-Tap 2.6.2 is a small follow-up to 2.6.1 that fixes post-USB-reset verification on RTL8761B adapters and wires up automated GitHub Pages deployment for the docs site.
Fixed — Hardware
DarkFirmwareManager.usb_reset_and_wait()— new method that resets the RTL8761B, waits for teardown, then pollsfind_rtl8761b_hci()until the adapter re-enumerates and returns the newhciXname. The kernel can re-enumerate the adapter under a different index after reset (e.g.hci8 → hci0); callers that verified post-reset state (is_darkfirmware_loaded,get_current_bdaddr) were probing the pre-reset name and reporting "verification inconclusive" even when install/patch succeededfirmware-install(install + restore),patch_bdaddr, and the startup auto-install prompt now use the re-enumeratedhcifor verification and user-facing messages
Build
- Version bumped to
2.6.2 .github/workflows/docs.yml— new workflow auto-builds MkDocs site with--strictand deploys to GitHub Pages on every push tomainpyproject.toml— license metadata format fixed to satisfy PEP 639 (SPDX expression only, no classifier duplication)
[2.6.1] - 2026-04-17
Summary
Blue-Tap 2.6.1 is a stability, ergonomics, and correctness release on top of 2.6.0. The CLI now supports interactive target selection across every target-taking command (omit the address to get a device picker); the hardware layer picks up a second RTL8761B dongle variant and hardens the DarkFirmware watchdog against concurrent HCI access; several modules that silently "succeeded" while producing wrong results now return honest envelopes; and the module loader can actually unregister + re-import plugin classes instead of leaking descriptors on reload.
Added — CLI Ergonomics
- Interactive target picker —
vulnscan,recon,exploit,extract,dos,fleet,adapter infonow acceptTARGETas optional. When omitted (or when the argument doesn't match a MAC), a device scan runs and presents a numbered picker invoke_or_exit()(interfaces/cli/_module_runner.py) — new helper used by all facade commands; failed module runs now exit with status1instead of0, soblue-tapworks correctly in shell pipelines and CI- Command-name-aware proxy usage hints —
dos-<check>,vuln-cve-*,vuln-<check>,recon-hci-capture,recon-snifferproxy commands now print the exact real-command invocation (e.g.blue-tap dos TARGET --checks bluefragorblue-tap vulnscan TARGET --cve CVE-2020-0022) instead of a generic "<group> <subcommand>" template fuzz cve— registered proxy command for replaying a known CVE fuzz patternrun-playbookadded to no-session command allow-list soblue-tap run-playbook --listworks without an active sessionauto— docstring rewritten to state explicitly that this is a 4-module shortcut (SDP recon → vuln_scanner → KNOB exploit → PBAP extract → report), not a "full pentest"; report generation now uses the active session's data correctly and writesreport.htmlinto the session directory
Added — Framework
ReportAdapter.priority— adapters now carry an integer priority (lower = runs first). Plugin adapters default to50; the built-invulnscanfallback adapter is pinned to200so third-party adapters are always tried firstget_report_adapters()— returns adapters sorted by priority, unifying built-in + plugin-registered adapters;interfaces/reporting/generator.pynow iterates through this function instead of the staticREPORT_ADAPTERStuple (plugin adapters were previously ignored during report generation)ModuleRegistry.unregister(module_id)— returnsTrueif the descriptor was present; used by the loader to clean up onreload=TrueModuleLoader.load_plugins(reload=True)— now unregisters previously-loaded descriptors and evicts cached modules fromsys.modulesbefore re-importing, so plugin upgrades no longer leak stale classesfunction_module()decorator — the generated_FunctionModuleclass is now injected into the calling module's namespace so itsentry_pointstring resolves at import time; this previously failed silently for any module defined via@function_module- Recon outcome taxonomy —
VALID_OUTCOMES_BY_FAMILY["reconnaissance"]extended withundetermined,partial_observation,auth_required,not_found,not_connectable,timeout,no_resultsto cover the actual envelopes recon modules were already emitting build_recon_execution(module_id=...)— new optional argument so recon executions can record their fully-qualified module ID (e.g.reconnaissance.campaign) instead of justreconnaissance- Session timestamps in UTC —
framework/sessions/store.pynow usesdatetime.now(timezone.utc).isoformat()via a single_now_iso_utc()helper; prevents naïve-local timestamps from drifting across hosts OptPath.validate()— returnsNonefor optional paths with no default instead of raisingOptionError, letting modules distinguish "path was given" from "path was not set"- Plugin discovery diagnostics —
ModuleRegistry.load_entry_points()now logs a warning with traceback when discovery fails instead of swallowing the exception silently
Added — Hardware
- Second RTL8761B dongle variant —
firmware.pynow detects both2357:0604(TP-Link UB500) and0bda:8771(generic Realtek) via a newRTL8761B_VID_PIDStuple;is_darkfirmware_loaded()and USB presence checks iterate both VID:PIDs - DarkFirmware watchdog thread safety —
DarkFirmwareWatchdognow uses athreading.Lockaround_reinit_count,_last_reinit, and a new_reinit_in_progressflag; prevents double-reinit races when a USB event fires during an in-flight reinit - HCIVSCSocket.recv_event() concurrency guard — raises
RuntimeErrorif called from an external thread while the LMP monitor loop is running on the same socket; two concurrent readers were causing event-frame corruption adapter_up,adapter_down,adapter_reset— now auto-resolvehci=Noneviaresolve_active_hci()and return a structured error dict if no adapter can be discovered, instead of NPE-ing downstream- L2CAP DoS socket binding —
_l2cap_raw_socket()now binds to the requested HCI's local address before connecting, so DoS traffic goes out the intended adapter in multi-dongle setups
Fixed — Hardware
- MAC spoofer fallback —
spoof_rtl8761b()now falls through from RAM patch to firmware-file patch when RAM patch reports success but the adapter still reports the wrong BDADDR (previously returnedverified=Falsewithsuccess=True, confusing the caller) - MAC spoofer file-write permission —
save_original_mac()now catchesPermissionErrorand emits a user-facing warning pointing at the root-owned state file, instead of raising into the caller - Firmware RAM-patch length check —
patch_bdaddr_ram()now requires exactly 4 bytes back fromvsc.read_memory()before attempting the file-patch fallback, instead of accepting any byte count ≥4 - Firmware file-read leak —
is_darkfirmware_loaded()now useswith open(...)for modalias probes (previously leaked file descriptors in the multi-adapter loop)
Fixed — Modules
assessment.fleet— UUID matching now canonicalizes short form,0xprefix, and full 128-bit Base UUID; previously only matched exact"0x111f"literal, so IVIs advertising"111f","0000111f-0000-1000-8000-00805f9b34fb", or uppercase variants were misclassified as generic headsetsassessment.vuln_scanner._check_blueborne— removed thebluetoothd --versionprobe (it reports the local stack version, not the target's); now relies on SDP-extractedBlueZ X.Ystrings only. Removes a class of false-positive BlueBorne findings on assessments run from a Kali attackerexploitation.encryption_downgrade—results["success"]now reflects whether at least one downgrade method actually worked; previously hardcodedTrueeven when the target rejected every methodexploitation.hijack— bails out of the attack chain when recon fails; was previously entering SSP/pairing with no target datareconnaissance.sdp.search_services_batch— UUID matching normalizes0x-prefixed hex and checks the fullclass_id_uuidslist against candidate service records; previously missed services whose class IDs used a different textual form than the filter UUIDreconnaissance.fingerprint—vendorderivation now usesmanufacturer(the actual output field) instead of a non-existentchipset.vendornested key, sohas_signalcorrectly flips on vendor-only fingerprintsreconnaissance.hci_capture— capture loop uses a clampedremainingtime slice and exits cleanly whenremaining <= 0, preventing a hang at the boundary ofdurationreconnaissance.campaign—_cleanup_tmp_artifact()unlinks the tempfile on all four capture-step failure paths (was leaking empty PCAPs into the session dir)reconnaissance.prerequisites— prerequisitemissinglist now filters by a newapplicableflag per check, so a BLE-only target no longer reports DarkFirmware/LMP prerequisites as "missing"post_exploitation.pbap—extract_allnow deduplicatesPBAP_PATH_ALIASESto 9 unique canonical paths instead of pulling the same phonebook 28 times (one for every alias key)post_exploitation.map_client— allself.sock.send()calls go through a_send()helper that raises if not connected; addsNoneguards on_setpath_root,_setpath_down,_recv_response; message bodyLENGTH:header now reflects byte length of the UTF-8 encoded body, not character countpost_exploitation.bluesnarfer— auto-discovers the AT RFCOMM channel via SDP (triesDial-up Networking,Serial Port,DUN,SPP) instead of raisingOptionErrorwhen CHANNEL was not supplied; also preserves original case on raw AT commands (was uppercasing vendor-specific payloads and breaking them)post_exploitation.a2dp—recordaction now usescapture_a2dp()(was calling an undefinedrecord_car_mic());bytesfield in result reflects actual on-disk size after capture;set_sink_volume()failure is now a warning instead of an uncaught exceptionpost_exploitation.hfp— codec-negotiation response now distinguishesERROR(rejected) from silent fallback;dial/answer/hangupsuccess flags reflect"ERROR" not in responserather than truthy-ness alone;silent_call()guards on socket being connected before issuing ATDfuzzing.engine— protocol names now run throughcanonical_protocol()which maps operator aliases (pbap,map,opp,att,smp,hfp,phonebook,sms) to canonical transport keys; mutator fallback generates fresh random bytes when the mutator returns an empty payload; strategy-unavailable path now updatesself.strategyso the envelope records what actually ran;CrashDBis closed in afinallyblock in_finalize()fuzzing.health_monitor— removed unused_check_zombie(protocol_responses)and replaced it with a per-protocol consecutive-failure tracker (_protocol_consecutive_fails); a target is declaredZOMBIEwhen ≥2 tracked protocols have ≥3 consecutive failures while L2CAP is still alive;update()now acceptsprotocol=to identify which protocol's response was observedfuzzing.campaign—CONTINUE=trueresumes an existingfuzz/campaign_state.json(falls back to a fresh campaign if the file is missing or corrupt); transport overrides are rebuilt from the resumed protocol list, not the CLIPROTOCOLSoptionfuzzing.cli_commands(replay) — delegates toCrashDB.reproduce_crash(transport)instead of duplicating the recv/timeout logic inline; multi-packet crashes now report packet count before replayfuzzing.state_inference— replaces non-deterministichash(indicator)withmd5(...)[:2]so AT state IDs are stable across Python interpreter runs (was breaking state-machine convergence on restart)fuzzing.lmp_state_tests— thekey_size_after_start_enctest now uses a fixed 16-byte hex seed instead ofos.urandom(16)so the test is reproduciblefuzzing.transport—LMPTransport._establish_acl()closes the probe socket in afinallyblock instead of relying on successful-path cleanuputils.bt_helpers.get_adapter_state— escapeshcibefore embedding it in thepgrepregex; previously vulnerable to weird adapter names injecting regex metacharacters
Fixed — CLI
blue-tap run <module>— missing / destructive / option-error conditions now exit with status1instead of falling through to status0; "see available modules" hint now points atblue-tap search(the real command) instead of the removedlist-modulesblue-tap run-playbook— no longer replaces the lowercase literaltargetinside command strings (broke any module that hadtargetas a legitimate substring in an argument); only the uppercaseTARGETsentinel and the explicit{target}placeholder are substitutedadapter up/down/reset/set-name— raiseClickExceptionon failure so exit status matches;inforaisesClickExceptioninstead of silently returning when the adapter doesn't exist_module_runner.resolve_target— validates theTARGETargument shape with a MAC regex; if the first positional token is a subcommand name (e.g.blue-tap recon sdp) the picker fires instead of treating the subcommand as an address
Fixed — Playbooks
full-assessment.yaml— updated to v2.6 CLI grammar:recon {target} rfcomminstead ofrecon rfcomm-scan {target},sniff -m lmpinstead oflmp-sniff,-ainstead of-iivi-attack.yaml— exploit commands now use theexploit {target} <sub>form;-ainstead of-ilmp-fuzzing.yaml— removed the deprecated standalonefuzz lmpstep; campaign uses the-p <proto>repeatable flag (matches current CLI) andcoverage_guided(underscore form)passive-recon.yaml,quick-recon.yaml—scan classic/blereplaced withdiscover classic/ble; recon subcommands reordered torecon {target} <sub>form
Fixed — Tests
test_cli_facades—vulnscan/dos"requires target" tests replaced with "interactive picker when no target" to reflect the new optional-target behaviortest_userflow_dos,test_userflow_exploitation_bias,test_userflow_exploitation_knob— expect exit code1for unknown modules and blocked destructive runs (previously accepted0due to the silent-failure bug)
Fixed — Docs
- CLI reference — rewritten to show
[TARGET]as optional acrossvulnscan,recon,exploit,extract,dos; options table updated (-a, --hcireplaces the old-a, --adapter/-i, --adapterforms); added interactive-picker callout - Navigation — mkdocs sidebar renames "Reference" to "Technical Reference"
- Guide pages —
reconnaissance,vulnerability-assessment,denial-of-service,fuzzing,post-exploitation,sessions-and-reporting,automation,exploitationupdated to match the v2.6 command grammar;docs/developer/architecture.mdexpanded with framework-layer details - README and target/README — all example invocations updated to
discover/recon {target} <sub>/-agrammar; fuzz examples use-prepeatable andfuzz crashes list
Build
- Version bumped to
2.6.1 pyproject.toml— removed strayasyncio_default_fixture_loop_scope(no async tests in the suite).gitignore— addssite/(mkdocs build),fuzz/(corpus + crashes.db),map_dump/,x/,hci_capture.pcap,tmp_dos_review.*to avoid committing operator artifacts