Syna icon

Syna Security & License Audit Report

Mirrored from NUSV/Syna-NUSV · docs/security/security-audit-v1.0.0.md · synced 2026-09-26 11:28 UTC · original on GitHub

Version audited: v0.9.1 (candidate) Date: 2026-08-10 Scope: full codebase (common / Android / desktop / server / native NDK / tests)


1. Code review & bug-fix summary

Three independent review passes (network/protocol, storage/UI/voice, Shield/server) plus verification against the full test suite. ~60 confirmed issues found; all fixed.

Critical fixes (data safety & lockout)

#IssueFix
1TOTP seed / key pins / audit / fail-counter encrypted with the session key — after lock-out (no session key) 2FA verification, TOFU checks and audit loading could never decrypt → permanent 2FA lockoutNew encryptWithMaster/decryptWithMaster channel; all metadata now uses the master key (Keystore) — data files keep session-key gating
2Honeypot decoy write did rewriteNow() with empty memory → overwrote real chat history permanentlyDecoy only rewrites disk when real messages exist in memory (hasMessagesInMemory), else memory-only
3burnSweepMarks was never written → the 60 s burn TTL fallback was dead code (unviewed burn messages never destroyed)Marks written on enqueue
4Server-history replay was killed by the replay guard (join → old history invisible)routeServerFrame(..., fromHistory = true) bypasses replay for persisted history
5Mesh group file transfer misdetected as "server group disconnected" → group files never sentServer-group detection now uses explicit serverGroupId
6ACK retransmission counter was reset to 0 on every recursion → infinite retransmission; ACK race could resurrect the entry; TEXT never used ACK at allInsert-once + recheck-before-resend + TEXT wired into ACK/retry
7UDP key exchange sent to a dead default port (45878) → encryption could never establish in UDP modeKey frames carry the announcement's real UDP port
864 KB file chunks exceed the 65 KB UDP payload → UDP file transfer unusableChunk size 40 KB in UDP mode
9Debounced rewrite could overwrite disk with empty memory during the 500 ms window (lock/memory-wipe) → history wiperewriteJob cancelled in releaseMemory
10Burn-purge path never persisted → destroyed messages resurrected on restartscheduleRewrite added to remove/purge paths

Shield & state machine

#IssueFix
11Devices without biometrics: every unlock tap counted as brute-force → 5 taps = self-destructERROR_NO_BIOMETRICS/HW_UNAVAILABLE/NO_DEVICE_CREDENTIAL no longer counted
12disablePending residue: unlock after an interrupted disable flow silently turned the Shield offAny new LOCKED transition clears the disable flow
13WATCHDOG_TRIP never cleared → permanent 30 s re-lock loopAuto-cleared when the heartbeat recovers (3 s)
14Self-destruct order: audit event written after the file wipe → audit "resurrected" with a recreated keyShield disabled at the end of full destruct; key pins & voice cache added to wipe list
15Screen-capture callback unregister was a no-op (activity nulled first)Order fixed
16Group admins could promote/demote admins & kick the creator (privilege escalation)SET_ADMIN/REMOVE_ADMIN creator-only on both ends; creator protected
17verifyIdentity no-op when Shield disabled → burn messages un-sendableNull-safe fallthrough
18Burn send verification failure still cleared the inputInput cleared only inside doSend
19Voice recording: no cleanup path (microphone leak, unbounded WAV buffer on desktop)DisposableEffect cancels recording; start-failure handled; AMR-on-desktop play gives explicit notice

Server

#IssueFix
20Zombie sessions (no read timeout) could exhaust the connection cap180 s read timeout
21Kick only closed the first session of a multi-session userAll sessions kicked
22Any member could forge from = groupId (server impersonation)Relay requires from == authenticated userId
23BURN_ACK could purge any message; RECALL could recall anyone's messageOwnership + burn-type checks
24SRV_AUTH_OK history read unsynchronized → CME crashed joinsLocked snapshot
25Expired burn history visible for the first hour after startImmediate sweep on boot

Robustness

  • fileAssemblers → concurrent map; watchdog cascade trip fixed; ShieldGate.beat atomic; persistence writes mutexed (random tmp suffix) + fd.sync; MAGIC boundary; open flag volatile; outbox flush semantics; UDP send exception guard; removeMessageById CAS; purge paths persist.

2. License (contamination) audit

Result: CLEAN — no license contamination

CheckResult
Direct & transitive runtime dependencies100% Apache-2.0 (JetBrains Kotlin/Compose, androidx, com.russhwolf:multiplatform-settings, com.google.guava:listenablefuture, org.jspecify:jspecify)
Copyleft (AGPL/GPL/LGPL) dependenciesNone
Native toolchain (NDK / CMake)Apache-2.0 tooling, no runtime linkage
In-tree third-party codeNone — SHA-256, base32, TOTP, native anti-hook layer, voice recorders all original
Icon assetsassets/icons/Syna_logo_2.png (user-provided project asset)
License fileLICENSE — GPL-3.0 full text present
SPDX headersAll source files carry SPDX-License-Identifier: GPL-3.0-only (3 test files were missing headers — fixed)
AI-generated-code noticeREADME notice present

GPL-3.0 compatibility note: Apache-2.0 dependencies are compatible with a GPL-3.0 project (GPL-3.0 may link Apache-2.0 code; the reverse direction would be the concern, which does not apply here).


3. Test status

  • 76/76 automated tests passing (serial execution; multicast flakiness eliminated)
  • Coverage includes: ACK flow, group admin (kick/mute/admins), TOFU pin/reject/retrust,

TOTP vectors + 2FA flow, hash-chain tamper, gate fail-closed, honeypot, brute-force, watchdog, burn/temp chat, server join/kick/ban, file transfer, offline outbox.