2a7142c7223a4f182b140c6d3feab5b2d0ab3d6e
19
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2a7142c722 |
feat(webrtc): recover a dropped connection without a signalling server; release v5.6.0
A chat no longer dies when the network moves under it. A NAT rebind, a lift, a Wi-Fi radio parking itself, a phone that dozed: the session repairs its own network path in place, and the messages typed meanwhile go out when it returns. Recovery is an ICE restart, which renegotiates only the transport path — the DTLS handshake, the session keys and the SCTP association carrying the data channel all sit above ICE and survive it. The renegotiation SDP therefore travels over the existing end-to-end encrypted, SAS-verified channel: no signalling service enters the design, and an attacker who cannot already decrypt the session cannot inject a reconnection. A restart is refused outright unless the DTLS fingerprint in the incoming SDP matches the live session's, so recovery can never re-point a conversation at a different peer. When the path is gone for good the session is ended and its data wiped rather than left half-alive: with no server there is nothing to re-signal through, and a conversation whose transport is gone should not leave its plaintext in an open tab. The two cases where that is already certain are recognised in seconds instead of being retried for two minutes — a channel that has delivered nothing at all since the drop cannot carry a renegotiation, and an ICE agent left bound to a network that no longer exists reports zero candidate pairs on every restart. Judging liveness was the hard part. Silence is not evidence of death: browsers freeze backgrounded tabs outright, and a frozen peer answers nothing while being perfectly healthy. What survives that freeze is ICE consent, which the browser runs in its network stack rather than on the page's thread — so a connected ICE state means a silent peer is asleep, and only a degraded one turns an unanswered probe into a teardown. The grace window before a restart is sized to the browser's own timings: 'disconnected' arrives after ~5s of missed consent responses and is held ~25s before 'failed', and that window exists for self-healing, so restarting at the start of it broke connections that were about to recover. Several long-standing bugs surfaced along the way and are fixed here: - handleHeartbeat() was dispatched to but never defined, so every inbound heartbeat threw a TypeError and peer liveness was never observed at all. - Heartbeats were folded into the 5-minute maintenance cycle instead of running on their own timer, far too coarse to notice a dead path. - ondatachannel can hand over a channel that is already open, so the answering side's 'open' event had been dispatched before the handler was assigned and never fired, leaving that side with no heartbeat, no watchdog and no file-transfer init. The peer whose network was fine kept showing "connected" indefinitely because nothing was running to notice. - Answering a heartbeat required the peer to have finished verifying, but the two sides confirm a SAS code at different moments; for that whole window one of them could not reply and was declared dead on a healthy connection. - Sending on a channel that was not ready returned in silence: the text stayed in the box, nothing was transmitted, and nothing said why. - The send path gated on navigator.onLine and the offline/online events, which report whether an interface exists rather than whether anything is reachable. A tab the OS froze misses the 'online' edge, and this side then queued every message forever: one tick on everything it sent, while incoming messages kept arriving. Sending is now decided by the data channel, and queues drain by polling rather than on an edge, so a missed event cannot strand them. - A false offline modal appeared on a working session, because the offline event was taken at face value. tests/session-recovery.test.mjs covers the state machine, the backoff and its serialisation, the offline hold, the sleeping-peer discriminator and the identity check. |
||
|
|
60bf037ef9 |
Fix the desktop download buttons; release v5.5.4
The buttons still led to a dead GitHub page. 5.5.3 updated one of the two places these links live — the platforms menu on the connection screen keeps its own DOWNLOADS table, and it was missed, so it stayed on 0.1.0. Why it looked like a working link that did nothing: the stale entries used /releases/latest/download/<file>, and GitHub resolves `latest` by redirecting to the newest tag. Once 0.3.0 shipped, a link written for 0.1.0 resolved to /releases/download/v0.3.0/SecureBit.Chat_0.1.0_x64-setup.exe — a file that never existed under that tag. The browser navigated to GitHub and downloaded nothing. Both places now build their URLs from a DESKTOP_VERSION constant with the tag pinned, so the version is written once per file and a link cannot silently become invalid when a new release goes out. Adds tests/desktop-download-links.test.mjs, which fails the build if this drifts again: every source must derive URLs from that constant, /latest/ and hardcoded versions in filenames are rejected, and each generated URL is fetched to prove the asset exists. SKIP_NETWORK=1 skips the fetches offline. |
||
|
|
fcee4216e8 |
Point desktop downloads at 0.3.0; release v5.5.3
The Download block still offered desktop 0.1.0 — the build from before in-app updates, the voice-note fixes and the verification hardening. Windows, macOS and Linux now link to 0.3.0. The version was repeated across three URLs in two different forms: two resolved through /releases/latest/download/ and one pinned a tag. That is how they drifted out of date, so it is now one constant. The tag stays pinned on purpose. Release filenames carry the version, so a /latest/ link breaks the moment a newer release exists, while a pinned tag keeps serving a working installer — the safer way to fail if the constant is ever left behind. |
||
|
|
6152a77b51 |
security: fix SAS verification bypass and unauthenticated frame injection; release v5.5.2
A security review of the transport and verification layers. Every item is a fix
to how untrusted peer input is handled; no features changed.
- SAS verification could be bypassed. `verification_both_confirmed` is an
unauthenticated frame on a channel that is not yet trusted, but it was taken as
proof that both sides had compared their codes — so a peer who completed the
signalling exchange could send it right after the data channel opened and drive
the other side to a "verified" session while the user never looked at the code.
It is now only an acknowledgement: refused unless this side already confirmed
locally, and _setVerifiedStatus() independently rejects any SAS-based
transition without a local confirmation. Holding ECDH-derived keys was never
proof of identity — a MITM has those too.
- Unauthenticated frames could be injected into the chat. A bare
{type:"message"} frame, a raw non-JSON frame and a binary frame were each
decoded and rendered, bypassing decryption, the HMAC check and the verification
gate; the injected text was indistinguishable from a genuine message. Chat
content now reaches the UI only through the authenticated enhanced_message
path.
- A peer could supply the verification code. `sas_code` announcements were
adopted verbatim when no local SAS had been derived yet. They may now only
corroborate the locally derived code.
- Anti-replay never ran. The sequence-number and AAD validators were defined on
SecureKeyStorage instead of the connection manager, so every call site failed
with a TypeError and the sliding replay window was dead code. Moved onto the
manager, wired into the live chat path, and a missing or non-numeric sequence
number now fails closed instead of sailing through the range checks.
- File transfers are gated on verification in both directions. Control frames are
written straight to the data channel by the transfer system; sending was
already gated, receiving now is too.
- Tighter CSP: connect-src and img-src no longer allow arbitrary https: hosts
(nothing in the app talks to a third party), plus base-uri 'none'.
- The SAS is no longer written to logs, and is compared in constant time on every
path. Fixed SecureMasterKeyManager.isUnlocked() testing a field renamed long
ago, so it never actually gated anything.
- Fixed the header showing "Secure undefined%": getRealSecurityLevel() became
reachable for the first time by the move above and returned only per-feature
booleans, while the header renders `level` and `score` directly. It now runs
the same verified scoring as every other consumer.
Adds regression tests for the verification gate, inbound frame authentication and
the security-level shape.
|
||
|
|
b3fcf54670 |
feat(webrtc): end-to-end encrypted voice & video calls with adaptive codecs
Add 1:1 voice and video calling over the existing SAS-verified peer connection. Audio and video tracks ride the same RTCPeerConnection as the chat, bundled onto one DTLS-SRTP transport, so media inherits the session's end-to-end encryption. SDP offer/answer is renegotiated in-band over the verified data channel — no signalling server, so the media's DTLS fingerprints are authenticated end-to-end. Calls are gated on a connected, SAS-verified session. Codecs & adaptation: - Opus tuned for lossy links (in-band FEC, DTX, RED redundancy); audio is bandwidth-prioritised and never throttled. - VP9/AV1 single-encoding SVC with H.264/VP8 fallback; video degrades by spatial/temporal layer. - Runtime NetworkAdaptationController trims video bitrate on loss/RTT and recovers as the link clears — no renegotiation. Live connection-quality indicator (Excellent/Good/Fair/Weak) in the call UI. In-call controls: mute, camera on/off (voice→video upgrade in-band), camera flip, minimize-to-widget, hang up, and accept/decline for incoming calls. Production logging disabled (DEBUG_MODE=false); temporary call diagnostic logger removed. Codec rationale in docs/webrtc-config.md. |
||
|
|
c98a01b1d5 |
Add end-to-end encrypted voice messages; release v5.4.5
- Record voice notes in-browser, sent over the chunked AES-GCM file-transfer channel (per-file session key + signed SHA-256 integrity). - Captured as PCM and encoded to WAV for universal playback (incl. iOS/Safari); auto-accepted and played inline from an in-memory blob, never written to disk. - Composer mic button with live waveform + timer; desktop shows mic + send side by side, mobile swaps mic to send when typing. - CSP media-src now allows blob: so recorded/received audio can play. - Roadmap: Desktop Edition -> 5.0, new 5.5 'Secure Voice & Calls', later milestones shifted; version bumped to 5.4.5. - Update README, docs (security/API/cryptography), and CHANGELOG. |
||
|
|
cbf5c5f834 |
release: v4.9.0 — full redesign + reworked offline mode
Ground-up visual redesign across the entire surface (landing, connection setup, chat header, security verification report, file transfer, PWA install/update/offline dialogs). Offline reworked: store-and-forward queue (send while offline → queued, delivered on reconnect), WhatsApp-style per-message delivery status (sending/sent/delivered/not-sent) via delivery receipts, offline buffering for messages to an offline peer, and offline state no longer leaking into the connection indicator. Resilient chunked file transfer with retransmission and auto-save. README + screenshots added. |
||
|
|
b39f9ecd2c |
release: v4.8.20 secure chat tools — completed, fixed and polished
Completes the messaging controls from v4.8.14 and fixes the bug that made them appear broken for recipients. Fixed: - Per-message metadata was silently dropped for recipients. NotificationIntegration wrapped onMessage and deliverMessageToUI with 2-arg shims that called the originals without the 3rd argument (meta); with notifications enabled, view-once, disappearing timers and unsend all failed on the receiving side. Both wrappers now forward all arguments. Added tests/notification-meta-forwarding.test.mjs. - Chat would not open after SAS: composer props were threaded into the wrong component (EnhancedConnectionSetup vs EnhancedChatInterface) -> ReferenceError nowTick on the verified re-render. Props moved to the chat component. Changed: - Code blocks: lightweight dependency-free syntax highlighting via React nodes (no innerHTML/remote scripts); code mode expands the input; copy auto-clears the clipboard after ~30s. - View-once: configurable visible-after-open time (5s/15s/30s/1m) via meta.onceTtl. - Disappearing timer: duration picker (Off/30s/5m/1h) instead of click-cycling. - Composer toolbar moved next to "Send files"; borderless buttons, brand-orange active state; pickers open upward and are mobile-friendly. - Sender bubble background lightened to rgba(249,115,22,0.05). Removed: - Panic wipe button (disconnect already wipes keys and clears session state). Transport unchanged: per-message metadata travels inside the encrypted envelope, whitelisted/bounded by _sanitizeMessageMeta. Full suite: 19 files, all passing. Docs (README, CHANGELOG) updated; version bumped to 4.8.20. |
||
|
|
cb72b9cb1c |
fix: chat not opening after SAS in v4.8.14 (nowTick scope) — v4.8.15
The new composer props (nowTick, codeMode, view-once/timer setters, unsend/expire handlers) were threaded into EnhancedConnectionSetup, but the message list and composer live in the sibling EnhancedChatInterface. After SAS confirmation the verified-state re-render referenced an out-of-scope `nowTick`, throwing "ReferenceError: Can't find variable: nowTick" so the chat never rendered. Move the prop destructuring and pass-through onto EnhancedChatInterface (where the chat UI actually is) and revert the mistaken additions on EnhancedConnectionSetup. No behavioural change to the v4.8.14 features otherwise. Bumps to 4.8.15. |
||
|
|
15173a9278 |
release: v4.8.14 secure chat tools (code blocks, view-once, disappearing, unsend, panic)
New privacy-focused messaging controls in the composer: - Code blocks: button wraps the message in a fenced block; both peers render a monospace code window with a copy button (clipboard auto-clears after ~30s). Window is built from sanitized text via React nodes — no new XSS surface. - View-once: recipient sees a blurred bubble, reveals on tap, then it is wiped. Honestly cooperative (not screenshot-proof). - Disappearing messages: optional 30s/5m/1h timer auto-deletes on both sides with a live countdown; incoming TTL clamped to [5s, 24h]. - Unsend (delete for everyone) via new MESSAGE_TYPES.message_delete control. - Panic wipe: clears chat, wipes keys and disconnects (behind a confirm). Transport: - Per-message metadata (id / view-once / timer) travels inside the encrypted envelope, not in the sanitized text, so content cannot spoof these controls. - _sanitizeMessageMeta whitelists + bounds metadata on send and receive. - AAD/replay protection, SAS gate and receive-side DOMPurify are unchanged. Adds tests/secure-chat-features.test.mjs (full suite: 17 files, all passing). Bumps version to 4.8.14 across package.json, package-lock.json, manifest.json, index.html, meta.json, README, SECURITY_DISCLAIMER, header and init banner. |
||
|
|
cf36656341 |
release: v4.8.13 message integrity & transport hardening
Bumps version to 4.8.13 across package.json, package-lock.json, manifest.json, index.html, meta.json, README, SECURITY_DISCLAIMER, the site header and the in-app init banner (previously desynced at 4.8.10/4.8.11/4.8.12). Ships the security-review fixes already on main: - removed the over-broad send-path keyword blocklist that silently rejected legitimate messages (real XSS defense remains receive-side DOMPurify) - preserve newlines/tabs/indentation in outgoing message sanitization - stop logging raw AAD (sessionId + keyFingerprint) on validation failure - add Strict-Transport-Security and Permissions-Policy headers - add outgoing-message-integrity regression tests |
||
|
|
6f36fce8c6 |
release: v4.8.12 chat notification & file-transfer UI fixes
fix(file-transfer): announce received file once, not many times The per-transfer lock used a single `if` check, so when 3+ chunk operations queued on the same fileId they awaited the same in-flight lock and then ran concurrently, breaking assembly atomicity. The lock now loops until the slot is free (true serialization) and file assembly is idempotent, so `File received` shows exactly once per file. fix(verification): stop duplicate connection-setup system messages handleVerificationBothConfirmed had no guard, so when both peers sent verification_both_confirmed symmetrically one side ran both the local detection path and the peer-notification path, emitting "Both parties confirmed!" and the verified transition (and "Secure connection established") twice. It now bails out if both confirmations are already recorded. fix(ui): wrap long DTLS fingerprint inside the chat bubble The message text column is a flex child with default min-width:auto, so the long unbroken fingerprint overflowed. Added min-w-0 so break-words can wrap it. chore(release): bump version to 4.8.12 in header, init banner, manifest |
||
|
|
be1d02f1f7 |
release: v4.8.11 file transfer reliability fix
fix(file-transfer): size chunks under the 64KB SCTP message limit Each 64KB chunk became a ~87KB AES-GCM+Base64 file_chunk message, exceeding WebRTC's 64KB SCTP message-size floor. The consent handshake (small messages) succeeded, but no chunk was ever delivered on Safari and cross-browser connections whose SDP omits a=max-message-size, so files never transferred. Send chunk size is now 16KB (~22KB on the wire); inbound chunks up to 64KB stay accepted for backward compat. fix(file-transfer): make MIME advisory, drive validation by extension The client-supplied MIME type is easily spoofed and varies across browsers/OSes, yet was a hard gate: files with an empty MIME or a cross-OS variant (application/x-zip-compressed, image/jpg) were wrongly rejected. Extension allow-list plus BLOCKED_EXTENSIONS is now the boundary; a blatantly foreign MIME on a safe extension is still rejected and per-type size limits still apply. |
||
|
|
39aad6ae8b |
release: v4.8.10 user-configurable STUN/TURN servers
- add header gear + connection-screen entry points to Advanced network settings - render the ICE settings modal at the app root (reachable from any screen via event) - remove the standalone relay-only toggle/description from the start screen (relay-only now lives in the advanced settings panel) - fix crash from referencing main-component state inside EnhancedConnectionSetup - bump version to 4.8.10 across header, manifest, README, init message, disclaimer - document the feature in CHANGELOG and README |
||
|
|
366f080128 |
release: v4.8.9 security hardening patch
- upgrade DOMPurify to patched release (fixes high-severity XSS GHSA-87xg-pxx2-7hvx) - upgrade esbuild build dependency; npm audit now reports 0 vulnerabilities - stop tracking config/ice-servers.js (TURN credentials); add example template - production logger no longer prints error context or info/debug output - bump version to 4.8.9 across header, manifest, README, init message - update SECURITY.md supported-release table to v4.8.x |
||
|
|
d11f250257 |
release: v4.8.8 file transfer consent fix
Complete the mandatory receiver-consent gate that was wired in the backend but never connected to the UI callback chain: - Add the missing onIncomingFileRequest (4th) callback to setFileTransferCallbacks in app.jsx — its absence caused handleFileTransferStart to auto-reject every incoming file. - Remove independent callback registration from FileTransferComponent; the component was overwriting app-level callbacks on mount and nulling all four on unmount, silently breaking progress/received/ error handlers whenever the panel was hidden. - Lift pendingIncomingFiles state to the root component so consent prompts are shown regardless of panel visibility; auto-open the panel on incoming request. - Add getReceivedFileObjectURL / revokeReceivedFileObjectURL on EnhancedSecureWebRTCManager for download buttons in the panel. - Update file-transfer-ui-cleanup regression test to match the new single-owner callback architecture. - All 14 tests pass; clean production build. |
||
|
|
2468cb495e | release: v4.8.7 WebRTC join reliability patch | ||
|
|
01cb25f988 | release: v4.8.6 security hardening patch | ||
|
|
0a42aa13c3 | release: prepare v4.8.5 security hardening release |