keep NFC export tag live for repeated probes
What changed, and why it matters
This is a bugfix for the COLDCARD hardware wallet's NFC sharing feature. Previously, when exporting data over NFC, the device would write the tag contents fresh for each tap/scan attempt. Some phones check the tag multiple times before accepting it, so the repeated re-writes could cause the export to fail or behave inconsistently. The fix writes the NFC tag once and keeps it 'live' until the user exits, allowing repeated probes to succeed. There is no direct evidence in the commit that this is a security vulnerability; it appears to be a reliability/usability fix.
Treat as a normal reliability bugfix. No immediate security action required. Reviewers may want to confirm that keeping the tag live does not extend the window for unintended NFC reads, but the change does not appear to alter access controls or data exposure.
Security signals we found
NFC export reliability fix
Repeated NFC probe handling
NDEF tag state persistence
No explicit security claim in commit message
Evidence from the diff
The change modifies shared/nfc.py so that share_loop() performs a single big_write() of the NDEF object before entering the animation loop, rather than calling share_start() (which writes and then immediately waits for activity) on each iteration. A new exit_after_activity parameter is added to ux_animation() defaulting to True; share_loop passes False so the tag stays powered and responsive across multiple RF probes. start_nfc_rx now explicitly passes min_delay=3000 to preserve the previous longer timeout for write/receive mode. The changelog labels it ‘Bugfix: Keep NFC export tag live for repeated probes’.
Changed components
shared/nfc.pyNFCHandler.share_loop()NFCHandler.ux_animation()NFCHandler.start_nfc_rx()NFC export functionalityInspect captured patch +13 / −9
diff --git a/releases/Next-ChangeLog.md b/releases/Next-ChangeLog.md
index a7a615c..9c5afe3 100644
--- a/releases/Next-ChangeLog.md
+++ b/releases/Next-ChangeLog.md
@@ -40,6 +40,7 @@ This lists the new changes that have not yet been published in a normal release.
- Bugfix: Stricter address ownership validation rejects unrecognized payment addresses before wallet search
- Bugfix: Handle malformed NDEF records robustly. Thanks, @Damir
- Bugfix: Ignore `bkpw` if added to backup. Thanks [@dmonakhov](https://github.com/dmonakhov)
+- Bugfix: Keep NFC export tag live for repeated probes
# Mk Specific Changes
diff --git a/shared/nfc.py b/shared/nfc.py
index 7aa78ee..38ce807 100644
--- a/shared/nfc.py
+++ b/shared/nfc.py
@@ -226,10 +226,13 @@ class NFCHandler:
self.set_rf_disable(1)
async def share_loop(self, n, **kws):
+ # Keep one fully-written tag image live until the user exits. Some
+ # phones perform multiple probes while deciding if a tag is NDEF.
+ await self.big_write(n.bytes())
+
while 1:
- done = await self.share_start(n, **kws)
- if done:
- # do not wipe if we are not done
+ aborted = await self.ux_animation(exit_after_activity=False, **kws)
+ if aborted:
await self.wipe(kws.get("is_secret", False))
break
@@ -401,8 +404,9 @@ class NFCHandler:
self.write_dyn(GPO_CTRL_Dyn, 0x01) # GPO_EN
self.read_dyn(IT_STS_Dyn) # clear interrupt
- async def ux_animation(self, write_mode, allow_enter=True, prompt=None, line2=None,
- is_secret=False):
+ async def ux_animation(self, allow_enter=True, prompt=None, line2=None,
+ is_secret=False, exit_after_activity=True,
+ min_delay=1000):
# Run the pretty animation, and detect both when we are written, and/or key to exit/abort.
# - similar when "read" and then removed from field
# - return T if aborted by user
@@ -428,7 +432,6 @@ class NFCHandler:
# (ms) How long to wait after RF field comes and goes
# - user can press OK during this period if they know they are done
- min_delay = (3000 if write_mode else 1000)
while 1:
if dis.has_lcd:
@@ -467,7 +470,7 @@ class NFCHandler:
aborted = False
break
- if last_activity:
+ if exit_after_activity and last_activity:
dt = utime.ticks_diff(utime.ticks_ms(), last_activity)
if dt >= min_delay:
# They acheived some RF activity and then nothing for some time, so
@@ -484,14 +487,14 @@ class NFCHandler:
# - assumpting is people know what they are scanning
# - x key to abort early, but also self-clears
await self.big_write(ndef_obj.bytes())
- return await self.ux_animation(False, **kws)
+ return await self.ux_animation(**kws)
async def start_nfc_rx(self, **kws):
# Pretend to be a big warm empty tag ready to be stuffed with data
await self.big_write(ndef.CC_WR_FILE)
# wait until something is written
- aborted = await self.ux_animation(True, **kws)
+ aborted = await self.ux_animation(min_delay=3000, **kws)
if aborted: return
# read CCFILE area (header)
Why this scored 24/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.