fix(core): cache host and app name also at credential-based connection
What changed, and why it matters
This commit fixes a bug in Trezor's T3W1 hardware wallet where, if a user first paired their device over USB instead of Bluetooth, the device failed to remember the computer/app name for later use. The fix makes sure the host and app name are saved during credential-based pairing even when there is no Bluetooth MAC address. There is no direct evidence this is a security vulnerability, but missing pairing metadata could weaken future security checks or user trust.
Treat as a product-correctness fix rather than an active security flaw. Review whether downstream security decisions rely on cached host/app info being present, and consider adding a regression test for USB-first credential-based pairing to ensure metadata is cached.
Security signals we found
Fixes incomplete caching of pairing metadata in THP (Trezor Host Protocol)
USB-first pairing path previously left host/app name uncached
Credential-based pairing now mirrors earlier behavior for address-based pairing
No changelog security framing; marked as [T3W1] product fix
Evidence from the diff
The change adds a call to cache_host_info() inside the credential-based pairing path in received_message_handler.py, passing mac_addr (which may be None on USB) and the host/app names from the credential metadata. It also updates type hints in pairing.py and interface_context.py to reflect that connected_addr() can return None. The changelog labels this as a fix for caching THP host info during credential-based pairing.
Changed components
core/src/trezor/wire/thp/received_message_handler.pycore/src/apps/thp/pairing.pycore/src/trezor/wire/thp/interface_context.pyTrezor T3W1 THP pairing subsystemInspect captured patch +17 / −2
diff --git a/core/.changelog.d/5867.fixed b/core/.changelog.d/5867.fixed
new file mode 100644
index 00000000..a7d1e2a7
--- /dev/null
+++ b/core/.changelog.d/5867.fixed
@@ -0,0 +1 @@
+[T3W1] Cache THP host info also during credential-based pairing.
diff --git a/core/src/apps/thp/pairing.py b/core/src/apps/thp/pairing.py
index dbe98935..d67b6c03 100644
--- a/core/src/apps/thp/pairing.py
+++ b/core/src/apps/thp/pairing.py
@@ -50,6 +50,7 @@ if __debug__:
from trezor import log
if TYPE_CHECKING:
+ from buffer_types import AnyBytes
from typing import Any, Callable, Concatenate, ParamSpec
from trezorui_api import UiResult
@@ -117,7 +118,8 @@ async def handle_pairing_request(
if not message.app_name:
raise DataError("Missing app_name.")
- peer_addr = ctx.channel_ctx.iface_ctx.connected_addr()
+ # will be `None` on USB interface, to be ignored by `cache_host_info()`
+ peer_addr: AnyBytes | None = ctx.channel_ctx.iface_ctx.connected_addr()
await ui.show_pairing_dialog(message.host_name, message.app_name)
ctx.host_name = message.host_name
ctx.app_name = message.app_name
diff --git a/core/src/trezor/wire/thp/interface_context.py b/core/src/trezor/wire/thp/interface_context.py
index 57aec647..70ae0d00 100644
--- a/core/src/trezor/wire/thp/interface_context.py
+++ b/core/src/trezor/wire/thp/interface_context.py
@@ -234,7 +234,7 @@ class InterfaceContext:
header = PacketHeader.get_error_header(cid, length)
return self.write_payload(header, msg_data)
- def connected_addr(self) -> bytes | None:
+ def connected_addr(self) -> AnyBytes | None:
"""
Return peer MAC address (if connected).
diff --git a/core/src/trezor/wire/thp/received_message_handler.py b/core/src/trezor/wire/thp/received_message_handler.py
index dcfc4fdd..c551281f 100644
--- a/core/src/trezor/wire/thp/received_message_handler.py
+++ b/core/src/trezor/wire/thp/received_message_handler.py
@@ -31,6 +31,8 @@ from .crypto import PUBKEY_LENGTH, Handshake
from .session_context import SeedlessSessionContext
if TYPE_CHECKING:
+ from buffer_types import AnyBytes
+
from trezor.messages import ThpHandshakeCompletionReqNoisePayload
from .channel import Channel
@@ -125,6 +127,9 @@ async def _handle_state_handshake(
payload = await ctx.recv_payload(control_byte.is_handshake_comp_req)
+ # will be `None` on USB interface, to be ignored by `cache_host_info()`
+ mac_addr: AnyBytes | None = ctx.iface_ctx.connected_addr()
+
if not config.is_unlocked():
raise ThpDeviceLockedError
@@ -175,6 +180,13 @@ async def _handle_state_handshake(
host_static_public_key,
)
if paired:
+ from trezor.wire.thp.paired_cache import cache_host_info
+
+ cache_host_info(
+ mac_addr=mac_addr,
+ host_name=credential.cred_metadata.host_name,
+ app_name=credential.cred_metadata.app_name,
+ )
trezor_state = _TREZOR_STATE_PAIRED
ctx.credential = credential
else:
Why this scored 35/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.