Skip to content

Harden reconnection, fix resource leaks, improve security and UX - #1

Merged
hmawla merged 3 commits into
mainfrom
claude/app-audit-optimize-0k8iu5
Jun 9, 2026
Merged

Harden reconnection, fix resource leaks, improve security and UX#1
hmawla merged 3 commits into
mainfrom
claude/app-audit-optimize-0k8iu5

Conversation

@hmawla

@hmawla hmawla commented Jun 9, 2026

Copy link
Copy Markdown
Member

Connection & reconnection:

  • Broadcast reconnect offers to each interface's directed broadcast
    address (/24) in addition to 255.255.255.255, which many routers/APs
    silently drop — the main cause of LAN reconnect never finding a peer.
  • Deduplicate incoming offer datagrams (same offer now arrives via
    several broadcast addresses) so a duplicate no longer spawns a second
    answer connection that breaks the one already completing.
  • Add a 10s timeout to the reconnect auth handshake; a peer that opens
    a channel but never answers the challenge no longer leaves a dangling
    half-trusted connection.
  • Close (not just drop) peer connections on pre-registration failure on
    both the initiator and answerer paths — these previously leaked.
  • Rebind the reconnect UDP socket lazily: a failed bind at startup now
    self-heals on the next supervisor sweep instead of disabling
    reconnect until app restart.
  • Use LAN-only ICE (no STUN) for reconnects so candidate gathering
    completes in milliseconds instead of waiting up to the 5s STUN
    deadline on every attempt.
  • Add fallback STUN servers for pairing.
  • Guard the connection supervisor against overlapping sweeps
    (pool-change events firing mid-sweep double-fired reconnects).
  • Real backpressure in the chunked sender: wait for the data channel
    buffer to drain below 1MB with a stall timeout, and fail fast with a
    typed ConnectionError when the channel is no longer open.

Security:

  • The loopback view-once video server now serves only an unguessable
    random token path and binds unshared; previously any app on the
    device could port-scan 127.0.0.1 and download the decrypted video.
  • Zero-fill pending decrypted images/videos on unpair instead of just
    dropping the references.

UX/UI:

  • Swipe-to-unpair now notifies the remote peer (parity with the
    in-conversation unpair) so they don't keep a dead pairing.
  • Image viewer no longer consumes the view on transient inactive
    lifecycle events (same fix the video viewer already had), and can no
    longer double-pop.
  • Tapping a message notification opens that conversation, including
    when the tap cold-launches the app.
  • Settings: configurable device name (announced during pairing instead
    of the random "Me_xxxx"), and working max-dimension / JPEG-quality
    controls that previously did nothing.
  • Friendlier "Paired" date format on peer tiles.

Tests: fake data channels now report an open state; new test covers
fail-fast send on a closed channel. 48/48 passing, analyzer clean.

https://claude.ai/code/session_016PVQaBGjqmrWPg5zFfpBaB

claude added 3 commits June 9, 2026 21:26
Connection & reconnection:
- Broadcast reconnect offers to each interface's directed broadcast
  address (/24) in addition to 255.255.255.255, which many routers/APs
  silently drop — the main cause of LAN reconnect never finding a peer.
- Deduplicate incoming offer datagrams (same offer now arrives via
  several broadcast addresses) so a duplicate no longer spawns a second
  answer connection that breaks the one already completing.
- Add a 10s timeout to the reconnect auth handshake; a peer that opens
  a channel but never answers the challenge no longer leaves a dangling
  half-trusted connection.
- Close (not just drop) peer connections on pre-registration failure on
  both the initiator and answerer paths — these previously leaked.
- Rebind the reconnect UDP socket lazily: a failed bind at startup now
  self-heals on the next supervisor sweep instead of disabling
  reconnect until app restart.
- Use LAN-only ICE (no STUN) for reconnects so candidate gathering
  completes in milliseconds instead of waiting up to the 5s STUN
  deadline on every attempt.
- Add fallback STUN servers for pairing.
- Guard the connection supervisor against overlapping sweeps
  (pool-change events firing mid-sweep double-fired reconnects).
- Real backpressure in the chunked sender: wait for the data channel
  buffer to drain below 1MB with a stall timeout, and fail fast with a
  typed ConnectionError when the channel is no longer open.

Security:
- The loopback view-once video server now serves only an unguessable
  random token path and binds unshared; previously any app on the
  device could port-scan 127.0.0.1 and download the decrypted video.
- Zero-fill pending decrypted images/videos on unpair instead of just
  dropping the references.

UX/UI:
- Swipe-to-unpair now notifies the remote peer (parity with the
  in-conversation unpair) so they don't keep a dead pairing.
- Image viewer no longer consumes the view on transient `inactive`
  lifecycle events (same fix the video viewer already had), and can no
  longer double-pop.
- Tapping a message notification opens that conversation, including
  when the tap cold-launches the app.
- Settings: configurable device name (announced during pairing instead
  of the random "Me_xxxx"), and working max-dimension / JPEG-quality
  controls that previously did nothing.
- Friendlier "Paired" date format on peer tiles.

Tests: fake data channels now report an open state; new test covers
fail-fast send on a closed channel. 48/48 passing, analyzer clean.

https://claude.ai/code/session_016PVQaBGjqmrWPg5zFfpBaB
Fonts (the "weird letter spacing / gradient numbers" bug):
- Root cause: the theme pulled Inter at runtime via google_fonts. When
  the device was offline (or before the first-launch download finished)
  the primary font was missing and ALL text fell through to the
  NotoColorEmoji fallback, which renders digits as gradient emoji and
  Latin letters with broken metrics.
- Bundle Inter (Regular/Medium/SemiBold/Bold) locally and drop the
  google_fonts dependency, removing the network dependency entirely.
  NotoColorEmoji stays as a fallback but, because Inter covers Latin +
  digits, it is now only consulted for actual emoji.

UI / contrast:
- New central theme (lib/core/theme.dart) with a Material 3 dark scheme
  and shared AppColors tokens, replacing scattered color literals and
  low-contrast Colors.grey/green/orange usages.
- Brightened accent (#8C74FF) and accessible status colors (green/amber)
  that clear WCAG AA on the near-black background.
- Fixed the received chat bubble: it was #12121A, all but invisible
  against the #0A0A0F background. Now a distinct raised surface with
  asymmetric corners and proper timestamp contrast on both bubble types.
- Polished the empty-peers state, encryption banner (added a lock icon),
  offline/reconnecting banners, and setup screen to the new tokens.

Reconnect auth — wire format v2 (breaks pairings with v1 peers):
- Bind the proof-of-key-possession to the DTLS connection: each side now
  signs `nonce || channelBinding`, where channelBinding is a SHA-256 of
  the two DTLS certificate fingerprints (sorted) from the local/remote
  SDP. A direct connection yields the same binding on both ends; a LAN
  relay/MITM must terminate DTLS with its own certificate per leg, so
  the fingerprints — and the binding — differ and auth fails. Closes the
  previously-documented relay gap.
- v2 peers refuse the v1 handshake; documented in CLAUDE.md.

48/48 tests pass, analyzer clean.

https://claude.ai/code/session_016PVQaBGjqmrWPg5zFfpBaB
- .github/workflows/build-apk.yml: on each push (and manual dispatch),
  sets up Java 17 + Flutter 3.41.1, runs flutter pub get, and builds
  with --obfuscate --split-debug-info. Uploads two artifacts: the
  release APK and the de-obfuscation symbol maps (needed to symbolize
  crash traces from obfuscated builds).
- Untrack android/build/ output that had been committed and ignore the
  directory so it cannot come back.

The release build type signs with the debug keystore (Flutter template
default), so CI needs no signing secrets; swap in a real keystore via
key.properties + repository secrets when a store-ready build is needed.

https://claude.ai/code/session_016PVQaBGjqmrWPg5zFfpBaB
@hmawla
hmawla merged commit ae5d322 into main Jun 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants