fix(python): enable BLE only if TREZOR_BLE=1 or --ble given
What changed, and why it matters
This commit changes the Trezor Python library so that Bluetooth Low Energy (BLE) device support is only enabled when the user explicitly sets an environment variable or passes a command-line flag. Previously, BLE transport was always included in device discovery. The change is a defensive hardening measure, not a fix for a known exploit.
No immediate action required. Users relying on BLE should set TREZOR_BLE=1 or use --ble. Review whether BleTransport has any additional security considerations when enabled.
Security signals we found
New opt-in flag for Bluetooth transport
Environment variable and CLI flag gating external wireless transport
Defensive reduction of default attack surface
No changelog entry (minor/internal change)
Evidence from the diff
The patch gates inclusion of BleTransport behind a new ble_enabled parameter, which defaults to true only when the TREZOR_BLE=1 environment variable is set or the --ble flag is passed. It threads this flag through all_transports(), enumerate_devices(), get_transport(), TrezorConnection, and the trezorctl list command. This prevents the library from attempting BLE discovery and using BLE transports by default.
Changed components
python/src/trezorlib/cli/__init__.pypython/src/trezorlib/cli/trezorctl.pypython/src/trezorlib/transport/__init__.pyInspect captured patch +44 / −12
diff --git a/python/src/trezorlib/cli/__init__.py b/python/src/trezorlib/cli/__init__.py
index 695f48619..7b51b4113 100644
--- a/python/src/trezorlib/cli/__init__.py
+++ b/python/src/trezorlib/cli/__init__.py
@@ -139,11 +139,13 @@ class TrezorConnection:
session_id: bytes | None,
passphrase_on_host: bool,
script: bool,
+ ble_enabled: bool,
) -> None:
self.path = path
self.session_id = session_id
self.passphrase_on_host = passphrase_on_host
self.script = script
+ self.ble_enabled = ble_enabled
def get_session(
self,
@@ -208,7 +210,9 @@ class TrezorConnection:
try:
# look for transport without prefix search
- _TRANSPORT = transport.get_transport(self.path, prefix_search=False)
+ _TRANSPORT = transport.get_transport(
+ self.path, prefix_search=False, ble_enabled=self.ble_enabled
+ )
except Exception:
# most likely not found. try again below.
pass
@@ -216,7 +220,9 @@ class TrezorConnection:
# look for transport with prefix search
# if this fails, we want the exception to bubble up to the caller
if not _TRANSPORT:
- _TRANSPORT = transport.get_transport(self.path, prefix_search=True)
+ _TRANSPORT = transport.get_transport(
+ self.path, prefix_search=True, ble_enabled=self.ble_enabled
+ )
_TRANSPORT.open()
atexit.register(_TRANSPORT.close)
diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py
index c23d596d3..5b21a3518 100755
--- a/python/src/trezorlib/cli/trezorctl.py
+++ b/python/src/trezorlib/cli/trezorctl.py
@@ -167,6 +167,13 @@ def configure_logging(verbose: int) -> None:
help="Select device by specific path.",
default=os.environ.get("TREZOR_PATH"),
)
+@click.option(
+ "-B",
+ "--ble/--no-ble",
+ help="Enable/disable support for Bluetooth Low Energy.",
+ is_flag=True,
+ default=(os.environ.get("TREZOR_BLE") == "1"),
+)
@click.option("-v", "--verbose", count=True, help="Show communication messages.")
@click.option(
"-j", "--json", "is_json", is_flag=True, help="Print result as JSON object"
@@ -200,6 +207,7 @@ def configure_logging(verbose: int) -> None:
def cli_main(
ctx: click.Context,
path: str,
+ ble: bool,
verbose: int,
is_json: bool,
passphrase_on_host: bool,
@@ -216,7 +224,9 @@ def cli_main(
except ValueError:
raise click.ClickException(f"Not a valid session id: {session_id}")
- ctx.obj = TrezorConnection(path, bytes_session_id, passphrase_on_host, script)
+ ctx.obj = TrezorConnection(
+ path, bytes_session_id, passphrase_on_host, script, ble_enabled=ble
+ )
# Optionally record the screen into a specified directory.
if record:
@@ -284,16 +294,19 @@ def format_device_name(features: messages.Features) -> str:
@cli.command(name="list")
@click.option("-n", "no_resolve", is_flag=True, help="Do not resolve Trezor names")
-def list_devices(no_resolve: bool) -> Optional[Iterable["Transport"]]:
+@click.pass_obj
+def list_devices(
+ obj: TrezorConnection, no_resolve: bool
+) -> Optional[Iterable["Transport"]]:
"""List connected Trezor devices."""
if no_resolve:
- for d in enumerate_devices():
+ for d in enumerate_devices(ble_enabled=obj.ble_enabled):
click.echo(d.get_path())
return
from . import get_client
- for transport in enumerate_devices():
+ for transport in enumerate_devices(ble_enabled=obj.ble_enabled):
try:
transport.open()
client = get_client(transport)
diff --git a/python/src/trezorlib/transport/__init__.py b/python/src/trezorlib/transport/__init__.py
index a95571e13..9cafde734 100644
--- a/python/src/trezorlib/transport/__init__.py
+++ b/python/src/trezorlib/transport/__init__.py
@@ -17,6 +17,7 @@
from __future__ import annotations
import logging
+import os
import typing as t
from ..exceptions import TrezorException
@@ -95,7 +96,7 @@ class Transport:
CHUNK_SIZE: t.ClassVar[int | None]
-def all_transports() -> t.Iterable[t.Type["Transport"]]:
+def all_transports(ble_enabled: bool | None = None) -> t.Iterable[t.Type["Transport"]]:
from .ble import BleTransport
from .bridge import BridgeTransport
from .hid import HidTransport
@@ -107,16 +108,20 @@ def all_transports() -> t.Iterable[t.Type["Transport"]]:
HidTransport,
UdpTransport,
WebUsbTransport,
- BleTransport,
)
+ if ble_enabled is None:
+ ble_enabled = os.environ.get("TREZOR_BLE") == "1"
+ if ble_enabled:
+ transports += (BleTransport,)
return set(t for t in transports if t.ENABLED)
def enumerate_devices(
models: t.Iterable[TrezorModel] | None = None,
+ ble_enabled: bool | None = None,
) -> t.Sequence[Transport]:
devices: t.List[Transport] = []
- for transport in all_transports():
+ for transport in all_transports(ble_enabled=ble_enabled):
name = transport.__name__
try:
found = list(transport.enumerate(models))
@@ -130,10 +135,14 @@ def enumerate_devices(
return devices
-def get_transport(path: str | None = None, prefix_search: bool = False) -> Transport:
+def get_transport(
+ path: str | None = None,
+ prefix_search: bool = False,
+ ble_enabled: bool | None = None,
+) -> Transport:
if path is None:
try:
- return next(iter(enumerate_devices()))
+ return next(iter(enumerate_devices(ble_enabled=ble_enabled)))
except StopIteration:
raise TransportException("No Trezor device found") from None
@@ -148,7 +157,11 @@ def get_transport(path: str | None = None, prefix_search: bool = False) -> Trans
"prefix" if prefix_search else "full path", path
)
)
- transports = [t for t in all_transports() if match_prefix(path, t.PATH_PREFIX)]
+ transports = [
+ t
+ for t in all_transports(ble_enabled=ble_enabled)
+ if match_prefix(path, t.PATH_PREFIX)
+ ]
if transports:
return transports[0].find_by_path(path, prefix_search=prefix_search)
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.