Hello,

I've prepared sogo 5.8.0-2+deb12u4 for bookworm-security, and it's ready for 
review.

This follows DSA-6561-1, which shipped 5.12.1-3+deb13u3 in Trixie. Everything 
below is also in that Trixie update; I'm not adding anything Trixie doesn't 
have. Sid has all of it as well (5.12.13-1). I've attached the debdiff against 
5.8.0-2+deb12u3 and the draft DLA.

Here's what's in it:
- CVE-2026-93453, password reset poisoning 
(https://www.sogo.nu/news/2026/sogo-v51211-released.html)
- The SOGo 5.12.10 security fixes, which don't have a CVE yet. The tracker groups them 
under TEMP-0000000-5E8928 ("sogo issues from 5.12.10") for now. They cover the 
sendmail shell injection, the MKCOL SQL injection, the external-calendar SSRF, and the 
XSS fixes (AngularJS directive escaping with its stripAngular follow-up, the sorting 
value, and upstream PR 395) (https://www.sogo.nu/news/2026/sogo-v51210-released.html)
- A fix for a regression caused by the CVE-2026-33550 TOTP patch in DLA-4657-1 
(#1142810, 
https://github.com/Alinto/sogo/commit/623f083cd94842766c6d9430cf3bdad8fc4d5dbc)

I left out CVE-2026-3054 on purpose. It's about the login hint in the query 
string, a parameter SOGo only added in 5.12.0, so Bookworm's 5.8.0 doesn't have 
it. Trixie got the fix in DSA-6366-1 
(https://github.com/Alinto/sogo/commit/e821b20f87d1a9757f1d0aff7d1e31703f97054b).
 In 5.8.0 the login page fills the username from the saved cookie instead, and 
the tracker already has it as postponed for Bookworm.

For testing, I built it in a Bookworm schroot and the unit tests ran 37 tests. 
The localhost address check and one MIME address header test fail, but deb12u3 
already records both failures, and the build completes fine.

I also installed deb12u3 and deb12u4 side by side in the schroot and poked at libSOGo's HTML filter directly. On 
deb12u3, "Hello {{7*7}}" and the decimal brace entities went through untouched, 
"javajavascript:script:" collapsed into "javascript:", and "onerror =alert(1)" survived. 
On deb12u4, the nested scheme comes back empty, the spaced handler gets rewritten to data-blocked=, and the Angular 
pass rewrites the braces and the decimal entities. @import gets cleaned on both versions, and the braces stay put when 
the Angular argument is NO, as they should.

Here is my Salsa fork: https://salsa.debian.org/tsimonq2/sogo/-/tree/bookworm

Here is the Debusine work request: 
https://debusine.debian.net/debian/developers/work-request/1610715/

Feedback is welcome. If nothing showstopping comes up, I'll upload this and 
issue the DLA on Sunday afternoon (November 15th, 2026).

Best regards,
Simon Quigley
[email protected]
diff -Nru sogo-5.8.0/debian/changelog sogo-5.8.0/debian/changelog
--- sogo-5.8.0/debian/changelog 2026-06-21 04:32:55.000000000 -0500
+++ sogo-5.8.0/debian/changelog 2026-11-11 11:18:44.000000000 -0600
@@ -1,3 +1,21 @@
+sogo (5.8.0-2+deb12u4) bookworm-security; urgency=high
+
+  * Non-maintainer upload by the LTS Team.
+  * CVE-2026-33550: Keep TOTP enabled when the secret is already stored
+    in totpKey. (Closes: #1142810)
+  * Reject the sendmail transport so message data is sent with SMTP.
+  * Quote a folder name and path before they are written into SQL.
+  * Strip AngularJS interpolation and repeat scheme removal until it is
+    stable.
+  * Report a local failure when an external calendar cannot be reloaded.
+  * CVE-2026-93453: Send a password-reset link only for an Origin listed
+    in SOGoPasswordRecoveryBaseURLs. (Closes: #1148454)
+  * Accept only known sort and list values.
+  * Escape invitation text and attachment titles before they are written
+    into the page.
+
+ -- Simon Quigley <[email protected]>  Wed, 11 Nov 2026 11:18:44 -0600
+
 sogo (5.8.0-2+deb12u3) bookworm-security; urgency=medium
 
   * Non-maintainer upload.
diff -Nru sogo-5.8.0/debian/patches/CVE-2026-33550-regression.patch 
sogo-5.8.0/debian/patches/CVE-2026-33550-regression.patch
--- sogo-5.8.0/debian/patches/CVE-2026-33550-regression.patch   1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/CVE-2026-33550-regression.patch   2026-11-10 
19:16:17.000000000 -0600
@@ -0,0 +1,24 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/623f083cd94842766c6d9430cf3bdad8fc4d5dbc
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. A login without a verification code keeps TOTP
+enabled when totpKey is already set.
+
+From 623f083cd94842766c6d9430cf3bdad8fc4d5dbc Mon Sep 17 00:00:00 2001
+From: Hivert Quentin <[email protected]>
+Date: Thu, 19 Mar 2026 13:30:28 +0100
+Subject: fix(totp): new user can properly use totp
+
+--- a/UI/MainUI/SOGoRootPage.m
++++ b/UI/MainUI/SOGoRootPage.m
+@@ -355,7 +355,8 @@ static const NSString *kJwtKey = @"jwt";
+           else
+             {
+               us = [loggedInUser userSettings];
+-              if ([us dictionaryForKey: @"General"] && ![[us 
dictionaryForKey: @"General"] objectForKey: @"PrivateSalt"])
++              if ([us dictionaryForKey: @"General"] && !([[us 
dictionaryForKey: @"General"] objectForKey: @"PrivateSalt"]
++                  || [[us dictionaryForKey: @"General"] objectForKey: 
@"totpKey"]))
+                 {
+                   // Since v5.3.0, a new salt is used for TOTP. If it's 
missing, disable TOTP and alert the user.
+                   [ud setTotpEnabled: NO];
diff -Nru sogo-5.8.0/debian/patches/CVE-2026-93453.patch 
sogo-5.8.0/debian/patches/CVE-2026-93453.patch
--- sogo-5.8.0/debian/patches/CVE-2026-93453.patch      1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/CVE-2026-93453.patch      2026-11-11 
09:12:33.000000000 -0600
@@ -0,0 +1,96 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/382118a93b6925de2ce7f774abc1865ebea2dbba
+Origin: backport, 
https://github.com/Alinto/sogo/commit/04a3e9823889acaf6c247b224f5f7a0108f8f829
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. The reset link uses the Origin header only when
+that value is listed in SOGoPasswordRecoveryBaseURLs. Recovery mail
+is rejected when the list is empty.
+
+From 04a3e9823889acaf6c247b224f5f7a0108f8f829 Mon Sep 17 00:00:00 2001
+From: Hivert Quentin <[email protected]>
+Date: Thu, 10 Sep 2026 08:11:23 +0200
+Subject: use the proper url for reset password mail (part 2)
+
+--- a/Documentation/SOGoInstallationGuide.asciidoc
++++ b/Documentation/SOGoInstallationGuide.asciidoc
+@@ -789,6 +789,13 @@ specified as an array of dictionaries.
+ |S |SOGoPasswordRecoveryEnabled
+ |Boolean enable password recovery with secret question or secondary e-mail. 
Default value is `NO`.
+ 
++|S |SOGoPasswordRecoveryBaseURLs
++|List of base URLs allowed in a password-reset link. Required when
++password recovery is enabled. Do not end a URL with `/`. There is no
++default: an empty or unset list rejects the reset mail.
+++
++`SOGoPasswordRecoveryBaseURLs = ("https://demo.sogo.nu";);`
++
+ |S |SOGoPasswordRecoveryDomains
+ |List of domains where password recovery is enabled, for example 
`(example.org, example.net)`. If empty array `()`, disabled for all domains. If 
not set, enabled for all domains.
+ 
+--- a/SoObjects/SOGo/SOGoSystemDefaults.h
++++ b/SoObjects/SOGo/SOGoSystemDefaults.h
+@@ -118,6 +118,7 @@
+ - (BOOL) easSearchInBody;
+ 
+ - (BOOL) isPasswordRecoveryEnabled;
++- (NSArray *) passwordRecoveryBaseURLs;
+ - (NSArray *) passwordRecoveryDomains;
+ - (NSString *) JWTSecret;
+ 
+--- a/SoObjects/SOGo/SOGoSystemDefaults.m
++++ b/SoObjects/SOGo/SOGoSystemDefaults.m
+@@ -766,6 +766,11 @@ _injectConfigurationFromFile (NSMutableD
+   return [self boolForKey: @"SOGoPasswordRecoveryEnabled"];
+ }
+ 
++- (NSArray *) passwordRecoveryBaseURLs
++{
++  return [self stringArrayForKey: @"SOGoPasswordRecoveryBaseURLs"];
++}
++
+ - (NSArray *) passwordRecoveryDomains
+ {
+   static NSArray *passwordRecoveryDomains = nil;
+--- a/UI/MainUI/SOGoRootPage.m
++++ b/UI/MainUI/SOGoRootPage.m
+@@ -874,7 +874,8 @@ static const NSString *kJwtKey = @"jwt";
+  */
+ - (WOResponse *) passwordRecoveryEmailAction
+ {
+-  NSString *username, *domain, *mode, *uid, *mailDomain, *fromEmail, 
*toEmail, *jwtToken, *url, *mailContent;
++  NSString *username, *domain, *mode, *uid, *mailDomain, *fromEmail, 
*toEmail, *jwtToken, *url, *mailContent, *serverUrl;
++  NSArray *baseUrls;
+   NSDictionary *message, *info;
+   WORequest *request;
+   SOGoUserManager *um;
+@@ -920,8 +921,28 @@ static const NSString *kJwtKey = @"jwt";
+           ownerUser = [SOGoUser userWithLogin: username];
+           dd = [ownerUser domainDefaults];
+           mailer = [SOGoMailer mailerWithDomainDefaults: dd];
++
++          baseUrls = [[SOGoSystemDefaults sharedSystemDefaults] 
passwordRecoveryBaseURLs];
++          serverUrl = [[request headers] objectForKey:@"origin"];
++
++          if (!(baseUrls && [baseUrls count] > 0))
++            {
++              [self logWithFormat: @"Password recovery exception for user %@: 
empty or unset SOGoPasswordRecoveryBaseURLs", uid];
++              response = [self responseWithStatus: 403
++                                andString: @"Password recovery email in 
error"];
++              return response;
++            }
++
++          if (![baseUrls containsObject: serverUrl])
++            {
++              [self logWithFormat: @"Password recovery exception for user %@: 
invalid request header", uid];
++              response = [self responseWithStatus: 403
++                                andString: @"Password recovery email in 
error"];
++              return response;
++            }
++
+           url = [NSString stringWithFormat:@"%@%@?token=%@"
+-                      , [[request headers] objectForKey:@"origin"]
++                      , serverUrl
+                       , [request uri]
+                       , jwtToken];
+ 
diff -Nru sogo-5.8.0/debian/patches/disable-sendmail.patch 
sogo-5.8.0/debian/patches/disable-sendmail.patch
--- sogo-5.8.0/debian/patches/disable-sendmail.patch    1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/disable-sendmail.patch    2026-11-10 
19:52:36.000000000 -0600
@@ -0,0 +1,59 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/f0c23407bfb67e978413e2bdfc12025c8a9b1494
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. SOGoMailingMechanism accepts smtp. The sendmail
+transport is rejected and the packaged default remains smtp.
+
+From f0c23407bfb67e978413e2bdfc12025c8a9b1494 Mon Sep 17 00:00:00 2001
+From: Hivert Quentin <[email protected]>
+Date: Tue, 11 Aug 2026 17:34:50 +0200
+Subject: chore(sendmail): disable sendmail for now
+
+--- a/SoObjects/SOGo/SOGoDomainDefaults.m
++++ b/SoObjects/SOGo/SOGoDomainDefaults.m
+@@ -262,11 +262,10 @@
+   NSString *mailingMechanism;
+ 
+   mailingMechanism = [self stringForKey: @"SOGoMailingMechanism"];
+-  if (!([mailingMechanism isEqualToString: @"sendmail"]
+-        || [mailingMechanism isEqualToString: @"smtp"]))
++  if (![mailingMechanism isEqualToString: @"smtp"])
+     {
+       [self logWithFormat: @"mechanism '%@' is invalid and"
+-            @" should be set to 'sendmail' or 'smtp' instead",
++            @" should be set to 'smtp' instead",
+             mailingMechanism];
+       mailingMechanism = nil;
+     }
+--- a/Documentation/SOGoInstallationGuide.asciidoc
++++ b/Documentation/SOGoInstallationGuide.asciidoc
+@@ -73,7 +73,7 @@ following:
+ * Database server (MySQL, PostgreSQL or Oracle)
+ * LDAP server (OpenLDAP, Novell eDirectory, Microsoft Active Directory
+ and others) 
+-* SMTP server (Postfix, Sendmail and others)
++* SMTP server (Postfix)
+ * IMAP server (Courier, Cyrus IMAP Server, Dovecot and others)
+ 
+ If you plan to use ActiveSync, an IMAP server supporting the ACL,
+@@ -1904,7 +1904,6 @@ The following table describes the relate
+ are:
+ 
+ [options="compact"]
+-* `sendmail` - to use the sendmail binary
+ * `smtp` - to use the SMTP protocol
+ 
+ |D |SOGoSMTPServer
+@@ -1924,11 +1923,6 @@ To disable TLS verification for localhos
+ Current, only `PLAIN` is supported and other values will cause
+ the authentication to fail.
+ 
+-|S |WOSendMail
+-|The path of the sendmail binary.
+-
+-Defaults to `/usr/lib/sendmail`.
+-
+ |D |SOGoForceExternalLoginWithEmail
+ |Parameter used to specify if, when logging in to the SMTP server, the
+ primary email address of the user will be used instead of the username.
diff -Nru sogo-5.8.0/debian/patches/fix-mkcol-sql.patch 
sogo-5.8.0/debian/patches/fix-mkcol-sql.patch
--- sogo-5.8.0/debian/patches/fix-mkcol-sql.patch       1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/fix-mkcol-sql.patch       2026-11-10 
20:36:19.000000000 -0600
@@ -0,0 +1,378 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/78807d01aee9195b95e2ed21344f260aadb2faef
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. Folder names are quoted with the database adaptor
+after the path is split, so the uid used for the table name stays raw.
+The caller releases the database channel once on every return.
+
+From 78807d01aee9195b95e2ed21344f260aadb2faef Mon Sep 17 00:00:00 2001
+From: Hivert Quentin <[email protected]>
+Date: Mon, 10 Aug 2026 19:31:32 +0200
+Subject: fix(dav): properly parse MKCOL request
+
+--- a/SOPE/GDLContentStore/GCSFolderManager.m
++++ b/SOPE/GDLContentStore/GCSFolderManager.m
+@@ -32,10 +32,13 @@
+ #import <NGExtensions/NSObject+Logs.h>
+ 
+ #import <GDLAccess/EOAdaptorContext.h>
++#import <GDLAccess/EOAdaptor.h>
++#import <GDLAccess/EOAttribute.h>
+ #import <NGExtensions/NGResourceLocator.h>
+ 
+ #import "GCSChannelManager.h"
+ #import "EOAdaptorChannel+GCS.h"
++#import "EOQualifier+GCS.h"
+ #import "GCSAlarmsFolder.h"
+ #import "GCSFolder.h"
+ #import "GCSFolderType.h"
+@@ -381,27 +384,18 @@ static BOOL       _singleStoreMode
+   return [[self channelManager] canConnect:[self folderInfoLocation]];
+ }
+ 
+-- (NSArray *)performSQL:(NSString *)_sql {
+-  EOAdaptorChannel *channel;
++- (NSArray *)performSQL:(NSString *)_sql withChannel:(EOAdaptorChannel 
*)channel {
+   NSException    *ex;
+   NSMutableArray *rows;
+   NSDictionary   *row;
+   NSArray        *attrs;
+-  
+-  /* acquire channel */
+-  
+-  if ((channel = [self acquireOpenChannel]) == nil) {
+-    if (debugOn) [self debugWithFormat:@"could not acquire channel!"];
+-    return nil;
+-  }
+-  if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
+-  
+-  /* run SQL */
+-  
++
++  /* The caller acquired the channel and releases it. Releasing it here
++     and again in the caller drops the channel twice. */
++
+   if ((ex = [channel evaluateExpressionX:_sql]) != nil) {
+     [self logWithFormat:@"ERROR(%s): cannot execute\n  SQL '%@':\n  %@", 
+         __PRETTY_FUNCTION__, _sql, ex];
+-    [self releaseChannel:channel];
+     return nil;
+   }
+   
+@@ -412,7 +406,6 @@ static BOOL       _singleStoreMode
+   while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
+     [rows addObject:row];
+   
+-  [self releaseChannel:channel];
+   return rows;
+ }
+ 
+@@ -499,9 +492,11 @@ static BOOL       _singleStoreMode
+ 
+ - (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names
+                                   exactMatch:(BOOL)_beExact 
orDirectSubfolderMatch:(BOOL)_directSubs
++                                    withChannel:(EOAdaptorChannel *)channel
+ {
+   /* generates a WHERE qualifier for matching the "quick" entries */
+   NSMutableString *sql;
++  EOAdaptor *adaptor;
+   unsigned i, count;
+   
+   if ((count = [_names count]) == 0) {
+@@ -509,10 +504,12 @@ static BOOL       _singleStoreMode
+         __PRETTY_FUNCTION__];
+     return @"1 = 2";
+   }
+-  
++
++  adaptor = [[channel adaptorContext] adaptor];
+   sql = [NSMutableString stringWithCapacity:(count * 8)];
+   for (i = 0; i < quickPathCount; i++) {
+     NSString *pathColumn;
++    EOQualifier *qualifier;
+     char buf[32];
+     
+     sprintf(buf, GCSPathColumnPattern, (i + 1));
+@@ -523,8 +520,11 @@ static BOOL       _singleStoreMode
+     if (i < count) {
+       /* exact match, regular column */
+       if ([sql length] > 0) [sql appendString:@" AND "];
+-      [sql appendString:pathColumn];
+-      [sql appendFormat:@" = '%@'", [_names objectAtIndex:i]];
++      qualifier = [[EOKeyValueQualifier alloc] initWithKey: pathColumn
++                                          operatorSelector: 
EOQualifierOperatorEqual
++                                                     value: [_names 
objectAtIndex:i]];
++      [qualifier appendSQLToString: sql withAdaptor: adaptor];
++      [qualifier release];
+     }
+     else if (_beExact) {
+       /* exact match, ensure that all additional quick-cols are NULL */
+@@ -555,9 +555,14 @@ static BOOL       _singleStoreMode
+   }
+   
+   if (_beExact && (count > quickPathCount)) {
+-    [sql appendString:@" AND c_foldername = '"];
+-    [sql appendString:[_names lastObject]];
+-    [sql appendString:@"'"];
++    EOQualifier *qualifier;
++
++    [sql appendString:@" AND "];
++    qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_foldername"
++                                        operatorSelector: 
EOQualifierOperatorEqual
++                                                   value: [_names 
lastObject]];
++    [qualifier appendSQLToString: sql withAdaptor: adaptor];
++    [qualifier release];
+   }
+   
+   return sql;
+@@ -565,13 +570,15 @@ static BOOL       _singleStoreMode
+ 
+ - (NSString *)generateSQLPathFetchForInternalNames:(NSArray *)_names
+                                       exactMatch:(BOOL)_beExact 
orDirectSubfolderMatch:(BOOL)_directSubs
++                                        withChannel:(EOAdaptorChannel 
*)channel
+ {
+   /* fetches the 'path' subset for a given quick-names */
+   NSMutableString *sql;
+   NSString *ws;
+   
+   ws = [self generateSQLWhereForInternalNames:_names 
+-           exactMatch:_beExact orDirectSubfolderMatch:_directSubs];
++           exactMatch:_beExact orDirectSubfolderMatch:_directSubs
++             withChannel:channel];
+   if ([ws length] == 0)
+     return nil;
+   
+@@ -692,19 +699,24 @@ static BOOL       _singleStoreMode
+   NSArray  *fnames, *records;
+   unsigned int count;
+   NSDictionary *record;
++  EOAdaptorChannel *channel;
+   BOOL result;
+   
+   result = NO;
+ 
++  if ((channel = [self acquireOpenChannel]) == nil)
++    return NO;
++
+   fnames = [self internalNamesFromPath: _path];
+   if (fnames)
+     {
+       sql = [self generateSQLPathFetchForInternalNames: fnames 
+                 exactMatch: YES
+-                orDirectSubfolderMatch: NO];
++                orDirectSubfolderMatch: NO
++                  withChannel: channel];
+       if ([sql length])
+       {
+-        records = [self performSQL: sql];
++        records = [self performSQL: sql withChannel: channel];
+         if (records)
+           {
+             count = [records count];
+@@ -730,7 +742,8 @@ static BOOL       _singleStoreMode
+     }
+   else
+     [self debugWithFormat:@"got no internal names for path: '%@'", _path];
+-  
++
++  [self releaseChannel: channel];
+   return result;
+ }
+ 
+@@ -739,28 +752,38 @@ static BOOL       _singleStoreMode
+   NSString *fname;
+   NSArray  *fnames, *records;
+   NSString *sql;
++  EOAdaptorChannel *channel;
+   unsigned i, count;
++
++  if ((channel = [self acquireOpenChannel]) == nil)
++    return nil;
+   
+   if ((fnames = [self internalNamesFromPath:_path]) == nil) {
+     [self debugWithFormat:@"got no internal names for path: '%@'", _path];
++    [self releaseChannel: channel];
+     return nil;
+   }
+   
+   sql = [self generateSQLPathFetchForInternalNames:fnames 
+-            exactMatch:NO orDirectSubfolderMatch:(_recursive ? NO : YES)];
++            exactMatch:NO orDirectSubfolderMatch:(_recursive ? NO : YES)
++              withChannel: channel];
+   if ([sql length] == 0) {
+     [self debugWithFormat:@"got no SQL for names: %@", fnames];
++    [self releaseChannel: channel];
+     return nil;
+   }
+   
+-  if ((records = [self performSQL:sql]) == nil) {
++  if ((records = [self performSQL:sql withChannel: channel]) == nil) {
+     [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", 
+         __PRETTY_FUNCTION__, sql];
++    [self releaseChannel: channel];
+     return nil;
+   }
+   
+-  if ((count = [records count]) == 0)
++  if ((count = [records count]) == 0) {
++    [self releaseChannel: channel];
+     return emptyArray;
++  }
+ 
+   result = [NSMutableArray arrayWithCapacity:(count > 128 ? 128 : count)];
+   
+@@ -788,7 +811,8 @@ static BOOL       _singleStoreMode
+       [result addObject:spath];
+     }
+   }
+-  
++
++  [self releaseChannel: channel];
+   return result;
+ }
+ 
+@@ -798,16 +822,22 @@ static BOOL       _singleStoreMode
+   NSArray      *fnames, *records;
+   NSString     *ws;
+   NSDictionary *record;
++  EOAdaptorChannel *channel;
++
++  if ((channel = [self acquireOpenChannel]) == nil)
++    return nil;
+   
+   if ((fnames = [self internalNamesFromPath:_path]) == nil) {
+     [self debugWithFormat:@"got no internal names for path: '%@'", _path];
++    [self releaseChannel: channel];
+     return nil;
+   }
+   
+   /* generate SQL to fetch folder attributes */
+   
+   ws = [self generateSQLWhereForInternalNames:fnames 
+-           exactMatch:YES orDirectSubfolderMatch:NO];
++           exactMatch:YES orDirectSubfolderMatch:NO
++             withChannel: channel];
+   
+   sql = [NSMutableString stringWithCapacity:256];
+   [sql appendString:@"SELECT "];
+@@ -824,9 +854,10 @@ static BOOL       _singleStoreMode
+ 
+   /* fetching */
+   
+-  if ((records = [self performSQL:sql]) == nil) {
++  if ((records = [self performSQL:sql withChannel: channel]) == nil) {
+     [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", 
+         __PRETTY_FUNCTION__, sql];
++    [self releaseChannel: channel];
+     return nil;
+   }
+   
+@@ -837,19 +868,23 @@ static BOOL       _singleStoreMode
+   if ([records count] != 1) {
+     if ([records count] == 0) {
+       [self debugWithFormat:@"found no records for path: '%@'", _path];
++      [self releaseChannel: channel];
+       return nil;
+     }
+     
+     [self logWithFormat:@"ERROR(%s): more than one row for path: '%@'", 
+         __PRETTY_FUNCTION__, _path];
++    [self releaseChannel: channel];
+     return nil;
+   }
+   
+   if ((record = [self filterRecords:records forPath:_path]) == nil) {
+     [self debugWithFormat:@"found no record for path: '%@'", _path];
++    [self releaseChannel: channel];
+     return nil;
+   }
+ 
++  [self releaseChannel: channel];
+   return record;
+ }
+ 
+@@ -892,8 +927,11 @@ static BOOL       _singleStoreMode
+                                      atPath: (NSString *) path
+ {
+   NSString *baseURL, *tableName, *quickTableName, *aclTableName, 
*createQuery, *sql;
++  NSString *safePath, *safeFolderName, *safe1, *safe2, *safe3, *safe4;
+   GCSSpecialQueries *specialQuery;
+   EOAdaptorContext *aContext;
++  EOAdaptor *adaptor;
++  EOAttribute *attribute;
+   NSMutableArray *paths;
+   NSException *error;
+   NSRange range;
+@@ -906,14 +944,24 @@ static BOOL       _singleStoreMode
+   aContext = [channel adaptorContext];
+   [aContext beginTransaction];
+ 
++  /* Quote after the split. paths[2] is the raw uid used for the table name. 
*/
++  adaptor = [aContext adaptor];
++  attribute = [EOAttribute new];
++  [attribute setExternalType: @"varchar"];
++  [attribute autorelease];
++  safePath = [adaptor formatValue: path forAttribute: attribute];
++  safe1 = [adaptor formatValue: [paths objectAtIndex: 1] forAttribute: 
attribute];
++  safe2 = [adaptor formatValue: [paths objectAtIndex: 2] forAttribute: 
attribute];
++  safe3 = [adaptor formatValue: [paths objectAtIndex: 3] forAttribute: 
attribute];
++  safe4 = [adaptor formatValue: [paths objectAtIndex: 4] forAttribute: 
attribute];
++  safeFolderName = [adaptor formatValue: folderName forAttribute: attribute];
++
+   tableName = [self baseTableNameWithUID: [paths objectAtIndex: 2]];
+   quickTableName = [tableName stringByAppendingString: @"_quick"];
+   aclTableName = [tableName stringByAppendingString: @"_acl"];
+ 
+-  // TBD: fix SQL injection issues.
+-  // We no longer call stringByDeletingLastPathComponent since, since GNUstep 
1.22,
+-  // it'll replace // characters in the URL with /, so mysql:// becomes 
mysql:/
+-  // This is to conform with recent Apple changes.
++  /* baseURL is a server URL, not the folder path. GNUstep 1.22's
++     stringByDeletingLastPathComponent rewrites mysql:// to mysql:/. */
+   baseURL = [folderInfoLocation absoluteString];
+   range = [baseURL rangeOfString: @"/" options: NSBackwardsSearch];
+   if (range.location != NSNotFound)
+@@ -923,12 +971,12 @@ static BOOL       _singleStoreMode
+                 @"        (c_path, c_path1, c_path2, c_path3, c_path4,"
+                 @"         c_foldername, c_location, c_quick_location,"
+                 @"         c_acl_location, c_folder_type)"
+-                @" VALUES ('%@', '%@', '%@', '%@', '%@', '%@', '%@/%@',"
++                @" VALUES (%@, %@, %@, %@, %@, %@, '%@/%@',"
+                 @"         '%@/%@', '%@/%@', '%@')",
+-                [self folderInfoTableName], path,
+-                [paths objectAtIndex: 1], [paths objectAtIndex: 2],
+-                [paths objectAtIndex: 3], [paths objectAtIndex: 4],
+-                [folderName stringByReplacingString: @"'" withString: @"''"],
++                [self folderInfoTableName], safePath,
++                safe1, safe2,
++                safe3, safe4,
++                safeFolderName,
+                 baseURL, tableName,
+                 baseURL, quickTableName,
+                 baseURL, aclTableName,
+@@ -1024,17 +1072,19 @@ static BOOL       _singleStoreMode
+     return nil;
+   }
+ 
+-  ws = [self generateSQLWhereForInternalNames:fnames 
+-           exactMatch:YES orDirectSubfolderMatch:NO];
+-
+-  sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE %@",
+-                [self folderInfoTableName], ws];
+   if ((channel = [self acquireOpenChannel]) == nil) {
+     return [NSException exceptionWithName:@"GCSNoChannel"
+                       reason:@"could not "
+                       userInfo:nil];
+   }
+ 
++  ws = [self generateSQLWhereForInternalNames:fnames 
++           exactMatch:YES orDirectSubfolderMatch:NO
++             withChannel: channel];
++
++  sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE %@",
++                [self folderInfoTableName], ws];
++
+   [[channel adaptorContext] beginTransaction];
+ 
+   if ((ex = [channel evaluateExpressionX:sql]) != nil) {
diff -Nru sogo-5.8.0/debian/patches/fix-ssrf-external-calendar.patch 
sogo-5.8.0/debian/patches/fix-ssrf-external-calendar.patch
--- sogo-5.8.0/debian/patches/fix-ssrf-external-calendar.patch  1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/fix-ssrf-external-calendar.patch  2026-11-10 
22:04:11.000000000 -0600
@@ -0,0 +1,55 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/3a78a3bbb44484c95c141b0f3c0c8fabdc8bcf6a
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. Reloading an external calendar reports need-auth
+or a local failure instead of the upstream status code.
+
+From 3a78a3bbb44484c95c141b0f3c0c8fabdc8bcf6a Mon Sep 17 00:00:00 2001
+From: Hivert Quentin <[email protected]>
+Date: Tue, 9 Jun 2026 10:26:58 +0200
+Subject: do not transmit og http status to external calendar
+
+--- a/SoObjects/Appointments/SOGoWebAppointmentFolder.m
++++ b/SoObjects/Appointments/SOGoWebAppointmentFolder.m
+@@ -184,6 +184,10 @@ size_t curl_body_function(void *ptr, siz
+                   else
+                     [result setObject: @"invalid-calendar-content" forKey: 
@"error"];
+                 }
++              else if (status == 401)
++              {
++                [result setObject: @"need-auth" forKey: @"error"];
++              }
+               else
+                 [result setObject: @"http-error" forKey: @"error"];
+             }
+--- a/UI/Scheduler/UIxCalFolderActions.m
++++ b/UI/Scheduler/UIxCalFolderActions.m
+@@ -136,14 +136,25 @@
+  */
+ - (WOResponse *) reloadAction
+ {
+-  NSDictionary *results;
++  NSMutableDictionary *results;
++  NSString *error;
+   unsigned int httpCode;
+ 
+   httpCode = 200;
+   results = [[self clientObject] loadWebCalendar];
+ 
+   if ([results objectForKey: @"status"])
+-    httpCode = [[results objectForKey: @"status"] intValue];
++  {
++    if ([results objectForKey: @"error"])
++    {
++      error = [results objectForKey: @"error"];
++      if ([error isEqualToString: @"need-auth"])
++        httpCode = 401;
++      if ([error isEqualToString: @"invalid-calendar-content"] || [error 
isEqualToString: @"http-error"])
++        httpCode = 500;
++    }
++    [results removeObjectForKey:@"status"];
++  }
+ 
+   return [self responseWithStatus: httpCode andJSONRepresentation: results];
+ }
diff -Nru sogo-5.8.0/debian/patches/fix-xss-angular.patch 
sogo-5.8.0/debian/patches/fix-xss-angular.patch
--- sogo-5.8.0/debian/patches/fix-xss-angular.patch     1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/fix-xss-angular.patch     2026-11-10 
21:22:47.000000000 -0600
@@ -0,0 +1,332 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/47133fdf3bc3148d374bbf413f058d714776e832
+Origin: backport, 
https://github.com/Alinto/sogo/commit/1caa25711f5891135c7ebca63f33b8758c00c8e8
+Origin: backport, 
https://github.com/Alinto/sogo/commit/8813677ecb171e5d27cd582989c1d87b308c6df3
+Origin: backport, 
https://github.com/Alinto/sogo/commit/10dc1733496fb2f393537d864d83408e357f2215
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. stringWithoutHTMLInjection takes stripAngular.
+10dc173 repeats scheme removal until it is stable and neutralises the
+inline handlers the filter knows, including a decimal brace entity.
+The @import rule stays on its own expression.
+
+From 47133fdf3bc3148d374bbf413f058d714776e832 Mon Sep 17 00:00:00 2001
+From: Hivert Quentin <[email protected]>
+Date: Mon, 10 Aug 2026 15:39:40 +0200
+Subject: escape angularJS directive for description
+
+--- a/SoObjects/Mailer/SOGoDraftObject.m
++++ b/SoObjects/Mailer/SOGoDraftObject.m
+@@ -314,7 +314,7 @@ static NSString    *userAgent      = nil
+ 
+ - (void) setText: (NSString *) newText
+ {
+-  ASSIGN (text, [newText stringWithoutHTMLInjection: NO]);
++  ASSIGN (text, [newText stringWithoutHTMLInjection: NO stripAngular: NO]);
+ }
+ 
+ - (NSString *) text
+--- a/SoObjects/SOGo/NSString+Utilities.h
++++ b/SoObjects/SOGo/NSString+Utilities.h
+@@ -75,7 +75,8 @@
+ /* OpenSSL multiline DN */
+ - (NSArray *) componentsFromMultilineDN;
+ 
+-- (NSString *) stringWithoutHTMLInjection: (BOOL)stripHTMLCode;
++- (NSString *) stringWithoutHTMLInjection: (BOOL)stripHTMLCode
++                             stripAngular: (BOOL)stripAngular;
+ 
+ #ifndef GNUSTEP_BASE_LIBRARY
+ - (BOOL) boolValue;
+--- a/SoObjects/SOGo/NSString+Utilities.m
++++ b/SoObjects/SOGo/NSString+Utilities.m
+@@ -896,18 +896,19 @@ static int cssEscapingCount;
+  * @param stripHTMLCode Remove all HTML code from content
+  * @return A safe string
+  */
+-- (NSString *) stringWithoutHTMLInjection: (BOOL)stripHTMLCode
++- (NSString *) stringWithoutHTMLInjection: (BOOL)stripHTMLCode stripAngular: 
(BOOL)stripAngular
+ {
+   NSString *result, *text, *newResult;
+   NSScanner *theScanner;
+   NSError *error;
+   NSUInteger numberOfMatches;
+-  NSRegularExpression *regex;
++  NSRegularExpression *regex, *importRegex;
+ 
+   text = nil;
+   error = nil;
+   result = [NSString stringWithString: self];
+   regex = nil;
++  importRegex = nil;
+   
+   NS_DURING
+   {
+@@ -946,20 +947,26 @@ static int cssEscapingCount;
+       // Remove javascript:
+       regex = [NSRegularExpression 
regularExpressionWithPattern:@"j[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*a[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*v[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*a[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*c[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*:"
+                                   options: NSRegularExpressionCaseInsensitive 
error:&error];
+-      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@""];
+-      result = [NSString stringWithString: newResult];
++      while ([regex numberOfMatchesInString:result options:0 
range:NSMakeRange(0, [result length])] > 0) {
++        newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@""];
++        result = [NSString stringWithString: newResult];
++      }
+ 
+       // Remove vbscript:
+       regex = [NSRegularExpression 
regularExpressionWithPattern:@"v[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*b[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*c[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*:"
+                                   options: NSRegularExpressionCaseInsensitive 
error:&error];
+-      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@""];
+-      result = [NSString stringWithString: newResult];
++      while ([regex numberOfMatchesInString:result options:0 
range:NSMakeRange(0, [result length])] > 0) {
++        newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@""];
++        result = [NSString stringWithString: newResult];
++      }
+ 
+       // Remove livescript:
+       regex = [NSRegularExpression 
regularExpressionWithPattern:@"l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*v[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*c[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*:"
+                                   options: NSRegularExpressionCaseInsensitive 
error:&error];
+-      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@""];
+-      result = [NSString stringWithString: newResult];
++      while ([regex numberOfMatchesInString:result options:0 
range:NSMakeRange(0, [result length])] > 0) {
++        newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@""];
++        result = [NSString stringWithString: newResult];
++      }
+ 
+       // Remove <script
+       regex = [NSRegularExpression 
regularExpressionWithPattern:@"<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*c[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t"
 
+@@ -991,36 +998,35 @@ static int cssEscapingCount;
+       newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"</for*"];
+       result = [NSString stringWithString: newResult];
+ 
+-      // Remove onload
+-      regex = [NSRegularExpression regularExpressionWithPattern:@"onload=" 
+-                                  options: NSRegularExpressionCaseInsensitive 
error:&error];
+-      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"onl***="];
+-      result = [NSString stringWithString: newResult];
+-
+-      // Remove onmouseover
+-      regex = [NSRegularExpression 
regularExpressionWithPattern:@"onmouseover=" 
++      // One rule for the inline handlers this filter knows. A single pass of
++      // onload= left "javajavascript:script:" able to rebuild a scheme, and
++      // names outside that list were left intact.
++      regex = [NSRegularExpression 
regularExpressionWithPattern:@"\\bon(click|error|focus|load|mouseover|animationstart)[\\s\\u200B&#x09;&#x0A;&#x0D;\\r\\n\\t]*="
+                                   options: NSRegularExpressionCaseInsensitive 
error:&error];
+-      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"onmouseo***="];
++      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"data-blocked="];
+       result = [NSString stringWithString: newResult];
+ 
+-      // Remove onrepeat
+-      regex = [NSRegularExpression regularExpressionWithPattern:@"onrepeat=" 
++      // Remove @import css (in style tags)
++      importRegex = [NSRegularExpression 
regularExpressionWithPattern:@"(<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e.*)([\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*@[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*m[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*o[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t)(.*<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*\\/[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*>)"
+                                   options: NSRegularExpressionCaseInsensitive 
error:&error];
+-      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"onrep***="];
++      newResult = [importRegex stringByReplacingMatchesInString:result 
options:0 range:NSMakeRange(0, [result length]) withTemplate:@"$1@im****$3"];
+       result = [NSString stringWithString: newResult];
+ 
+-      // Remove onerror
+-      regex = [NSRegularExpression regularExpressionWithPattern:@"onerror=" 
+-                                  options: NSRegularExpressionCaseInsensitive 
error:&error];
+-      newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"onerr***="];
+-      result = [NSString stringWithString: newResult];
++      if(stripAngular) {
++        // Remove {{ and }} as they are interprated by angularJS with no way 
of escaping
++        regex = [NSRegularExpression 
regularExpressionWithPattern:@"(\\{\\{)|(&#x0*7b;&#x0*7b;)|(&#0*123;&#0*123;)"
++                                    options: 
NSRegularExpressionCaseInsensitive error:&error];
++        newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"{\\\\{"];
++        result = [NSString stringWithString: newResult];
++        regex = [NSRegularExpression 
regularExpressionWithPattern:@"(\\}\\})|(&#x0*7d;&#x0*7d;)|(&#0*125;&#0*125;)"
++                                    options: 
NSRegularExpressionCaseInsensitive error:&error];
++        newResult = [regex stringByReplacingMatchesInString:result options:0 
range:NSMakeRange(0, [result length]) withTemplate:@"}/}"];
++        result = [NSString stringWithString: newResult];
++      }
+ 
+-      // Remove @import css (in style tags)
+-      regex = [NSRegularExpression 
regularExpressionWithPattern:@"(<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e.*)([\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*@[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*m[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*o[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t)(.*<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*\\/[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*>)"
 
+-                                  options: NSRegularExpressionCaseInsensitive 
error:&error];
+       newResult = result;
+-      while([regex numberOfMatchesInString:newResult options:0 
range:NSMakeRange(0, [newResult length])] > 0) {
+-        newResult = [regex stringByReplacingMatchesInString:newResult 
options:0 range:NSMakeRange(0, [newResult length]) withTemplate:@"$1@im****$3"];
++      while([importRegex numberOfMatchesInString:newResult options:0 
range:NSMakeRange(0, [newResult length])] > 0) {
++        newResult = [importRegex stringByReplacingMatchesInString:newResult 
options:0 range:NSMakeRange(0, [newResult length]) withTemplate:@"$1@im****$3"];
+       }
+       result = [NSString stringWithString: newResult];
+     }
+--- a/SoObjects/SOGo/SOGoUserDefaults.m
++++ b/SoObjects/SOGo/SOGoUserDefaults.m
+@@ -147,7 +147,7 @@ NSString *SOGoPasswordRecoverySecondaryE
+       rc = NO;
+ 
+       if ([fullName length])
+-        [identity setObject: [fullName stringWithoutHTMLInjection: YES] 
forKey: @"fullName"];
++        [identity setObject: [fullName stringWithoutHTMLInjection: YES 
stripAngular:NO] forKey: @"fullName"];
+       if ([email length])
+         [identity setObject: email forKey: @"email"];
+       if ([replyTo length])
+@@ -809,7 +809,7 @@ NSString *SOGoPasswordRecoverySecondaryE
+     if (mailIdentity && [mailIdentity objectForKey: @"fullName"]) {
+       fullName = [NSString stringWithString: [mailIdentity objectForKey: 
@"fullName"]];
+       if (fullName) {
+-        [mailIdentity setObject: [fullName stringWithoutHTMLInjection: YES] 
forKey: @"fullName"];
++        [mailIdentity setObject: [fullName stringWithoutHTMLInjection: YES 
stripAngular:NO] forKey: @"fullName"];
+         [mailIdentities setObject: mailIdentity atIndexedSubscript: i];
+       }
+     }
+--- a/Tests/Unit/TestNSString+Utilities.m
++++ b/Tests/Unit/TestNSString+Utilities.m
+@@ -99,17 +99,24 @@
+ 
+ - (void) test_stringWithoutHTMLInjection
+ {
+-  testEquals([[NSString stringWithString:@"<a href=\"\">foo</a>bar"] 
stringWithoutHTMLInjection: YES], @" foo bar");
+-  testEquals([[NSString stringWithString:@"fb <[email protected]>"] 
stringWithoutHTMLInjection: YES], @"fb <[email protected]>");
+-  testEquals([[NSString stringWithString:@"Test\n<script>alert(\"foobar\");"] 
stringWithoutHTMLInjection: NO], @"Test\n<scr***>alert(\"foobar\");");
+-  testEquals([[NSString stringWithString:@"<img vbscript:test"] 
stringWithoutHTMLInjection: NO], @"<img test");
+-  testEquals([[NSString stringWithString:@"<img javascript:test"] 
stringWithoutHTMLInjection: NO], @"<img test");
+-  testEquals([[NSString stringWithString:@"<img livescript:test"] 
stringWithoutHTMLInjection: NO], @"<img test");
+-  testEquals([[NSString stringWithString:@"foobar <form 
action=\"\">bar</form>"] stringWithoutHTMLInjection: NO], @"foobar <for* 
action=\"\">bar</for*>");
+-  testEquals([[NSString stringWithString:@"foobar <iframe 
src=\"\">bar</iframe>"] stringWithoutHTMLInjection: NO], @"foobar <ifr*** 
src=\"\">bar</iframe>");
+-  testEquals([[NSString stringWithString:@"foobar <img onload=foo bar"] 
stringWithoutHTMLInjection: NO], @"foobar <img onl***=foo bar");
+-  testEquals([[NSString stringWithString:@"foobar <img onmouseover=foo bar"] 
stringWithoutHTMLInjection: NO], @"foobar <img onmouseo***=foo bar");
+-  testEquals([[NSString stringWithString:@"<!DOCTYPE 
html><html><head><style>@import 
url(https://foo.bar/malicious.css);.foo{background-color: red; @import 
url(https://bar.foo/malicious2.css);</style></head><body><table><tr><td>A</td><td>B</td><td>C</td></tr></table></body></html>"]
 stringWithoutHTMLInjection: NO], @"<!DOCTYPE html><html><head><style>@im**** 
url(https://foo.bar/malicious.css);.foo{background-color: red; @im**** 
url(https://bar.foo/malicious2.css);</style></head><body><table><tr><td>A</td><td>B</td><td>C</td></tr></table></body></html>");
++  testEquals([[NSString stringWithString:@"<a href=\"\">foo</a>bar"] 
stringWithoutHTMLInjection: YES stripAngular: NO], @" foo bar");
++  testEquals([[NSString stringWithString:@"fb <[email protected]>"] 
stringWithoutHTMLInjection: YES stripAngular: NO], @"fb <[email protected]>");
++  testEquals([[NSString stringWithString:@"Test\n<script>alert(\"foobar\");"] 
stringWithoutHTMLInjection: NO stripAngular: NO], 
@"Test\n<scr***>alert(\"foobar\");");
++  testEquals([[NSString stringWithString:@"<img vbscript:test"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"<img test");
++  testEquals([[NSString stringWithString:@"<img javascript:test"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"<img test");
++  testEquals([[NSString stringWithString:@"<img livescript:test"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"<img test");
++  testEquals([[NSString stringWithString:@"foobar <form 
action=\"\">bar</form>"] stringWithoutHTMLInjection: NO stripAngular: NO], 
@"foobar <for* action=\"\">bar</for*>");
++  testEquals([[NSString stringWithString:@"foobar <iframe 
src=\"\">bar</iframe>"] stringWithoutHTMLInjection: NO stripAngular: NO], 
@"foobar <ifr*** src=\"\">bar</iframe>");
++  testEquals([[NSString stringWithString:@"foobar <img onload=foo bar"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"foobar <img 
data-blocked=foo bar");
++  testEquals([[NSString stringWithString:@"foobar <img onmouseover=foo bar"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"foobar <img 
data-blocked=foo bar");
++  testEquals([[NSString stringWithString:@"<img src=x onerror =alert(1)>"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"<img src=x 
data-blocked=alert(1)>");
++  testEquals([[NSString stringWithString:@"<input autofocus 
onfocus=alert(1)>"] stringWithoutHTMLInjection: NO stripAngular: NO], @"<input 
autofocus data-blocked=alert(1)>");
++  testEquals([[NSString stringWithString:@"javajavascript:script:"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"");
++  testEquals([[NSString 
stringWithString:@"&#123;&#123;1337*1337&#125;&#125;"] 
stringWithoutHTMLInjection: NO stripAngular: YES], @"{\\{1337*1337}/}");
++  testEquals([[NSString stringWithString:@"<!DOCTYPE 
html><html><head><style>@import 
url(https://foo.bar/malicious.css);.foo{background-color: red; @import 
url(https://bar.foo/malicious2.css);</style></head><body><table><tr><td>A</td><td>B</td><td>C</td></tr></table></body></html>"]
 stringWithoutHTMLInjection: NO stripAngular: NO], @"<!DOCTYPE 
html><html><head><style>@im**** 
url(https://foo.bar/malicious.css);.foo{background-color: red; @im**** 
url(https://bar.foo/malicious2.css);</style></head><body><table><tr><td>A</td><td>B</td><td>C</td></tr></table></body></html>");
++  testEquals([[NSString stringWithString:@"<style>@import 
url(https://foo.bar/malicious.css);</style>"] stringWithoutHTMLInjection: NO 
stripAngular: YES], @"<style>@im**** 
url(https://foo.bar/malicious.css);</style>");
++  testEquals([[NSString stringWithString:@"Hello {{7*7}}"] 
stringWithoutHTMLInjection: NO stripAngular: NO], @"Hello {{7*7}}");
++  testEquals([[NSString stringWithString:@"Hello {{7*7}}"] 
stringWithoutHTMLInjection: NO stripAngular: YES], @"Hello {\\{7*7}/}");
+ }
+ 
+ 
+--- a/UI/Common/UIxParentFolderActions.m
++++ b/UI/Common/UIxParentFolderActions.m
+@@ -61,7 +61,7 @@
+   request = [context request];
+   params = [[request contentAsString] objectFromJSONString];
+ 
+-  name = [[params objectForKey: @"name"] stringWithoutHTMLInjection: YES];
++  name = [[params objectForKey: @"name"] stringWithoutHTMLInjection: YES 
stripAngular:NO];
+   nameInContainer = nil;
+ 
+   if ([name length] > 0)
+--- a/UI/Contacts/UIxContactEditor.m
++++ b/UI/Contacts/UIxContactEditor.m
+@@ -485,7 +485,7 @@ static Class SOGoContactGCSEntryK = Nil;
+   co = [self clientObject];
+   card = [co vCard];
+   request = [context request];
+-  params = [[[request contentAsString] stringWithoutHTMLInjection: YES] 
objectFromJSONString];
++  params = [[[request contentAsString] stringWithoutHTMLInjection: YES 
stripAngular:NO] objectFromJSONString];
+   forceSave = [[params objectForKey: @"ignoreDuplicate"] boolValue];
+ 
+   [self setAttributes: params];
+--- a/UI/Contacts/UIxListEditor.m
++++ b/UI/Contacts/UIxListEditor.m
+@@ -310,7 +310,7 @@
+   [list retain];
+ 
+   request = [context request];
+-  params = [[[request contentAsString] stringWithoutHTMLInjection: YES] 
objectFromJSONString];
++  params = [[[request contentAsString] stringWithoutHTMLInjection: YES 
stripAngular:NO] objectFromJSONString];
+ 
+   o = [params objectForKey: @"refs"];
+   if (![o isKindOfClass: [NSArray class]])
+--- a/UI/MailerUI/UIxMailFolderActions.m
++++ b/UI/MailerUI/UIxMailFolderActions.m
+@@ -76,7 +76,7 @@
+ 
+   request = [context request];
+   params = [[request contentAsString] objectFromJSONString];
+-  folderName = [[params objectForKey: @"name"] stringWithoutHTMLInjection: 
YES];
++  folderName = [[params objectForKey: @"name"] stringWithoutHTMLInjection: 
YES stripAngular:NO];
+   if ([folderName length] > 0)
+     {
+       encodedFolderName = [folderName stringByEncodingImap4FolderName];
+@@ -142,7 +142,7 @@
+   // Retrieve new folder name from JSON payload
+   request = [context request];
+   params = [[request contentAsString] objectFromJSONString];
+-  newFolderName = [[params objectForKey: @"name"] stringWithoutHTMLInjection: 
YES];
++  newFolderName = [[params objectForKey: @"name"] stringWithoutHTMLInjection: 
YES stripAngular:NO];
+ 
+   if (!newFolderName || [newFolderName length] == 0)
+     {
+--- a/UI/MailerUI/UIxMailListActions.m
++++ b/UI/MailerUI/UIxMailListActions.m
+@@ -976,7 +976,7 @@
+           [msg addObject: [NSNumber numberWithBool: [self isMessageFlagged]]];
+ 
+           // Subject
+-          [msg addObject: [[[self messageSubject] stringWithoutHTMLInjection: 
YES] stringWithoutHTMLInjection: NO]];
++          [msg addObject: [[[self messageSubject] stringWithoutHTMLInjection: 
YES stripAngular:NO] stringWithoutHTMLInjection: NO stripAngular:NO]];
+       
+           // From
+           from = [[message objectForKey: @"envelope"] from];
+--- a/UI/MailerUI/UIxMailView.m
++++ b/UI/MailerUI/UIxMailView.m
+@@ -347,7 +347,7 @@ static NSString *mailETag = nil;
+   if ([self formattedDate])
+     [data setObject: [self formattedDate] forKey: @"date"];
+   if ([self messageSubject])
+-    [data setObject: [[[self messageSubject] stringWithoutHTMLInjection: YES] 
stringWithoutHTMLInjection: NO] forKey: @"subject"];
++    [data setObject: [[[self messageSubject] stringWithoutHTMLInjection: YES 
stripAngular:YES] stringWithoutHTMLInjection: NO stripAngular:YES] forKey: 
@"subject"];
+   if ((addresses = [addressFormatter dictionariesForArray: [co 
fromEnvelopeAddresses]]))
+     [data setObject: addresses forKey: @"from"];
+   if ((addresses = [addressFormatter dictionariesForArray: [co 
toEnvelopeAddresses]]))
+--- a/UI/SOGoUI/UIxComponent.m
++++ b/UI/SOGoUI/UIxComponent.m
+@@ -403,7 +403,7 @@ static SoProduct      *commonProduct
+   theme = [[context request] formValueForKey: @"theme"];
+   if ([theme length])
+   {
+-    safeTheme = [theme stringWithoutHTMLInjection: YES];
++    safeTheme = [theme stringWithoutHTMLInjection: YES stripAngular:NO];
+     if([safeTheme isEqualToString: theme])
+       rel = [NSString stringWithFormat: @"%@?theme=%@", rel, theme];
+   }
+--- a/UI/Scheduler/UIxAppointmentEditor.m
++++ b/UI/Scheduler/UIxAppointmentEditor.m
+@@ -556,7 +556,7 @@
+ 
+   ex = nil;
+   request = [context request];
+-  params = [[[request contentAsString] stringWithoutHTMLInjection: NO] 
objectFromJSONString];
++  params = [[[request contentAsString] stringWithoutHTMLInjection: NO 
stripAngular:NO] objectFromJSONString];
+   if (params == nil)
+     {
+       ex = [NSException exceptionWithName: @"JSONParsingException"
+--- a/UI/Scheduler/UIxCalendarSelector.m
++++ b/UI/Scheduler/UIxCalendarSelector.m
+@@ -182,7 +182,7 @@ _intValueFromHex (NSString *hexString)
+           fActiveTasks = [folder activeTasks];
+ 
+           [calendar setObject: folderName forKey: @"id"];
+-          [calendar setObject: [fDisplayName stringWithoutHTMLInjection: YES] 
forKey: @"name"];
++          [calendar setObject: [fDisplayName stringWithoutHTMLInjection: YES 
stripAngular:NO] forKey: @"name"];
+           [calendar setObject: [folder calendarColor] forKey: @"color"];
+           isActive = [NSNumber numberWithBool: [folder isActive]];
+           [calendar setObject: isActive forKey: @"active"];
+--- a/UI/Scheduler/UIxTaskEditor.m
++++ b/UI/Scheduler/UIxTaskEditor.m
+@@ -335,7 +335,7 @@
+ 
+   ex = nil;
+   request = [context request];
+-  params = [[[request contentAsString] stringWithoutHTMLInjection: NO] 
objectFromJSONString];
++  params = [[[request contentAsString] stringWithoutHTMLInjection: NO 
stripAngular:NO] objectFromJSONString];
+   if (params == nil)
+     {
+       ex = [NSException exceptionWithName: @"JSONParsingException"
diff -Nru sogo-5.8.0/debian/patches/fix-xss-mail-parts.patch 
sogo-5.8.0/debian/patches/fix-xss-mail-parts.patch
--- sogo-5.8.0/debian/patches/fix-xss-mail-parts.patch  1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/fix-xss-mail-parts.patch  2026-11-11 
10:08:52.000000000 -0600
@@ -0,0 +1,209 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/0d66d7503d8fd147842e87b9b4f1b921986ef44b
+Origin: backport, 
https://github.com/Alinto/sogo/commit/045a0b9c0ac57aeea1b619f281acff0427cd81c7
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. Invitation description, comment, and organizer
+are escaped before the template writes them, and an attachment title
+drops a quote.
+
+From 0d66d7503d8fd147842e87b9b4f1b921986ef44b Mon Sep 17 00:00:00 2001
+From: Jan Kahmen <[email protected]>
+Date: Mon, 17 Aug 2026 23:03:41 +0200
+Subject: fix(mail): stop interpreting iMIP card text fields as markup
+
+--- a/UI/MailPartViewers/UIxMailPartICalViewer.h
++++ b/UI/MailPartViewers/UIxMailPartICalViewer.h
+@@ -44,6 +44,9 @@
+ - (NSString *) startDate;
+ - (NSString *) startTime;
+ - (BOOL) isEndDateOnSameDay;
++- (NSString *) organizerHref;
++- (NSString *) userComment;
++- (NSString *) eventDescription;
+ 
+ @end
+ 
+--- a/UI/MailPartViewers/UIxMailPartICalViewer.m
++++ b/UI/MailPartViewers/UIxMailPartICalViewer.m
+@@ -32,6 +32,7 @@
+ 
+ #import <NGExtensions/NSCalendarDate+misc.h>
+ #import <NGExtensions/NSObject+Logs.h>
++#import <NGExtensions/NSString+misc.h>
+ 
+ #import <NGImap4/NGImap4EnvelopeAddress.h>
+ 
+@@ -408,6 +409,18 @@
+   return YES;
+ }
+ 
++- (NSString *) organizerHref
++{
++  NSString *address;
++
++  address = [[[self inEvent] organizer] rfc822Email];
++  if (![address length])
++    return nil;
++
++  return [[NSString stringWithFormat: @"mailto:%@";, address]
++           stringByEscapingHTMLAttributeValue];
++}
++
+ - (NSString *) organizerDisplayName
+ {
+   iCalPerson *organizer;
+@@ -587,10 +600,20 @@
+ 
+   //Sanitise the html content
+   if([d objectForKey:@"content"]){
+-    [d setObject: [[d objectForKey:@"content"] stringWithoutHTMLInjection: 
NO] forKey: @"content"];
++    [d setObject: [[d objectForKey:@"content"] stringWithoutHTMLInjection: NO 
stripAngular:YES] forKey: @"content"];
+   }
+ 
+   return d;
+ }
+ 
++- (NSString *) userComment
++{
++  return [[[self inEvent] userComment] stringByEscapingHTMLString];
++}
++
++- (NSString *) eventDescription
++{
++  return [[[self authorativeEvent] comment] stringByEscapingHTMLString];
++}
++
+ @end /* UIxMailPartICalViewer */
+--- a/UI/MailPartViewers/UIxMailPartViewer.h
++++ b/UI/MailPartViewers/UIxMailPartViewer.h
+@@ -83,6 +83,7 @@
+ - (NSString *)preferredPathExtension;
+ - (NSString *)filename;
+ - (NSString *)filenameForDisplay;
++- (NSString *)filenameForTitle;
+ - (NSFormatter *)sizeFormatter;
+ 
+ /* caches */
+--- a/UI/MailPartViewers/UIxMailPartViewer.m
++++ b/UI/MailPartViewers/UIxMailPartViewer.m
+@@ -198,7 +198,7 @@
+   return [NSDictionary dictionaryWithObjectsAndKeys:
+                          [self className], @"type",
+                        type, @"contentType",
+-                       [[[self generateResponse] contentAsString] 
stringWithoutHTMLInjection: NO], @"content",
++                       [[[self generateResponse] contentAsString] 
stringWithoutHTMLInjection: NO stripAngular:YES], @"content",
+                        [self filenameForDisplay], @"filename",
+                        [self preferredPathExtension], @"extension",
+                        [[self sizeFormatter] stringForObjectValue: [bodyInfo 
objectForKey: @"size"]], @"size",
+@@ -314,6 +314,14 @@
+     : (id)@"untitled";
+ }
+ 
++- (NSString *) filenameForTitle
++{
++  /* The attribute writer escapes & and leaves a quote in the filename
++     able to close the title attribute. */
++  return [[self filenameForDisplay] stringByReplacingOccurrencesOfString: 
@"\""
++                                                              withString: 
@""];
++}
++
+ - (NSFormatter *) sizeFormatter
+ {
+   return [UIxMailSizeFormatter sharedMailSizeFormatter];
+--- a/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox
++++ b/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox
+@@ -17,7 +17,7 @@
+           </div>
+       </md-toolbar>
+       <md-whiteframe class="md-whiteframe-z1" layout="row" 
layout-align="space-between center">
+-        <pre><var:string value="flatContentAsString" /></pre>
++        <pre ng-non-bindable=""><var:string value="flatContentAsString" 
/></pre>
+       </md-whiteframe>
+     </div>
+   </var:if> <!-- if condition="couldParseCalendar" const:negate="1" -->
+@@ -137,7 +137,7 @@
+ 
+             <p>
+               <var:string label:value="Organizer" />
+-              <a var:href="inEvent.organizer.email"
++              <a ng-non-bindable="" var:href="organizerHref"
+                ><var:string value="organizerDisplayName" /></a>
+               <var:string label:value="request_info" />
+             </p>
+@@ -147,7 +147,7 @@
+           <var:if condition="isLoggedInUserAnAttendee" const:negate="YES">
+             <p>
+               <var:string label:value="Organizer" />
+-              <a var:href="inEvent.organizer.email">
++              <a ng-non-bindable="" var:href="organizerHref">
+                 <var:string value="organizerDisplayName" />
+               </a>
+               <var:string label:value="request_info_no_attendee" />
+@@ -233,14 +233,14 @@
+         </var:if>
+ 
+         <!-- the user comment is used in replies -->
+-        <var:if condition="inEvent.userComment.isNotEmpty">
++        <var:if condition="userComment.isNotEmpty">
+           <div class="pseudo-input-container">
+             <label class="pseudo-input-label">
+               <var:string label:value="Comment"/>
+             </label>
+-            <div>
++            <div ng-non-bindable="">
+               <md-content>
+-                <var:string value="inEvent.userComment.stringByDetectingURLs" 
const:insertBR="1" const:escapeHTML="NO"/>
++                <var:string value="userComment.stringByDetectingURLs" 
const:insertBR="1" const:escapeHTML="NO"/>
+               </md-content>
+             </div>
+           </div>
+@@ -251,7 +251,7 @@
+       <var:if condition="hasOrganizer" const:value="true">
+           <div class="pseudo-input-container">
+             <label class="pseudo-input-label"><var:string 
label:value="Organizer"/></label>
+-            <div>
++            <div ng-non-bindable="">
+               <var:string value="organizerDisplayName"/>
+             </div>
+           </div>
+@@ -284,14 +284,14 @@
+           </div>
+         </md-list>
+ 
+-        <var:if condition="authorativeEvent.comment.isNotEmpty">
++        <var:if condition="eventDescription.isNotEmpty">
+           <div class="pseudo-input-container">
+             <label class="pseudo-input-label">
+               <var:string label:value="Description"/>
+             </label>
+-            <div>
++            <div ng-non-bindable="">
+               <md-content>
+-                <var:string 
value="authorativeEvent.comment.stringByDetectingURLs" const:insertBR="1" 
const:escapeHTML="NO"/>
++                <var:string value="eventDescription.stringByDetectingURLs" 
const:insertBR="1" const:escapeHTML="NO"/>
+               </md-content>
+             </div>
+           </div>
+--- a/UI/Templates/MailPartViewers/UIxMailPartImageViewer.wox
++++ b/UI/Templates/MailPartViewers/UIxMailPartImageViewer.wox
+@@ -10,7 +10,7 @@
+     <img var:src="pathToAttachment"
+          var:title="filenameForDisplay"><!-- image --></img>
+     <md-card-content>
+-      <p class="md-caption" var:title="filenameForDisplay">
++      <p class="md-caption" var:title="filenameForTitle">
+         <var:string value="filenameForDisplay" />
+       </p>
+     </md-card-content>
+--- a/UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox
++++ b/UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox
+@@ -6,7 +6,7 @@
+   xmlns:label="OGo:label">
+   <md-card>
+     <md-card-content>
+-      <p class="md-caption sg-attachment-name" var:title="filenameForDisplay">
++      <p class="md-caption sg-attachment-name" var:title="filenameForTitle">
+         <var:if condition="preferredPathExtension.length"><span 
class="sg-label-outline"><var:string 
value="preferredPathExtension"/></span></var:if>
+         <var:string value="filenameForDisplay"/>
+       </p>
diff -Nru sogo-5.8.0/debian/patches/fix-xss-sort-values.patch 
sogo-5.8.0/debian/patches/fix-xss-sort-values.patch
--- sogo-5.8.0/debian/patches/fix-xss-sort-values.patch 1969-12-31 
18:00:00.000000000 -0600
+++ sogo-5.8.0/debian/patches/fix-xss-sort-values.patch 2026-11-11 
09:41:18.000000000 -0600
@@ -0,0 +1,106 @@
+Origin: backport, 
https://github.com/Alinto/sogo/commit/d089c4046ffac8124874c504491db9d1f15ae650
+Origin: backport, 
https://github.com/Alinto/sogo/commit/6c93d6b66cd2e53d66b9f921ef9223b9598b040d
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-10
+
+Backported to 5.8.0. Sort and list values are accepted only from the
+known set. Mail sorts are compared after the server uppercases them.
+
+From d089c4046ffac8124874c504491db9d1f15ae650 Mon Sep 17 00:00:00 2001
+From: Hivert Quentin <[email protected]>
+Date: Thu, 3 Sep 2026 14:42:51 +0200
+Subject: use legitimate sorting value
+
+--- a/UI/Contacts/UIxContactsListActions.m
++++ b/UI/Contacts/UIxContactsListActions.m
+@@ -114,12 +114,18 @@
+ {
+   NSMutableDictionary *contactSettings;
+   NSString *ascending, *sort;
++  NSArray *sortValues;
+   SOGoUserSettings *us;
+ 
+   sort = [[self requestData] objectForKey: @"sort"];
+   ascending = [[self requestData] objectForKey: @"asc"];
+ 
+-  if ([sort length])
++  //Check value of sorting
++  sortValues = [NSArray arrayWithObjects: @"c_cn", @"c_sn",
++                    @"c_givenname", @"c_mail", @"c_screenname", @"c_o", 
@"c_telephonenumber", nil];
++
++
++  if ([sort length] && [sortValues containsObject:sort])
+     {
+       sort = [self sortKey];
+       us = [[context activeUser] userSettings];
+--- a/UI/MailerUI/UIxMailListActions.m
++++ b/UI/MailerUI/UIxMailListActions.m
+@@ -361,6 +361,7 @@
+   NSString *sort, *module;
+   NSMutableDictionary *moduleSettings;
+   NSDictionary *urlParams, *sortingAttributes;
++  NSArray *sortValues;
+   SOGoUser *activeUser;
+   SOGoUserSettings *us;
+   BOOL asc, dry;
+@@ -377,7 +378,11 @@
+   us = [activeUser userSettings];
+   moduleSettings = [us objectForKey: module];
+ 
+-  if ([sort length])
++  //Check sort value
++  sortValues = [NSArray arrayWithObjects: @"SUBJECT", @"FROM",
++                    @"DATE", @"SIZE", @"ARRIVAL", nil];
++
++  if ([sort length] && [sortValues containsObject:sort])
+     {
+       if ([sort isEqualToString: [self defaultSortKey]] && !asc)
+       {
+--- a/UI/Scheduler/UIxCalListingActions.m
++++ b/UI/Scheduler/UIxCalListingActions.m
+@@ -780,12 +780,19 @@ static NSArray *tasksFields = nil;
+ - (void) saveSortValue: (NSString *) submodule
+ {
+   NSString *sort, *ascending;
++  NSArray *sortValues;
+   SOGoUserSettings *us;
+   NSMutableDictionary *calendarSettings;
+ 
+   sort = [[context request] formValueForKey: @"sort"];
+   ascending = [[context request] formValueForKey: @"asc"];
+-  if ([sort length])
++
++  //Check value of sorting
++  sortValues = [NSArray arrayWithObjects: @"title", @"location",
++                    @"calendarName", @"start", @"priority", @"category", 
@"status", @"end", nil];
++
++
++  if ([sort length] && [sortValues containsObject:sort])
+   {
+     us = [[context activeUser] userSettings];
+     calendarSettings = [us objectForKey: @"Calendar"];
+--- a/UI/Scheduler/UIxCalMainView.m
++++ b/UI/Scheduler/UIxCalMainView.m
+@@ -253,6 +253,7 @@
+ {
+   WORequest *request;
+   NSDictionary *params;
++  NSArray *listValues;
+   NSString *selectedList;
+   
+   [self _setupContext];
+@@ -260,6 +261,14 @@
+   params = [[request contentAsString] objectFromJSONString];
+ 
+   selectedList = [params objectForKey: @"list"];
++
++  //Check value
++  listValues = [NSArray arrayWithObjects: @"eventsListView", 
@"tasksListView", nil];
++  if([selectedList length] && ![listValues containsObject:selectedList])
++  {
++    selectedList = @"eventsListView";
++  }
++
+   [moduleSettings setObject: selectedList
+                      forKey: @"SelectedList"];
+   [us synchronize];
diff -Nru sogo-5.8.0/debian/patches/series sogo-5.8.0/debian/patches/series
--- sogo-5.8.0/debian/patches/series    2026-06-21 04:32:55.000000000 -0500
+++ sogo-5.8.0/debian/patches/series    2026-11-11 10:08:52.000000000 -0600
@@ -24,3 +24,11 @@
 CVE-2026-8851_2.patch
 fix_xss_message_subject_rendering_2.patch
 git_clean_import_event.patch
+CVE-2026-33550-regression.patch
+disable-sendmail.patch
+fix-mkcol-sql.patch
+fix-xss-angular.patch
+fix-ssrf-external-calendar.patch
+CVE-2026-93453.patch
+fix-xss-sort-values.patch
+fix-xss-mail-parts.patch
-------------------------------------------------------------------------
Debian LTS Advisory DLA-XXXX-1                [email protected]
https://www.debian.org/lts/security/                        Simon Quigley
November 15, 2026                             https://wiki.debian.org/LTS
-------------------------------------------------------------------------

Package        : sogo
Version        : 5.8.0-2+deb12u4
CVE ID         : CVE-2026-33550 CVE-2026-93453 not available yet
Debian Bug     : 1142810 1148454

SOGo is a groupware server for mail, calendars, and contacts.

An attacker who can reach the server can run JavaScript in a user's
browser, write into an SQL statement, or pass a sender address to a
shell. An unauthenticated attacker can aim a password-reset link at
a host the attacker chooses, and the previous time-based one-time
password (TOTP) correction turned TOTP off for an account whose
secret was already stored.

CVE-2026-33550

    The previous correction turned TOTP off when the verification
    code was absent. It cleared TOTP for an account whose secret was
    already stored in totpKey and whose PrivateSalt was absent, so a
    later login without a verification code disabled TOTP for that
    account.

CVE-2026-93453

    Password recovery built the link in the reset mail from the
    Origin header of the request and the request URI, then sent that
    mail. An unauthenticated attacker can choose that Origin, so the
    link points at a host the attacker chooses.

The CVE identifiers for the script injection, the folder SQL
statement, the external-calendar status, and the sendmail transport
are not yet available.

A Web Distributed Authoring and Versioning (WebDAV) MKCOL (make
collection) request placed the folder name and path in the
folder-creation SQL statement. An attacker who can create a folder
can write that name into the statement.

Mail and calendar fields were copied into the page while they still
contained AngularJS interpolation, an inline event handler, or a
script scheme nested inside another scheme. A user who views the
field runs that script in the browser.

A sort or list value taken from the request was stored and later
written into the page. An attacker who can set that value can run
script when the saved list is displayed.

Invitation text and an attachment title were written into the page
as markup. An attacker who sends a crafted invitation can run script
in the browser of a user who opens it.

Reloading a subscribed external calendar copied the HTTP status of
the URL the server fetched into the browser response. The user who
asked for the reload was shown that remote server's status.

The sendmail transport built a shell command from the sender address
and ran it with popen. When mail is sent through that transport, the
sender address is passed to a shell.

For Debian 12 bookworm, these problems have been fixed in version
5.8.0-2+deb12u4.

We recommend that you upgrade your sogo packages.

For the detailed security status of sogo please refer to
its security tracker page at:
https://security-tracker.debian.org/tracker/sogo

Further information about Debian LTS security advisories, how to apply
these updates to your system and frequently asked questions can be
found at: https://wiki.debian.org/LTS

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to