feat(python): do not skip pairing if credential is requested
What changed, and why it matters
This commit fixes a logic bug in the Trezor Python library's pairing flow. Previously, the library would skip pairing whenever the device allowed it, even when the caller also asked for a security credential. Skipping pairing moves the protocol straight to a finished state where credential requests cannot succeed. The change makes the library only skip pairing when no credential is requested, preventing a likely functional failure or error condition during setup.
Treat as a routine correctness fix. Review whether any callers relied on the old skip-always behavior and confirm that credential-request paths now complete pairing as intended. No urgent security response is indicated by the diff alone.
Security signals we found
Protocol-state mismatch between pairing skip and credential request
Functional bug in THP (Trezor Host Protocol) pairing flow
Type-safety overloads added to clarify credential return behavior
Evidence from the diff
In python/src/trezorlib/thp/pairing.py, default_pairing_flow() previously called pairing.skip() whenever SkipPairing was available, regardless of the request_credential flag. The patch adds type overloads for request_credential=True/False and changes the skip condition to ‘not request_credential and SkipPairing in pairing.methods’. This ensures a credential is requested only when pairing is actually performed, because the FINISHED phase reached by SkipPairing cannot satisfy a CredentialRequest without a pre-existing credential.
Changed components
python/src/trezorlib/thp/pairing.pydefault_pairing_flow()SkipPairing pairing methodCredentialRequest handlingInspect captured patch +21 / −2
diff --git a/python/src/trezorlib/thp/pairing.py b/python/src/trezorlib/thp/pairing.py
index b7d49d9f..ac5c2e80 100644
--- a/python/src/trezorlib/thp/pairing.py
+++ b/python/src/trezorlib/thp/pairing.py
@@ -361,6 +361,24 @@ class Nfc(PairingMethod):
self.controller.set_paired()
+@t.overload
+def default_pairing_flow(
+ pairing: PairingController,
+ *,
+ code_entry_callback: t.Callable[[], str] | None = None,
+ request_credential: t.Literal[True] = True,
+) -> Credential: ...
+
+
+@t.overload
+def default_pairing_flow(
+ pairing: PairingController,
+ *,
+ request_credential: t.Literal[False],
+ code_entry_callback: t.Callable[[], str] | None = None,
+) -> None: ...
+
+
def default_pairing_flow(
pairing: PairingController,
*,
@@ -370,9 +388,10 @@ def default_pairing_flow(
if pairing.is_paired():
return
- if SkipPairing in pairing.methods:
+ # we currently can't get a credential if pairing is skipped
+ if not request_credential and SkipPairing in pairing.methods:
pairing.skip()
- return
+ return None
if CodeEntry not in pairing.methods:
raise NotImplementedError(
Why this scored 26/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.