Skip to content

Conversation

@torlando-tech
Copy link
Owner

No description provided.

torlando-tech and others added 8 commits January 13, 2026 21:28
Implements guardian/parental control system allowing a parent device
to control a child's messaging through cryptographically signed commands.

Features:
- QR code pairing with Ed25519 signed payloads (5-minute validity)
- Guardian config and allowed contacts stored in Room database
- Message filtering for incoming/outgoing based on allow-list
- Lock/unlock commands via signed LXMF messages (field 0x80)
- Feature restrictions when locked (Announces tab, Network settings)

Components added:
- GuardianConfigEntity/AllowedContactEntity with DAOs
- GuardianRepository for state management
- GuardianViewModel for UI state
- GuardianScreen for parent QR generation
- GuardianQrScannerScreen for child pairing
- GuardianCommandProcessor for LXMF command handling
- guardian_crypto.py for Ed25519 operations
- Python test suite (16 tests)

Bug fixes during implementation:
- Field name mismatches between Python and Kotlin layers
- RNS.Identity.validate() is instance method, not class method
- QR scanner bottom card padding for nav bar

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add PairedChildEntity and DAO to track paired children on parent device
- Add PAIR_ACK command sent by child after successful pairing
- Process PAIR_ACK on parent side to register paired children
- Add parent management UI showing paired children with lock/unlock buttons
- Implement guardian command signing and sending via LXMF
- Move PAIR_ACK check before duplicate check in MessageCollector
- Support content-based command format (__GUARDIAN_CMD__: prefix)
- Add database migration 31->32 for paired_children table
- Observe paired children Flow for immediate UI updates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix base64/hex encoding mismatch: Python now sends source_hash and
  public_key as base64, EventHandler decodes correctly
- Add guardian command filtering in EventHandler to skip persistence
  for messages with __GUARDIAN_CMD__: prefix (prevents commands from
  appearing in chat)
- Update GuardianCommandProcessor to detect commands in message content
  (new format) in addition to LXMF field 0x80 (legacy format)
- Fix MessageCollector to return after processing guardian commands

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Child-side restrictions (when locked):
- Hide Network subtab in Contacts, show only contacts list
- Disable Manage Interfaces button in NetworkCard
- Disable Manage Identities button in IdentityCard
- Disable Location toggle in LocationSharingCard
- Hide Remove Parental Controls button in GuardianScreen

Parent-side features:
- Add ManageChildContactsDialog for adding/removing contacts from child's allow list
- Fix ALLOW_ADD/ALLOW_REMOVE payload format to match GuardianCommandProcessor expectations
- Add contacts to child's contacts list when parent sends ALLOW_ADD command

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Messages were bypassing the MessageCollector filtering by being
persisted directly in the service process. Add allow-list check
to ServicePersistenceManager.persistMessage() to block messages
from non-allowed contacts when the device is locked.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Syncs guardian/parental control state to the Python layer on app startup
and when guardian commands are processed. This enables potential link-level
filtering when the device is locked.

Changes:
- Add updateGuardianConfig to ReticulumProtocol interface and implementations
- Add AIDL method for cross-process guardian config updates
- Sync guardian config on app initialization (all startup paths)
- Sync after processing LOCK/UNLOCK/ALLOW_* commands
- Add link_established callback with remote_identified_callback

Known limitation (documented in code):
RNS links don't reveal initiator identity at establishment time. The
remote_identified_callback only fires if the peer explicitly calls
link.identify(), which LXMF doesn't do. This means blocked contacts can
still briefly show as "online" before being blocked at the message layer.
Message filtering remains fully effective.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The guardian locked state (isGuardianLocked) was being reset to false
when the app was restarted, even though it was correctly stored in the
database. This caused parental control restrictions (like hiding the
Map nav button) to not apply on app launch.

Root cause: Two issues working together:
1. The StateFlow was initialized with default values before the init{}
   block ran, allowing Compose to observe false before the actual value
   was loaded
2. The loadSettings() combine created a new SettingsState that didn't
   preserve the guardian fields, overwriting them with defaults when
   isLoading became false

Fix:
- Load initial guardian config synchronously during property init using
  runBlocking, before the StateFlow is created
- Initialize _state with the correct guardian values from the start
- Preserve guardian state fields (hasGuardian, isGuardianLocked,
  guardianName, allowedContactCount) in the loadSettings() combine

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Documents the parental control feature for both parents and developers:
- Setup guide with QR pairing flow for parents and children
- Security model explaining Reticulum identity-based authentication
- Child device restrictions when locked (messaging + UI)
- Complete command protocol reference with JSON schemas
- Known limitations and security gaps (online status leak, factory
  reset bypass, signature verification TODO)
- Database schema and source code references

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@torlando-tech torlando-tech added this to the v0.8.0 milestone Jan 29, 2026
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