From 02380a7ecda694b8808dfaab832745e1d1c2dacc Mon Sep 17 00:00:00 2001 From: lockbitchat Date: Wed, 23 Sep 2026 20:10:03 -0400 Subject: [PATCH] v6.8.5: the relay password is no longer built into the app --- CHANGELOG.md | 23 +++++++---------- README.md | 2 +- ar/index.html | 38 +++++++++++++-------------- config/ice-servers.prod.js | 40 +++++----------------------- de/index.html | 38 +++++++++++++-------------- dist/app-boot.js | 2 +- dist/app-boot.js.map | 2 +- dist/app.js | 8 +++--- dist/app.js.map | 6 ++--- doc/CRYPTOGRAPHY.md | 4 +-- docs/cryptography/index.html | 4 +-- es/index.html | 38 +++++++++++++-------------- fa/index.html | 38 +++++++++++++-------------- fr/index.html | 38 +++++++++++++-------------- he/index.html | 38 +++++++++++++-------------- hi/index.html | 38 +++++++++++++-------------- index.html | 36 +++++++++++++------------- ko/index.html | 38 +++++++++++++-------------- meta.json | 14 +++++----- package.json | 2 +- ru/index.html | 38 +++++++++++++-------------- sitemap.xml | 46 ++++++++++++++++----------------- src/network/turnCredentials.js | 22 +++++++++++++--- sw.js | 2 +- tests/turn-credentials.test.mjs | 41 ++++++++++++++++++++++++++++- uk/index.html | 38 +++++++++++++-------------- ur/index.html | 38 +++++++++++++-------------- zh/index.html | 38 +++++++++++++-------------- 28 files changed, 365 insertions(+), 345 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b05954..94c8094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,16 @@ # Changelog -## v6.8.4 — The relay password now changes every day +## v6.8.5 — The relay password is no longer built into the app -When two people cannot connect directly, the app routes the call through our own -relay server. Until now every copy of the app carried the same password for that -server, valid for years, and anyone could copy it and push their own traffic -through it. The site now hands each app a fresh password that runs out after a -day, and only gives them out at a limited pace. Nothing changes for you: the app -picks up a new one on its own, including during long conversations. +When two people cannot connect directly, calls and chats go through our relay +server. Until now every copy of the app carried the same password for it, valid +for years, so anyone could copy it and send their own traffic through our server. +That password is gone. The app now asks the site for a new one that works for a +day and renews it by itself, including during long conversations. The site hands +them out only at a limited pace. You do not need to do anything. -## v6.8.3 — Private project files are no longer published with the site - -A few files meant only for the developer's own machine — local editor settings and -tool notes — were being uploaded along with the website and could be opened by anyone -who knew the address. They held no passwords or keys, but they had no business being -public. They are now left out of every deploy, along with anything else kept private -in the project. +A few files meant only for the developer's own computer were being published +along with the site. They held no passwords, but they are no longer published. ## v6.8.2 — The keyboard no longer pushes the chat off the screen diff --git a/README.md b/README.md index 92070e3..d5bb4a2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ No accounts. No servers storing your messages. No installation required. [![License: MIT](https://img.shields.io/badge/License-MIT-f0892a.svg)](LICENSE) -[![Version](https://img.shields.io/badge/version-6.8.4-3ecf8e.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-6.8.5-3ecf8e.svg)](CHANGELOG.md) [![Get it from the Snap Store](https://snapcraft.io/securebit-chat/badge.svg)](https://snapcraft.io/securebit-chat) [![PWA](https://img.shields.io/badge/PWA-installable-3ecf8e.svg)](#install-as-an-app) [![Encryption](https://img.shields.io/badge/crypto-ECDH%20P--384%20%C2%B7%20AES--256--GCM-blue.svg)](#security-model) diff --git a/ar/index.html b/ar/index.html index 3d6e354..87ded2b 100644 --- a/ar/index.html +++ b/ar/index.html @@ -30,18 +30,18 @@ - + - + - + @@ -116,7 +116,7 @@ - + @@ -125,7 +125,7 @@ - + @@ -264,7 +264,7 @@ - + - - + + - + - + - + - - + + - + - + - + @@ -116,7 +116,7 @@ - + @@ -125,7 +125,7 @@ - + @@ -264,7 +264,7 @@ - + - - + + - + - + - + - - + + ';\n }\n\n /* Stringify, in case dirty is an object */\n if (typeof dirty !== 'string' && !_isNode(dirty)) {\n dirty = stringifyValue(dirty);\n\n if (typeof dirty !== 'string') {\n throw typeErrorCreate('dirty is not a string, aborting');\n }\n }\n\n /* Return dirty HTML if DOMPurify cannot run */\n if (!DOMPurify.isSupported) {\n return dirty;\n }\n\n /* Assign config vars */\n if (!SET_CONFIG) {\n _parseConfig(cfg);\n }\n\n /* Clean up removed elements */\n DOMPurify.removed = [];\n\n /* Resolve IN_PLACE for this call without mutating persistent config.\n Writing the IN_PLACE closure variable here leaks under setConfig(),\n where _parseConfig is skipped on later calls: a single string call would\n disable in-place mode for every subsequent node call, returning a\n sanitized copy while leaving the caller's node \u2014 which in-place callers\n keep using and whose return value they ignore \u2014 unsanitized. REPORT-2. */\n const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);\n\n if (inPlace) {\n /* Do some early pre-sanitization to avoid unsafe root nodes.\n Read nodeName through the cached prototype getter \u2014 a clobbering\n child named \"nodeName\" on the form root would otherwise shadow\n the property and let this check skip the root-allowlist\n validation entirely. */\n const nn = getNodeName\n ? getNodeName(dirty as Node)\n : (dirty as Node).nodeName;\n if (typeof nn === 'string') {\n const tagName = transformCaseFunc(nn);\n if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n throw typeErrorCreate(\n 'root node is forbidden and cannot be sanitized in-place'\n );\n }\n }\n\n /* Pre-flight the root through _isClobbered. The iterator-driven\n removal path can not detach a parent-less root: _forceRemove\n falls through to Element.prototype.remove(), which per spec\n is a no-op on a node with no parent. A clobbered root would\n then survive the main loop with its attributes uninspected,\n because _sanitizeAttributes early-returns on _isClobbered. The\n result would be an attacker-controlled form, complete with any\n event-handler attributes the caller passed in, handed back to\n the application unsanitized. Refuse to sanitize such a root\n the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */\n if (_isClobbered(dirty as Element)) {\n throw typeErrorCreate(\n 'root node is clobbered and cannot be sanitized in-place'\n );\n }\n\n /* Sanitize attached shadow roots before the main iterator runs.\n The iterator does not descend into shadow trees. Same fail-closed\n barrier as the main walk (campaign-3 F2): a custom-element reaction\n inside a shadow root could abort this pre-pass before the walk runs,\n which would otherwise leave the entire live tree unsanitized. */\n try {\n _sanitizeAttachedShadowRoots(dirty as Node);\n } catch (error) {\n _neutralizeRoot(dirty as Node);\n\n throw error;\n }\n } else if (_isNode(dirty)) {\n /* If dirty is a DOM element, append to an empty document to avoid\n elements being stripped by the parser */\n body = _initDocument('');\n importedNode = body.ownerDocument.importNode(dirty, true);\n if (\n importedNode.nodeType === NODE_TYPE.element &&\n importedNode.nodeName === 'BODY'\n ) {\n /* Node is already a body, use as is */\n body = importedNode;\n } else if (importedNode.nodeName === 'HTML') {\n body = importedNode;\n } else {\n // eslint-disable-next-line unicorn/prefer-dom-node-append\n body.appendChild(importedNode);\n }\n\n /* Clonable shadow roots are deep-cloned by importNode(); sanitize\n them before the main iterator runs, since the iterator does not\n descend into shadow trees. The walk routes every read through a\n cached prototype getter so clobbering descendants on a form root\n cannot hide a shadow host from this pass. */\n _sanitizeAttachedShadowRoots(importedNode);\n } else {\n /* Exit directly if we have nothing to do */\n if (\n !RETURN_DOM &&\n !SAFE_FOR_TEMPLATES &&\n !WHOLE_DOCUMENT &&\n // eslint-disable-next-line unicorn/prefer-includes\n dirty.indexOf('<') === -1\n ) {\n return trustedTypesPolicy && RETURN_TRUSTED_TYPE\n ? _createTrustedHTML(dirty)\n : dirty;\n }\n\n /* Initialize the document to work on */\n body = _initDocument(dirty);\n\n /* Check we have a DOM node from the data */\n if (!body) {\n return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';\n }\n }\n\n /* Remove first element node (ours) if FORCE_BODY is set */\n if (body && FORCE_BODY) {\n _forceRemove(body.firstChild);\n }\n\n /* Get node iterator */\n const nodeIterator = _createNodeIterator(inPlace ? dirty : body);\n\n /* Now start iterating over the created document.\n The walk runs inside an exception barrier (campaign-3 F2): a re-entrant\n engine/custom-element mutation can detach a node mid-walk so\n `_forceRemove`'s parentless guard throws, aborting the loop. Without the\n barrier the caller's in-place tree would be left half-sanitized with the\n unvisited tail still armed. On any throw we fail closed \u2014 strip the\n in-place root bare \u2014 then rethrow so the existing throw contract is\n preserved. (String/DOM-copy paths never return the partial body, so the\n propagating throw is already fail-closed there.) */\n try {\n while ((currentNode = nodeIterator.nextNode())) {\n /* Sanitize tags and elements */\n _sanitizeElements(currentNode);\n\n /* Check attributes next */\n _sanitizeAttributes(currentNode);\n\n /* Shadow DOM detected, sanitize it.\n Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection\n instead of instanceof, so foreign-realm