Skip to content

[High] Mesh Radio off toggle does not persist, restarts silently on next foreground or BLE event #71

Description

@konkomaji

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

  1. In Settings, turn Mesh Radio off.
  2. Background the app, then foreground it again, or trigger any native BLE status change, for example toggling system Bluetooth off and back on.
  3. 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

  • Turning Mesh Radio off in Settings survives app foreground/background and BLE state change events until the user turns it back on
  • Regression test covers the AppState/BLE listener path not overriding manual off

Found during a codebase review.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions