From 5eecd99bc2450705dfdc4982bb3604cc4942a51a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 26 Sep 2026 12:52:57 +0100 Subject: [PATCH 1/9] 7.1.0.9 --- simplexmq.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplexmq.cabal b/simplexmq.cabal index c07b66cea..bfc81f8c5 100644 --- a/simplexmq.cabal +++ b/simplexmq.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: simplexmq -version: 7.1.0.8 +version: 7.1.0.9 synopsis: SimpleXMQ message broker description: This package includes <./docs/Simplex-Messaging-Server.html server>, <./docs/Simplex-Messaging-Client.html client> and From 972e50e7683a1f1780ee4df6f752ba4406811ec7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 29 Sep 2026 18:08:54 +0100 Subject: [PATCH 2/9] docs: update XRCP doc to make code verification optional for known connections (#1891) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- protocol/diagrams/xrcp/session.mmd | 4 ++-- protocol/diagrams/xrcp/session.svg | 2 +- protocol/xrcp.md | 21 ++++++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/protocol/diagrams/xrcp/session.mmd b/protocol/diagrams/xrcp/session.mmd index 2e98fbb1b..245776049 100644 --- a/protocol/diagrams/xrcp/session.mmd +++ b/protocol/diagrams/xrcp/session.mmd @@ -17,11 +17,11 @@ note over CC, HC: TLS handshake par note over CC: validate client X509 credentials CC->>CI: session code from tlsUnique - CI-->>CC: user confirmation + CI-->>CC: user confirmation (optional if known) and note over HC: validate server X509 credentials HC->>HI: session code from tlsUnique - HI-->>HC: user confirmation + HI-->>HC: user confirmation (optional if known) end note over CI, HI: 3. Session verification and protocol negotiation diff --git a/protocol/diagrams/xrcp/session.svg b/protocol/diagrams/xrcp/session.svg index 136b56fc6..337395737 100644 --- a/protocol/diagrams/xrcp/session.svg +++ b/protocol/diagrams/xrcp/session.svg @@ -1 +1 @@ -Host UIHost CoreController CoreController UIHost UIHost CoreController CoreController UI1. Session invitationListen for TCP connection2. Establishing TLS connectionTLS handshakevalidate client X509 credentialsvalidate server X509 credentialspar​3. Session verification and protocol negotiationvalidate version, CA fingerprintupdate stored keysalt​4. Session operationloop​"Link a mobile"Session invitation URISession invitation URITCP connectsession code from tlsUniqueuser confirmationsession code from tlsUniqueuser confirmationhost HELLOcontroller ERRORcontroller HELLOcommandXRCP commandXRCP responseresponse \ No newline at end of file +Host UIHost CoreController CoreController UIHost UIHost CoreController CoreController UI1. Session invitationListen for TCP connection2. Establishing TLS connectionTLS handshakevalidate client X509 credentialsvalidate server X509 credentialspar​3. Session verification and protocol negotiationvalidate version, CA fingerprintupdate stored keysalt​4. Session operationloop​"Link a mobile"Session invitation URISession invitation URITCP connectsession code from tlsUniqueuser confirmation (optional if known)session code from tlsUniqueuser confirmation (optional if known)host HELLOcontroller ERRORcontroller HELLOcommandXRCP commandXRCP responseresponse \ No newline at end of file diff --git a/protocol/xrcp.md b/protocol/xrcp.md index 1b2c320a7..a9df02f5a 100644 --- a/protocol/xrcp.md +++ b/protocol/xrcp.md @@ -121,9 +121,15 @@ During TLS handshake, parties validate certificate chains against previously kno ### Session verification and protocol negotiation -Once TLS session is established, both the host and controller devices present a "session security code" to the user who must match them (e.g., visually or via QR code scan) and confirm on the host device. The session security code must be a digest of tlsunique channel binding. As it is computed as a digest of the TLS handshake for both the controller and the host, it will validate that the same TLS certificates are used on both sides, and that the same TLS session is established, mitigating the possibility of MITM attack in the connection. +Once TLS session is established, both the host and controller devices present a "session security code" to the user. The session security code must be a digest of tlsunique channel binding. As it is computed as a digest of the TLS handshake for both the controller and the host, it will validate that the same TLS certificates are used on both sides, and that the same TLS session is established, mitigating the possibility of MITM attack in the connection. -Once the session is confirmed by the user, the host sends HELLO block to the controller. +To verify the session, the user matches the codes (e.g., visually or via QR code scan) and confirms the session on the host device. + +The user MUST verify the first session with a new controller. The host stores the long-term identity of the controller (CA fingerprint and Ed25519 public key) only after this session is verified. A controller with the stored long-term identity is a known controller. + +The user MAY verify the subsequent sessions with a known controller. In a session that the host confirms without user action, the controller is authenticated only by its long-term identity stored in the first session (see [Threat model](#threat-model)). SimpleX Chat mobile apps require user verification of sessions with known controllers only when the user enables the "Verify connections" option. + +Once the session is confirmed, the host sends HELLO block to the controller. XRCP blocks inside TLS are padded to 16384 bytes. @@ -305,7 +311,7 @@ to receive: (rcvKey', sk, nonce) = HKDF(rcvKey, "SimpleXSbChain", 88) - prevent host and controller devices from establishing the session *cannot:* -- same as passive adversary, provided that user visually verified session code out-of-band. +- same as passive adversary, provided that user visually verified session code out-of-band in the first session with the controller. #### An active adversary with the access to the network: @@ -324,6 +330,15 @@ to receive: (rcvKey', sk, nonce) = HKDF(rcvKey, "SimpleXSbChain", 88) *cannot:* - connect to the host or make host connect to itself. +#### An active adversary with the access to the network who also obtained the long-term private keys of a known controller (e.g., from the controller device): + +*can:* +- make host connect to itself instead of the controller. +- access any data of the controlled host application, within the capabilities of the provided API, if the host confirms the session without user action. + +*cannot:* +- access host application data, provided that user visually verified session code out-of-band. + #### Compromised controller device: *can:* From 551a79d99bd9bd3fc1c2e3ef1844b4676dfb9e81 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 29 Sep 2026 19:52:15 +0100 Subject: [PATCH 3/9] smp server: validate client version in forwarded command (#1890) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- src/Simplex/Messaging/Server.hs | 1 + src/Simplex/Messaging/Transport.hs | 1 - tests/SMPClient.hs | 3 +++ tests/SMPProxyTests.hs | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Simplex/Messaging/Server.hs b/src/Simplex/Messaging/Server.hs index 097cabbbe..1c58a5a49 100644 --- a/src/Simplex/Messaging/Server.hs +++ b/src/Simplex/Messaging/Server.hs @@ -2127,6 +2127,7 @@ client let proxyNonce = C.cbNonce $ bs corrId s' <- liftEitherWith (const CRYPTO) $ C.cbDecryptNoPad sessSecret proxyNonce s FwdTransmission {fwdCorrId, fwdVersion, fwdKey, fwdTransmission = EncTransmission et} <- liftEitherWith (const $ CMD SYNTAX) $ smpDecode s' + unless (fwdVersion `isCompatible` thServerVRange thParams') $ throwE $ transportErr TEVersion let clientSecret = C.dh' fwdKey serverPrivKey clientNonce = C.cbNonce $ bs fwdCorrId b <- liftEitherWith (const CRYPTO) $ C.cbDecrypt clientSecret clientNonce et diff --git a/src/Simplex/Messaging/Transport.hs b/src/Simplex/Messaging/Transport.hs index d2c30d25a..6fa307fd1 100644 --- a/src/Simplex/Messaging/Transport.hs +++ b/src/Simplex/Messaging/Transport.hs @@ -865,7 +865,6 @@ forceCertChain :: CertChainPubKey -> CertChainPubKey forceCertChain cert@(CertChainPubKey (X.CertificateChain cc) signedKey) = length (show cc) `seq` show signedKey `seq` cert {-# INLINE forceCertChain #-} --- This function is only used with v >= 8, so currently it's a simple record update. -- * Note: it requires updating version-based parameters, to be consistent with smpTHandle_. smpTHParamsSetVersion :: VersionSMP -> THandleParams SMPVersion p -> THandleParams SMPVersion p smpTHParamsSetVersion v params = diff --git a/tests/SMPClient.hs b/tests/SMPClient.hs index e5adaa749..43aa22a76 100644 --- a/tests/SMPClient.hs +++ b/tests/SMPClient.hs @@ -316,6 +316,9 @@ prevRange vr = vr {maxVersion = max (minVersion vr) (prevVersion $ maxVersion vr prevVersion :: Version v -> Version v prevVersion (Version v) = Version (v - 1) +nextVersion :: Version v -> Version v +nextVersion (Version v) = Version (v + 1) + proxyCfg :: AServerConfig proxyCfg = proxyCfgMS (ASType SQSMemory SMSJournal) diff --git a/tests/SMPProxyTests.hs b/tests/SMPProxyTests.hs index b8c86dee4..430d52304 100644 --- a/tests/SMPProxyTests.hs +++ b/tests/SMPProxyTests.hs @@ -191,6 +191,8 @@ deliverMessagesViaProxy proxyServ relayServ alg unsecuredMsgs securedMsgs = do forM_ unsecuredMsgs $ \msg -> do runExceptT' (proxySMPMessage pc NRMInteractive sess Nothing sndId noMsgFlags msg) `shouldReturn` Right () runExceptT' (proxySMPMessage pc NRMInteractive sess {prSessionId = "bad session"} Nothing sndId noMsgFlags msg) `shouldReturn` Left (ProxyProtocolError $ SMP.PROXY SMP.NO_SESSION) + forM_ ([prevVersion minServerSMPRelayVersion, nextVersion currentServerSMPRelayVersion] :: [VersionSMP]) $ \v -> + runExceptT' (proxySMPMessage pc NRMInteractive sess {prVersion = v} Nothing sndId noMsgFlags msg) `shouldReturn` Left (ProxyProtocolError $ SMP.PROXY $ SMP.PROTOCOL $ SMP.PROXY $ SMP.BROKER $ SMP.TRANSPORT TEVersion) -- receive 1 (_tSess, _, [(_entId, STEvent (Right (SMP.MSG RcvMessage {msgId, msgBody = EncRcvMsgBody encBody})))]) <- atomically $ readTBQueue msgQ dec msgId encBody `shouldBe` Right msg From 267e2e0727d03b45b50521c5c3ac411a654a2e90 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 29 Sep 2026 21:32:21 +0100 Subject: [PATCH 4/9] agent: fix ratchet - dont save if body auth failed (#1892) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- src/Simplex/Messaging/Agent.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Simplex/Messaging/Agent.hs b/src/Simplex/Messaging/Agent.hs index 79e411bc5..be34deafb 100644 --- a/src/Simplex/Messaging/Agent.hs +++ b/src/Simplex/Messaging/Agent.hs @@ -4388,8 +4388,9 @@ agentRatchetDecrypt' :: TVar ChaChaDRG -> DB.Connection -> ConnId -> CR.RatchetX agentRatchetDecrypt' g db connId rc encAgentMsg = do skipped <- liftIO $ getSkippedMsgKeys db connId (agentMsgBody_, rc', skippedDiff) <- withExceptT (SEAgentError . cryptoError) $ CR.rcDecrypt g rc skipped encAgentMsg + agentMsgBody <- liftEither $ first (SEAgentError . cryptoError) agentMsgBody_ liftIO $ updateRatchet db connId rc' skippedDiff - liftEither $ bimap (SEAgentError . cryptoError) (,CR.rcRcvKEM rc') agentMsgBody_ + pure (agentMsgBody, CR.rcRcvKEM rc') newSndQueue :: UserId -> ConnId -> Compatible SMPQueueInfo -> Maybe (C.APrivateAuthKey) -> AM' (NewSndQueue, C.PublicKeyX25519) newSndQueue userId connId (Compatible (SMPQueueInfo smpClientVersion SMPQueueAddress {smpServer, senderId, queueMode, dhPublicKey = rcvE2ePubDhKey})) sndKey_ = do From e2df2e1b7c0b6044cbccf16720796b3c08d42df5 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 30 Sep 2026 08:10:35 +0100 Subject: [PATCH 5/9] docs: update SMP protocol (#1900) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- protocol/simplex-messaging.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index a80d211b2..c5e5d8489 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -1120,6 +1120,8 @@ When the client receives PKEY response it MUST validate that: - the fingerprint of the received certificate matches fingerprint in the router address - it mitigates MITM attack by proxy. - the router session key is correctly signed with the received certificate. +`smpVersionRange` in `PKEY` is not signed. + The proxy router may respond with error response in case the destination router is not available or in case it has an earlier version that does not support proxied commands. #### Send command via proxy From c1776dc5a454597d552b12cb931af6193e2ae7fe Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 30 Sep 2026 09:04:28 +0100 Subject: [PATCH 6/9] server: improve control port auth (#1898) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- src/Simplex/Messaging/Protocol.hs | 6 +++++- src/Simplex/Messaging/Server.hs | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Simplex/Messaging/Protocol.hs b/src/Simplex/Messaging/Protocol.hs index e2cbef3a4..14f2a967f 100644 --- a/src/Simplex/Messaging/Protocol.hs +++ b/src/Simplex/Messaging/Protocol.hs @@ -241,6 +241,7 @@ import Data.Attoparsec.ByteString.Char8 (Parser, ()) import qualified Data.Attoparsec.ByteString.Char8 as A import Data.Bifunctor (bimap, first) import Data.Bits (xor) +import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Char8 (ByteString) @@ -1323,7 +1324,10 @@ instance ProtocolTypeI p => FromJSON (ProtocolServer p) where parseJSON = strParseJSON "ProtocolServer" newtype BasicAuth = BasicAuth {unBasicAuth :: ByteString} - deriving (Eq, Ord, Show) + deriving (Ord, Show) + +instance Eq BasicAuth where + BasicAuth s == BasicAuth s' = BA.constEq s s' instance IsString BasicAuth where fromString = BasicAuth . B.pack diff --git a/src/Simplex/Messaging/Server.hs b/src/Simplex/Messaging/Server.hs index 1c58a5a49..405e9b5f0 100644 --- a/src/Simplex/Messaging/Server.hs +++ b/src/Simplex/Messaging/Server.hs @@ -1092,6 +1092,7 @@ controlPortAuth h user admin role auth = do readTVarIO role >>= \case CPRNone -> do atomically $ writeTVar role $! newRole + when (newRole == CPRNone) $ logWarn "ControlPort: failed auth" hPutStrLn h $ currentRole newRole r -> hPutStrLn h $ currentRole r <> if r == newRole then "" else ", start new session to change." where From 0e1ecdcff6c78688063af6f98252c600f4b25fd3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 30 Sep 2026 09:36:45 +0100 Subject: [PATCH 7/9] xftp server: validate paths in store log (#1894) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- src/Simplex/FileTransfer/Server.hs | 3 +-- src/Simplex/FileTransfer/Server/StoreLog.hs | 12 ++++++++++-- tests/CoreTests/StoreLogTests.hs | 15 ++++++++++++++- tests/CoreTests/XFTPStoreTests.hs | 7 ++++--- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Simplex/FileTransfer/Server.hs b/src/Simplex/FileTransfer/Server.hs index 031879eee..16a705690 100644 --- a/src/Simplex/FileTransfer/Server.hs +++ b/src/Simplex/FileTransfer/Server.hs @@ -25,7 +25,6 @@ import Control.Monad.Reader import Control.Monad.Trans.Except import qualified Data.Aeson as J import Data.Bifunctor (first) -import qualified Data.ByteString.Base64.URL as B64 import Data.ByteString.Builder (Builder, byteString) import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B @@ -594,7 +593,7 @@ processXFTPRequest ent HTTP2Body {bodyPart} = \case \used -> let used' = used + fromIntegral size in if used' <= quota then (True, used') else (False, used) receive = do path <- asks $ filesPath . config - let fPath = path B.unpack (B64.encode $ unEntityId senderId) + let fPath = path serverFileName senderId receiveChunk (XFTPRcvChunkSpec fPath size digest) >>= \case Right () -> do stats <- asks serverStats diff --git a/src/Simplex/FileTransfer/Server/StoreLog.hs b/src/Simplex/FileTransfer/Server/StoreLog.hs index fe731ee2c..5a6d99c7a 100644 --- a/src/Simplex/FileTransfer/Server/StoreLog.hs +++ b/src/Simplex/FileTransfer/Server/StoreLog.hs @@ -17,6 +17,7 @@ module Simplex.FileTransfer.Server.StoreLog logDeleteFile, logBlockFile, logAckFile, + serverFileName, ) where @@ -24,6 +25,7 @@ import Control.Applicative (optional, (<|>)) import Control.Concurrent.STM import Control.Monad.Except import qualified Data.Attoparsec.ByteString.Char8 as A +import qualified Data.ByteString.Base64.URL as B64 import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB import Data.Composition ((.:), (.::.)) @@ -35,10 +37,11 @@ import Simplex.FileTransfer.Protocol (FileInfo (..)) import Simplex.FileTransfer.Server.Store import Simplex.FileTransfer.Transport (XFTPErrorType (..)) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Protocol (BlockingInfo, RcvPublicAuthKey, RecipientId, SenderId) +import Simplex.Messaging.Protocol (BlockingInfo, EntityId (..), RcvPublicAuthKey, RecipientId, SenderId) import Simplex.Messaging.Server.QueueStore (ServerEntityStatus (..)) import Simplex.Messaging.Server.StoreLog import Simplex.Messaging.Util (bshow) +import System.FilePath (takeFileName) import System.IO data FileStoreLogRecord @@ -99,6 +102,9 @@ logBlockFile s fId = logFileStoreRecord s . BlockFile fId logAckFile :: StoreLog 'WriteMode -> RecipientId -> IO () logAckFile s = logFileStoreRecord s . AckFile +serverFileName :: SenderId -> FilePath +serverFileName = B.unpack . B64.encode . unEntityId + readWriteFileStore :: FilePath -> STMFileStore -> IO (StoreLog 'WriteMode) readWriteFileStore = readWriteStoreLog readFileStore writeFileStore @@ -115,7 +121,9 @@ readFileStore f st = mapM_ (addFileLogRecord . LB.toStrict) . LB.lines =<< LB.re AddFile sId file createdAt expiresAt status | size file > 0 -> addFile st sId file createdAt expiresAt status | otherwise -> pure $ Left SIZE - PutFile qId path -> setFilePath st qId path + PutFile qId path + | takeFileName path == serverFileName qId -> setFilePath st qId path + | otherwise -> pure $ Left AUTH AddRecipients sId rcps -> runExceptT $ addRecipients sId rcps DeleteFile sId -> deleteFile st sId BlockFile sId info -> blockFile st sId info True diff --git a/tests/CoreTests/StoreLogTests.hs b/tests/CoreTests/StoreLogTests.hs index 13d831fe1..81a0535d5 100644 --- a/tests/CoreTests/StoreLogTests.hs +++ b/tests/CoreTests/StoreLogTests.hs @@ -22,7 +22,7 @@ import qualified Data.X509.Validation as XV import SMPClient import Simplex.FileTransfer.Protocol (FileInfo (..)) import Simplex.FileTransfer.Server.Store (FileRec (..), FileRecipient (..), FileStoreClass (..), RoundedFileTime, STMFileStore (..)) -import Simplex.FileTransfer.Server.StoreLog (FileStoreLogRecord (..), readWriteFileStore) +import Simplex.FileTransfer.Server.StoreLog (FileStoreLogRecord (..), readWriteFileStore, serverFileName) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String import Simplex.Messaging.Protocol @@ -37,6 +37,7 @@ import Simplex.Messaging.Server.StoreLog import Simplex.Messaging.SystemTime import Simplex.Messaging.Transport (SMPServiceRole (..)) import Simplex.Messaging.Transport.Credentials (genCredentials) +import System.FilePath (()) import Test.Hspec hiding (fit, it) import Util @@ -269,6 +270,18 @@ fileStoreLogTests = do saved = [AddFile sId file createdAt Nothing EntityActive, BlockFile sId blockedWithNotice], compacted = [AddFile sId file createdAt Nothing (EntityBlocked blockedWithNotice)], state = M.fromList [(sId, (file, createdAt, Nothing, EntityBlocked blockedWithNotice))] + }, + SLTC + { name = "put file with wrong file name", + saved = [AddFile sId file createdAt (Just expiresAt) EntityActive, PutFile sId "/etc/passwd"], + compacted = [AddFile sId file createdAt (Just expiresAt) EntityActive], + state = M.fromList [(sId, (file, createdAt, Just expiresAt, EntityActive))] + }, + SLTC + { name = "put file in another directory", + saved = [AddFile sId file createdAt (Just expiresAt) EntityActive, PutFile sId ("/srv/xftp" serverFileName sId)], + compacted = [AddFile sId file createdAt (Just expiresAt) EntityActive, PutFile sId ("/srv/xftp" serverFileName sId)], + state = M.fromList [(sId, (file, createdAt, Just expiresAt, EntityActive))] } ] diff --git a/tests/CoreTests/XFTPStoreTests.hs b/tests/CoreTests/XFTPStoreTests.hs index 9f980b4ea..52c7cedfa 100644 --- a/tests/CoreTests/XFTPStoreTests.hs +++ b/tests/CoreTests/XFTPStoreTests.hs @@ -9,7 +9,7 @@ import Data.Word (Word32) import Simplex.FileTransfer.Protocol (FileInfo (..), SFileParty (..)) import Simplex.FileTransfer.Server.Store import Simplex.FileTransfer.Server.Store.Postgres (PostgresFileStore, importFileStore, exportFileStore) -import Simplex.FileTransfer.Server.StoreLog (closeStoreLog, readWriteFileStore, writeFileStore) +import Simplex.FileTransfer.Server.StoreLog (closeStoreLog, readWriteFileStore, serverFileName, writeFileStore) import Simplex.FileTransfer.Transport (XFTPErrorType (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Protocol (BlockingInfo (..), BlockingReason (..), EntityId (..)) @@ -17,6 +17,7 @@ import Simplex.Messaging.Server.QueueStore (ServerEntityStatus (..)) import Simplex.Messaging.Server.StoreLog (openWriteStoreLog) import Simplex.Messaging.SystemTime (RoundedSystemTime (..)) import System.Directory (doesFileExist, removeFile) +import System.FilePath (()) import Test.Hspec hiding (fit, it) import UnliftIO.STM import Util @@ -265,7 +266,7 @@ testMigrationRoundTrip = do sId2 = EntityId "migration_file_2" rId1 = EntityId "migration_rcp_1_" addFile stmStore sId1 fileInfo1 testCreatedAt (Just testExpiresAt) EntityActive `shouldReturn` Right () - void $ setFilePath stmStore sId1 "/tmp/file1" + void $ setFilePath stmStore sId1 ("/tmp" serverFileName sId1) addRecipient stmStore sId1 (FileRecipient rId1 rcpKey1) `shouldReturn` Right () let testBlockInfo = BlockingInfo {reason = BRSpam, notice = Nothing} addFile stmStore sId2 fileInfo2 testCreatedAt Nothing (EntityBlocked testBlockInfo) `shouldReturn` Right () @@ -289,7 +290,7 @@ testMigrationRoundTrip = do case result1 of Right (FileRec {fileInfo = fi, filePath, expiresAt, fileStatus}, _) -> do size fi `shouldBe` 128000 - readTVarIO filePath `shouldReturn` Just "/tmp/file1" + readTVarIO filePath `shouldReturn` Just ("/tmp" serverFileName sId1) expiresAt `shouldBe` Just testExpiresAt readTVarIO fileStatus `shouldReturn` EntityActive Left e -> expectationFailure $ "getFile sId1 failed: " <> show e From b4c288c597c78987f76d0582fff69b39c939fdf7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 30 Sep 2026 10:18:52 +0100 Subject: [PATCH 8/9] docs: clarify security model (#1902) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- protocol/security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/security.md b/protocol/security.md index ea236daf4..dabe78139 100644 --- a/protocol/security.md +++ b/protocol/security.md @@ -168,7 +168,7 @@ This document describes the cryptographic primitives and threat model for the Si *cannot:* -- impersonate a sender and send messages to the user whose database was stolen. Doing so requires also compromising the router (to place the message in the queue, that is possible until the Double-Ratchet advances forward) or the user's device at a subsequent time (to place the message in the database). +- impersonate a sender and send messages to the user whose database was stolen, unless the database contains a new rotation queue that is not secured yet. Doing so requires also compromising the router (to place the message in the queue, that is possible until the Double-Ratchet advances forward) or the user's device at a subsequent time (to place the message in the database). - undetectably communicate at the same time as Alice with her contacts. Doing so would result in the contact getting different messages with repeated IDs. From 38fdc0c544ac950c0662ebb16f278a31805fb81a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 30 Sep 2026 13:03:16 +0100 Subject: [PATCH 9/9] 7.1.0.10 --- simplexmq.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplexmq.cabal b/simplexmq.cabal index bfc81f8c5..39b40dd33 100644 --- a/simplexmq.cabal +++ b/simplexmq.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: simplexmq -version: 7.1.0.9 +version: 7.1.0.10 synopsis: SimpleXMQ message broker description: This package includes <./docs/Simplex-Messaging-Server.html server>, <./docs/Simplex-Messaging-Client.html client> and