Summary
Settings has a "Mesh Radio" switch that calls stopRadio() (src/app/settings.tsx:150, mesh.stop() via app-state.tsx:225) when turned off. Nothing persists that the user asked for the radio to stay off. acceptStatus() in src/components/radio-access-gate.tsx:83-95 unconditionally calls startRadio() whenever the native BLE status is 'ready', with no check for user intent.
That function is invoked from two triggers that fire routinely after the user turns the radio off and simply backgrounds or unlocks the phone:
AppState.addEventListener('change', ...) on next === 'active' (radio-access-gate.tsx:116-118)
subscribeToRadioAccess, listening to any native BLE state change event (radio-access-gate.tsx:113-115)
mesh.start() is guarded only by this.running (mesh.ts:185-186), so since the user's stop() set running = false, the next acceptStatus call actually restarts the transport, not a no-op.
Why it matters
The threat model calls RF direction finding the single largest unsolved gap. A protester who deliberately turns the radio off, for example before walking past a checkpoint or a suspected direction finding sweep, locks the phone and puts it away believing it went dark. Any later unlock or foreground event silently starts advertising and scanning again, with no prompt or notification. The Settings toggle will show ON again only if and when they happen to reopen Settings. The person believes they went dark. The phone did not.
Evidence
src/app/settings.tsx:150 switch calls stopRadio()/startRadio(), presented as a persistent user choice
src/lib/app-state.tsx:225 stopRadio is just mesh.stop(), no flag set
src/components/radio-access-gate.tsx:83-95 acceptStatus, unconditional startRadio() on state === 'ready'
src/components/radio-access-gate.tsx:109-119 both AppState change and subscribeToRadioAccess call acceptStatus
src/lib/mesh.ts:185-186 start() guarded only by running
PoC steps
- In Settings, turn Mesh Radio off.
- Background the app, then foreground it again, or trigger any native BLE status change, for example toggling system Bluetooth off and back on.
- Observe
startRadio() fires from acceptStatus, radio resumes advertising and scanning with no user action and no notice.
Proposed fix
Persist explicit user intent (a "radio manually disabled" flag) and have acceptStatus respect it, only auto-starting the radio when the user has not deliberately turned it off. Surface a visible confirmation if the radio is restarted after having been manually stopped.
Done when
Found during a codebase review.
Summary
Settings has a "Mesh Radio" switch that calls
stopRadio()(src/app/settings.tsx:150,mesh.stop()viaapp-state.tsx:225) when turned off. Nothing persists that the user asked for the radio to stay off.acceptStatus()insrc/components/radio-access-gate.tsx:83-95unconditionally callsstartRadio()whenever the native BLE status is'ready', with no check for user intent.That function is invoked from two triggers that fire routinely after the user turns the radio off and simply backgrounds or unlocks the phone:
AppState.addEventListener('change', ...)onnext === 'active'(radio-access-gate.tsx:116-118)subscribeToRadioAccess, listening to any native BLE state change event (radio-access-gate.tsx:113-115)mesh.start()is guarded only bythis.running(mesh.ts:185-186), so since the user'sstop()setrunning = false, the nextacceptStatuscall actually restarts the transport, not a no-op.Why it matters
The threat model calls RF direction finding the single largest unsolved gap. A protester who deliberately turns the radio off, for example before walking past a checkpoint or a suspected direction finding sweep, locks the phone and puts it away believing it went dark. Any later unlock or foreground event silently starts advertising and scanning again, with no prompt or notification. The Settings toggle will show ON again only if and when they happen to reopen Settings. The person believes they went dark. The phone did not.
Evidence
src/app/settings.tsx:150switch callsstopRadio()/startRadio(), presented as a persistent user choicesrc/lib/app-state.tsx:225stopRadiois justmesh.stop(), no flag setsrc/components/radio-access-gate.tsx:83-95acceptStatus, unconditionalstartRadio()onstate === 'ready'src/components/radio-access-gate.tsx:109-119bothAppStatechange andsubscribeToRadioAccesscallacceptStatussrc/lib/mesh.ts:185-186start()guarded only byrunningPoC steps
startRadio()fires fromacceptStatus, radio resumes advertising and scanning with no user action and no notice.Proposed fix
Persist explicit user intent (a "radio manually disabled" flag) and have
acceptStatusrespect it, only auto-starting the radio when the user has not deliberately turned it off. Surface a visible confirmation if the radio is restarted after having been manually stopped.Done when
Found during a codebase review.