Repository navigation
Re-published on GitHub on 18 October 2026 from the GitLab release v6.3.7 of 2026-10-17. Each file below was checked against the SHA-256 that GitLab's package registry records for it before it was uploaded here.
Download
| Windows, x64 | hMailServer-6.3.7-x64.exe: the installer, Authenticode-signed by Progressive Robot Ltd. It installs a new server, or upgrades an existing one in place: read the notes below and back up the database and the data folder first. SHA-256 dcae96bd75d6cae8e5a910d15940567067539ed99db5aeed40a815added4d85f. |
| Windows on ARM64 (preview) | hMailServer-6.3.7-arm64.exe, signed the same way. A preview: it has not been run on ARM64 hardware. A new installation is set up against Microsoft SQL Server or PostgreSQL, since SQL Server Compact has no ARM64 build (WindowsOnArm.md). SHA-256 56a5425bd4e50e6dc79a02c6b79ff0f6bcc4e88ef59d457652efcaaac67601dd. |
| Linux | The .deb and .rpm packages for each distribution and the two AppImages, under Assets below, in Packages; hmailserver-linux-6.3.7-SHA256SUMS.txt lists them. |
| Container image | registry.gitlab.com/hmailserver/hmailserver:6.3.7, for linux/amd64 and linux/arm64, pushed by this release's pipeline after the release is published. :6.3 and :latest are moved to it too, unless a later version has either already. |
| Checking a download | Each file the release pipeline made is signed with Sigstore, its bundle beside it as <file>.cosign.bundle, and hmailserver-6.3.7.intoto.sigstore.json is the release's SLSA build provenance; Verification, at the end of these notes, gives the identity to check them against (SECURITY.md, "Verifying a release"). |
hMailServer 6.3.7
Everything that landed on master after the v6.3.6 tag, from 6 October 2026 (compare v6.3.6 with
v6.3.7). It is the first security release
made while releases pause (SECURITY.md, "While releases pause"), and it fixes vulnerabilities in every
earlier version: upgrade every server running 6.3.6 or earlier. The schema stays at 6108, as 6.3.6 has it:
6.3.7 adds no upgrade step (below). The security notes come first, then what changes on upgrade without a switch.
What it fixes, and who found it. Each is under Security below, with what changed and how it is tested.
- Programs that any user could replace, on a Windows server installed at the root of a drive
(work item 78, CVE-2026-107571). Where the program
folder took its permissions from the root of a drive, any account that could sign in to the computer could replace
the programs the service runs, in every release from 6.0.0 to 6.3.6; the default folder under Program Files was
never affected.hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens
the program folder,BinandControlPanel. Four narrower cases are not fixed in this release (What 6.3.7 does
not fix, below). Found by the project's own review. - Mail sent, and signed, in the name of a domain hosted here. Mail from outside whose
Fromnamed a hosted domain
was signed with that domain's DKIM key, and so was every forward and copy of it, in every version that signs; and an
authenticated submission was signed as whatever hosted domain itsFromnamed, which 6.3.7 does only where the
account may send as that address (work item 84,
CVE-2026-107567). An account's Sievenotifywas sent from whatever its:fromsaid
(work item 82, CVE-2026-107568), and itseditheader
could rewrite who a message was from ahead of aredirect
(work item 83, CVE-2026-107566), both from 6.2.22; a
domain's or an account's rule had the same gap in every version - a Reply from an address its owner may not send as,
a Set header value ofFrom,Sender, theResent-pair orDKIM-Signature- and is held the same way, under the
same two work items. A forward, a redirect, a list's copy or an alias's copy of mail from outside - a forward of a
message downloaded from an external account among them - is given an envelope sender, or for a null one is sent under
a host name, in the organisation of a domain itsFromnames only when the message passed DMARC for it on arrival
(work items 90 and 91; CVE-2026-114478 and CVE-2026-114474). Without a CVE request, as hardening: an ARC seal this server makes attests its
own SPF result, is made by the domain that relayed the message, and names the domains its results are for. All found
by the project's own review. - The rule loop limit (work item 81,
CVE-2026-107569). Any sender could lift the limit off a message, an account's own Sieve script or rule could reset
it, and a Sievenotifydid not consult it, so that a loop of generated mail could run without bound, in every
version. Found by the project's own review. - What a credential confined to named domains may change, over the REST and the COM APIs. On a server with several
tenants, such a credential could make a few changes that reach beyond its domains, and could read the server-wide
metric history; over COM an account given theserveradminrole with a list of domains was the server's
administrator, its list aside, and over both a role could be taken away from an account by a credential that does not
hold what the account's admin level then gives it; a browser that held a REST credential could be made to send a
state-changing request from another site; and the ACME challenge responder served one connection at a time, so that
idle clients could make a certificate's renewal fail (work items 86, 87, 88, 89, 93, 96 and 97; CVE-2026-114489, CVE-2026-114490, CVE-2026-114476, CVE-2026-114475, CVE-2026-114470, CVE-2026-114477 and CVE-2026-114472). Found
by the project's own reviews. - The anti-virus action that removes attachments delivered a message whose one part is an attachment, and an S/MIME
message signed as one part, as they arrived, with what the scanner had found in them, in every version; 6.3.7 replaces
each with the action's note, and deletes a message the server cannot read (work item 92; CVE-2026-114479). The default
action, which deletes the message, was never affected. Found by the project's review of this release. - Keys left in memory.
--import-stored-secrets-key, and the reading of the stored-secrets key from its line,
left copies of the key in memory freed without being written over, from 6.3.4 (found by the project's own review);
and a mailbox key encrypted at rest stayed open pastMailboxKeyIdleMinutesfor as long as the scheduled tasks held
up their minute, from 6.3.4 (found by the batch's regression gate). - Found by upstream hMailServer, in code this project shares with it: with SRS on, mail to an SRS address this
server had handed out was not held to the relay rules, from 6.2.1 (work item 94; CVE-2026-114473), and an SRS address or
a BATV tag almost a whole cycle old was taken for fresh (upstream PR 649); a SASL PLAIN client that asked to act as
another account was signed in as itself and told it had succeeded (upstream issue 652); and the SMTP log wrote some
credentials in the clear (upstream issue 654; work item 95; CVE-2026-114471). - What the Windows installers carry. The .NET Desktop Runtime 10.0.13, Microsoft's security release of 13 October
2026, where the installers of 6.3.4 to 6.3.6 carried 10.0.12 (The .NET 10 Desktop Runtime, below); and, as the
MySQL and MariaDB client, MariaDB Connector/C 3.4.11, where 6.2.3 to 6.3.6 carried 3.4.9, which has CVE-2026-107819.
Reported from outside and fixed besides: the Control Panel's IP ranges page, which could show no list of ranges at
all (work item 79, ThiagoES84); mail to a DNSSEC-signed domain whose MX answer came compressed, live.com among
them, which DANE enforcement deferred (work item 80, ThiagoES84); and classic Outlook's POP3 downloads, which
stopped at the UTF8 it sends after the login (work item 85, ciprianbalus). Thank you.
A security release, and more than fixes. A security release during the pause is cut from master with the
finished work master holds then, as SECURITY.md, "What is supported, and for how long", now says;
until this release it said "6.3.6 with fixes", and the five places that said so are reworded (Documentation). 6.3.7
carries the work that was under way on 13 October 2026, which the owner decided that day to finish and fold into one
batch: the Control Panel's corrections to its pages, texts, translations and keyboard and screen-reader behaviour,
and its regrouped sidebar (Control Panel); IMAP and POP3 taking the client's address from a PROXY header (IMAP,
POP3 and the webmail); a link's host that is not ASCII read by UTS 46 (Anti-spam, anti-virus and mail flow
rules); the first units of the active-active work, which is still not built
(ActiveActive.md); and the Windows suite run in two halves on two benches, with
GitLab's hosted runners available by a switch (Build and tests). By the owner's decision of 15 October 2026 it also
carries the work already done for a later release, the first step of the Control Deck's and the webmail's rebuild
planned for 6.4: the framework decision, the threat model of the web surface and the authorisation matrix with its
check, with no product code (Documentation); and more fixes: the COM Database object once the database has ended a
script's transaction, and the stop of a work queue that gave up on one of its threads (Correctness), and GitLab's
report of the dependency findings and the guard of the hosted runners (Build and tests).
Five of the security fixes below, those of work items 78 and 81 to 84, and their entries here, were public in the
repository before this release - on GitLab from 6 and 7 October 2026, and on master from 7 and 9 October 2026 - by the
owner's decision; the batch this release is made from was pushed and tagged on the same day, by his decision of 16
October 2026; the rule, a day or so between a fix becoming public and its release, holds again after it
(SECURITY.md, "What gets a release").
Signing, and who installs this release by the live update. Every file is signed with Sigstore as this release's
own pipeline at the group's path, https://gitlab.com/hmailserver/hmailserver//.gitlab-ci.yml@refs/tags/v6.3.7,
issuer https://gitlab.com, with the project's id, 86730559, in the certificate, as 6.3.6 was. Every server from
6.3.4 on has that signer compiled in and requires the id for it. The update is applied by the version already
installed, so:
- From 6.3.6 the live update installs 6.3.7, with 6.3.6's own file as its rollback image. A Windows service
running as LocalSystem installs it itself, as it installs any update. One running asNT SERVICE\hMailServer
downloads and verifies it, and an administrator installs it with Install update on the Server status page of the
Control Panel on the server: one Windows prompt, and the installer is checked again, elevated, before it runs. On
Linux 6.3.6's own update helper applies it, for the generic.deband.rpmof either architecture and for both
AppImages, each of which finds its own file in the feed by name; an ARM64 Windows server finds the ARM64 installer
by name. A server installed from one of 6.3.6's per-distribution packages is offered the generic package, which apt
refuses on Debian 12 and dnf on Enterprise Linux 9: upgrade it with its package manager and its own release's package
of 6.3.7. No live update had installed a release signed at the group's path when these notes were written. - From 6.3.4 or 6.3.5 what 6.3.6's notes said of those two versions still holds. A Windows service running as
LocalSystem installs 6.3.7 by its live update, from 6.3.5 with 6.3.5's file as its rollback image and from 6.3.4,
which carries no Sigstore bundle, without one. That image is no way back once the upgrade's schema steps have run:
6.3.5's updater runs it whenever the service is not running again in time, without looking at the database, and
6.3.5 refuses a database the steps have moved to 6108, so the backup taken before the upgrade is the way back
(Back up first, below). One running asNT SERVICE\hMailServerdownloads and verifies it, then leaves the install
to an administrator (HM7463), whose Install update on 6.3.4 and 6.3.5 cannot do it: install 6.3.7 by hand,
once, as below, and every release after it with one click. On Linux install 6.3.7 with apt, dnf or zypper rather
than by the live update, whose apply step on 6.3.4 and 6.3.5 is the one 6.3.6 fixed (CVE-2026-104658), or turn the
apply off until then withsystemctl disable --now hmailserver-update.path. - A server whose
UpdateSigningIdentity,UpdateSigningIssuerandUpdateSourceRepositorystill name the path the
project began at - set so that a server from 6.2.28 to 6.3.3 could install 6.3.5 - trusts that path alone and
refuses every release signed after the project moved, 6.3.7 included. Clear the three first: on 6.3.4 and 6.3.5 in
the Control Panel, withDELETE /api/v1/settings/ini/<name>or on Linux withhmailserver --set-setting <name>=,
not in the file, which those versions put back; on 6.3.6, which reads them fromhMailServer.inialone, by deleting
their lines from[Settings]with an editor run as an administrator, or on Linux withsudo hmailserver --set-setting <name>=, and restarting the service (UpdateFeed.md). - From 6.2.28 to 6.3.3, and from 6.3.3's AArch64 packages or AppImages: by hand, once, after the backup
Upgrading.md describes. Those versions trust only the GitHub workflow that signed
the releases up to 6.3.3, and refuse a release signed on GitLab; one pointed at this project's feed with
UpdateFeedUrldownloads the installer at every check and refuses it, so keepUpdateAutoDownloadoff there until
then (UpdateFeed.md, "A server from 6.2.28 to 6.3.3"). Older than 6.2.28: run
the installer, as always; the upgrade chain runs from every earlier version of this fork, and of the original
hMailServer up to 5.6 - not from the original's 5.7 builds of August and September 2026, whose mail moves over
IMAP instead (Upgrading.md, "Where you can upgrade from"). - The container image,
registry.gitlab.com/hmailserver/hmailserver:6.3.7, also tagged6.3andlatest, is
pushed from this release's own pipeline once the release is published, by a job started by hand, as 6.3.6's was: in
the container the update check names the image to pull and installs nothing
(ContainerImage.md).
By hand on Windows: download hMailServer-6.3.7-x64.exe from this release, check that Get-AuthenticodeSignature
reports it Valid and signed by CN=Progressive Robot Ltd, and run it as an administrator, keeping the service
account on the installer's Service account page. On a machine that has not seen the file before, Windows
SmartScreen may say that it "prevented an unrecognized app from starting": More info names the publisher,
Progressive Robot Ltd, and with the signature checked, Run anyway installs it.
Back up first; no schema step. Take the backup Upgrading.md describes - the
database and the data directory, each with its own tools - before installing. 6.3.7 requires schema 6108, as 6.3.6
does, and adds no upgrade step, so an upgrade from 6.3.6 changes no table, and the live update's rollback to 6.3.6,
should 6.3.7 not start, meets no newer schema. From 6.3.5 or older the upgrade runs 6.3.6's three steps to 6108,
which are one-way (6.3.6's notes; from 6.3.3 or older 6.3.4's steps come first, and 6.3.4's notes apply), and the
backup is the only way back.
The .NET 10 Desktop Runtime. The Control Panel and the setup tools - DBSetup, DBSetupQuick and DBUpdater - run on
the .NET 10 Desktop Runtime; the server itself is native code and does not. Both installers now carry 10.0.13,
Microsoft's security release of 13 October 2026, where those of 6.3.4 to 6.3.6 carried 10.0.12, against which
Microsoft published advisories that day, several of them rated High; whether hMailServer's own tools reach the code
they concern has not been established. Setup installs the runtime it carries only on a machine that has no .NET 10
Desktop Runtime at all, and leaves one that is there as it is, of whatever 10.0 release: an upgraded server keeps
the runtime it had, 10.0.12 where 6.3.4, 6.3.5 or 6.3.6 installed it and an older 10.0 where an earlier installer
did, and so does every other .NET 10 desktop program on it. The runtime is Microsoft's product, serviced through
Microsoft Update rather than the operating system's own updates: on Windows Server through WSUS or the Microsoft Update
Catalog, and by Automatic Updates only where the server has been opted in. Bring it to 10.0.13 or later that way, or
with Microsoft's installer (https://dotnet.microsoft.com/download/dotnet/10.0); dotnet --list-runtimes lists the
releases a machine has (Upgrading.md, "The .NET 10 runtime").
Before you upgrade. What an upgraded server does differently with no setting turned is in What changes on upgrade
without a switch, below. The changes most likely to need something done:
- An account that sends over authenticated SMTP as an address of another domain hosted here, with no alias or grant
that lets it, now has that mail sent unsigned as that domain: give it the alias or the grant first. - A Sieve script stored before the upgrade still runs, without an edit of
From,Sender,Resent-From,
Resent-Sender,DKIM-Signature,Auto-Submittedor the loop count, and itsnotifygoes from the account's own
address where its:fromdoes not reach the account; a domain's or an account's rule stored before it is taken
without a Set header value of one of the first five or of the loop count, and without a Reply from an address
its owner may not send as. The application log names each one. RuleLoopLimit0 now stops out-of-office replies, as it stopped every other generated message.- An account given the
serveradminrole with a list of domains is, over COM as over REST, confined to those
domains and no longer the server's administrator: give the role with no list to an account that is to administer
the whole server, a script's or a Control Panel's account among them. - On Windows a program folder at a drive's root, its
Binand itsControlPanelkeep no entry that lets another
user change them, a file's own entry aside, which aCHECKline names; under Program Files the first run makes the
three folders' inherited entries their own. - The MySQL and MariaDB client ships with seven authentication plugins where 6.3.6's had nine: a database account
that signs in with MySQL Enterprise's Windows authentication can no longer sign in, and the server then cannot open
its database. - SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account; and with SRS on, mail
to an SRS address this server handed out is held to the relay rules unless it is a bounce. - Under Remove all attachments and deliver the text, a message that is one attachment arrives as the note alone, and
one the server cannot read is deleted, its recipients told. - A forward, a redirect or a rule's forward of mail from outside whose
Fromnames the forwarding domain's own
organisation, and that did not pass DMARC for it when it arrived, keeps the envelope sender it came with, with no
SRS address or rewritten sender; a copy of it with an empty envelope sender is not sent to an outside address under
this server's host name where that name is in the same organisation; a copy whose own envelope sender is at this
server's host name, or at a hosted domain or a subdomain of one, in the organisation itsFromnames is not sent to
an outside address at all - the message stays in the mailbox, and the application log names each recipient - nor
is such a message sent where an alias or a distribution list expands to an outside address; and a managed list does
not distribute a posting whoseFromnames the list domain's organisation unless it passed. No pass is recorded for
a message the DMARC check did not judge - withASDMARCEnabledoff, from a sender on the anti-spam white list or an
IP range with spam protection off, larger thanantispammaxsize(1024 KB as shipped), already scored by the tests
before it at the higher of the spam thresholds, imported from a file or downloaded - nor for aFromwhose domain
publishes no DMARC record. - An event script that goes on in a transaction after
Database.ExecuteSQLhas failed is refused every statement
after it once the database has ended the transaction, andDatabase.CommitTransactionthen fails: call
Database.RollbackTransactionand run the whole transaction again, or stop.
What 6.3.7 does not fix. Four narrower cases of work item 78 are not in this release, by the owner's decision, and
stay open:
- setup hardens a program folder before it copies anything into it only when setup makes the folder, and then just
after making it rather than as it makes it; - a folder that is already there is hardened in place, before the copy, only for an installation of the Control
Panel alone; for a server installation, and at every upgrade, the program folder,BinandControlPanelare
hardened by/ProtectFoldersat the end of setup; - a link or a junction in the program folder's path is named in setup's log as not hardened, and not refused;
- the files in
BinandControlPanelare not listed by their owners.
None of them reaches an installation in a folder only the administrators may change - the default under Program Files
is one - unless a link that another account controls is on its path. An installation at a drive's root is hardened
by the upgrade itself; to have it hardened before the upgrade runs anything from it, follow
ServiceAccount.md's steps "Before upgrading an installation at a drive's root"
first. Reinstall the programs from the release into a new folder that only the administrators may change, not over the
installation, where accounts that are not administrators could sign in to the computer while it was installed so, where
anybody but SYSTEM, the Administrators or TrustedInstaller owned one of the three folders, a file directly in the
program folder, or Events, WebAdmin, DBScripts, Addons or Languages or anything in them or in Bin or
ControlPanel, or where one of the three had an entry of its own that let anybody else change it, or one the steps
could not take away: setup leaves in place every file it does not ship, and puts the event script in Events, which the
service runs, and the scripts in Addons only where none is there. The documentation says how: nothing run from the old
folder, its uninstaller included; the mail, the built-in database and the settings file copied into the new folder
before setup runs, since setup run into a folder without a settings file sets up a new, empty server; the settings
file - its [Directories] and administrator password - and the event script read first; and setup given the new folder
with /DIR=, since over an installation it installs into the folder it finds, once a 32-bit installation's uninstall
entry, which overrides /DIR=, is taken away.
Known issues.
- The ARM64 installer has never run on ARM64 hardware. It is cross-compiled on the project's x64 Windows runner
and remains a preview (WindowsOnArm.md); if you install it on an ARM64 machine,
please say how it went, working or not, in an issue. The
AArch64 Linux files are cross-compiled and tested under emulation: no AArch64 machine has run them. - No channel publishes it yet. The apt and dnf repositories, Docker Hub, Quay.io, the AUR, the Ubuntu PPA, Copr,
the openSUSE Build Service, Chocolatey, PyPI and the PowerShell Gallery publish frommasteronce their keys and
accounts exist; none is live as this release is made (Channels.md). The Snap Store's
name is registered, andsnap-upload, a job started by hand once the release is published, puts 6.3.7 on its
edgeandcandidatechannels;stableis promoted by hand (Snap.md). - An AppImage on Fedora, Enterprise Linux or openSUSE runs on the host's glibc and cannot require its extra
character-set converters: installglibc-gconv-extra(Fedora, EL) orglibc-gconv-modules-extra(openSUSE) by
hand; the server says when they are missing.
Security
Every user of a Windows computer on which hMailServer was installed in a folder at the root of a drive could replace the
programs its service runs. The installer left the program folder, Bin and ControlPanel with the permissions the
folder above them hands down. Under C:\Program Files, the default, that lets nobody but SYSTEM, the Administrators and
TrustedInstaller change them; but the root of a drive on a Windows client hands every folder made below it
Authenticated Users Modify, so any account that can sign in to the computer could replace hMailServer.exe, or put a
library in Bin that Windows loads into it before its own, and the service - LocalSystem by default and after every
upgrade that keeps it - ran it at its next start, as did the next administrator to run DBSetup, the Control Panel's
elevated steps or the uninstaller. On Windows Server the root lets Users make files in it, which is enough for the
library. Every release from 6.0.0 to 6.3.6 installs so in such a folder; the default folder was never affected. 6.3.6's
/ProtectFolders made the data folders private and left these three as they were, and /GrantServiceAccess only said
CHECK, and only under the virtual account. hMailServer.exe /ProtectFolders, which setup runs at every installation
and upgrade, now hardens the three: SYSTEM and the Administrators with full control, BUILTIN\Users with Read & execute
where every user could read them before (none in a folder only the administrators may open), nothing inherited, and the
Administrators as the owner of one somebody else owned, without the entries of its own that let others change it, which
that owner could have written. It then holds the installation to the rule an administrator's install of an update
checks, which such an installation now passes, and says what it does not change - an entry of a file's own, a folder
above the program folder that others may rename - on a CHECK line with the icacls that removes it. Setup also gives
a program folder it makes those permissions before it copies anything into it, hardens in place the folder an
installation of the Control Panel alone finds there - no /ProtectFolders follows one, as it has no server - unless it
is a drive's root, holds a server installation or is a link, and says, without refusing it, when the folder chosen lets
every user change what is in it. Under Program Files the first run makes the three's entries their own, and nobody may
do there anything they could not before. An administrator who cannot upgrade yet follows the documentation's steps from
an elevated prompt on each of the three: the Administrators made its owner, every entry of its own but SYSTEM's, the
Administrators' and any deny entry taken away, what it inherits replaced, and the Users' Read & execute and, on Bin
and ControlPanel where the server is installed, the service's given back; and reinstalls the programs into a new
folder only the administrators may change, not over the installation, where accounts that are not administrators could
sign in while it was installed so, where anybody but SYSTEM, the Administrators or TrustedInstaller owned one of the
three, a file directly in the program folder, or Events, WebAdmin, DBScripts, Addons or Languages or anything
in them or in Bin or ControlPanel, or where one of the three had an entry of its own that let anybody else change
it, or one the steps could not take away: setup leaves in place every file it does not ship, and puts the event script
in Events, which the service runs, and the scripts in Addons only where none is there. The documentation says how:
nothing is run from the old folder, its uninstaller included; with the service stopped, the settings file, the mail and
the built-in database are copied into the new folder before setup runs, since setup run into a folder without a settings
file sets up a new, empty server there; the settings file is read first - the lines of its [Directories] that name the
old folder and its administrator password deleted, which setup writes anew - and the event script before it is copied;
setup is given the new folder with /DIR=, since over an installation it installs into the folder it finds, once a
32-bit installation's uninstall entry, which overrides /DIR=, is taken away; and the programs the database names are
read in the new Control Panel, the paths the database holds into the old folder are changed, and, once nothing names it,
the old folder is deleted by hand. Found by the project's own review
(work item 78, CVE-2026-107571).
ServiceAccount.md, "What the other users of the computer may read".
-
Fixed: a credential confined to named domains could make a few changes that reach other tenants, on a
multi-tenant server. A domain administrator, an account or API key given another role that manages accounts in
named domains, or a full-scope API key issued for named domains administers what is inside its domains; a handful
of changes named, or reached, something beyond them, and are
now held to the server administrator alone — over the REST API and over the COM API both — with the confined
credential keeping every change that stays inside its domains:- renaming a domain, and creating a domain alias, which decide what names the server accepts mail for;
- creating, rewriting or deleting a custom role, which is assigned across domains;
- setting a rule's
bind_to_address,send_using_routeorscript_functionaction on a domain's or an
account's rule, each of which reaches one of the server's own addresses, a configured route's stored
credentials, or a function of the server's event script (a global rule, which only the server administrator
saves, is unchanged).
A confined credential's domain relay host is now held to a public address, as a reader's collected-from host
already was, so it cannot point the server's delivery at the server's own network — and only a change of the host
is judged, so a domain whose server administrator set an internal smart host stays its domain administrator's to
save, from the Control Panel as from a script. The server-wide metric history (GET /api/v1/metrics/history),
counted from counters that carry no domain, is refused a confined credential as the report's server-wide sections
already were. Found by the project's own web-surface review and handled under SECURITY.md
(work items 86, 89 and 96; CVE-2026-114489, CVE-2026-114475 and CVE-2026-114477).
Roles.md, "What names a domain beyond the credential's own". -
Fixed: the COM API holds a server-administrator role assigned with a domain list to those domains, as the
REST API does. An account given theserveradminrole with a list of domains is confined to the domains the
list names: it holdsserveradmin's permissions in them, short of what reaches beyond a domain, and none in any
other, so it is not the server administrator. Over COM it is refused the server settings, a domain alias, a
domain's name and the other changes that reach beyond a domain, exactly as over REST. Over COM it is a domain
administrator of its own account's domain where its list names that domain, and reaches the other domains its
list names through REST, and over COM through the calls that name a domain of their own, among them the password
reset, the unlock, the mailbox move and its history, the message trace, role assignment and a message's sender
(send-as). Over COM a role holdingadminister_domainis a domain administrator of its own account's
domain only where its list of domains names that domain, as over REST. A role is given or taken away, over REST as
over COM, only by a credential that holdsmanage_rolesin the account's domain and takes in what the account
holds now and what it will hold afterwards — with no role left, the authority of its admin level
(RoleManager::MayChangeAssignment) — so a full key issued for named domains, or an account assigned
serveradminwith a list, is refused taking a role away from an account whose admin level says server
administrator.RoleManager::Allowsanswersadminister_serveronly for a role that reaches every domain — the
one ruleCOMAuthentication::GetIsServerAdminandRestApiServer::HoldsServerAuthority_both read — and a role
given no list of domains is unchanged. Found by the project's own review and handled under
SECURITY.md (work items 87 and 88; CVE-2026-114490 and CVE-2026-114476).
Roles.md, "What names a domain beyond the credential's own" and "Giving somebody a
role". -
Fixed: a credentialed REST request a browser made from another site was accepted (cross-site request
forgery). A session's write already neededX-Requested-With: hMailServerand the session cookie is
SameSite=Strict, but HTTP Basic - which a browser keeps once it has answered the sign-in box - was not held to
anything, so a form on another site could act with it. A state-changing request that carries a credential is now
refused when the browser reports it as cross-site (Sec-Fetch-Site, or anOriginthat is not this server's,
Origin: nullamong them), and the two anonymous posts that carry no token - the mailing-list subscribe form and
the password-reset request - are held to the same rule, so another site cannot have a current browser drive them.
The subscribe form alone still takesOrigin: null, as this server's own form can be posted with it by a browser
too old to sendSec-Fetch-Site. Scripts,hmctland the PowerShell module send no such header and are
unaffected, as are the Deck and the webmail, which act on their own origin. Found by the project's own web-surface
review and handled under SECURITY.md (work item 93; CVE-2026-114470).
DevicesAndSessions.md. -
Fixed: the ACME HTTP-01 challenge responder served one connection at a time. During a certificate
validation a client that connected and sent nothing held the responder for several seconds, so enough of them in
turn could keep the certificate authority's own request from being answered and make the renewal fail. The
responder now serves connections concurrently, up to 64 at once, and closes a connection past that, so that an idle
client holds only its own short-lived handler. Found by the project's own web-surface review
(work item 97; CVE-2026-114472). -
Fixed: any sender could lift the rule loop limit off a message, an account's own Sieve script or rule could
reset it, and a Sievenotifydid not consult it. The server counts the generated messages a message has been
through - a rule's forward, reply or copy, an account's forward, a Sieveredirect,vacationornotify, a
bounce - in itsX-hMailServer-LoopCountheader, and makes no more once the count reaches the rule loop limit
(RuleLoopLimit, 5 by default): that is what ends a loop between two accounts that forward to each other.- Nothing takes the header off mail that arrives from outside, and the server read it with
_ttoi, so a negative
count passed every test against the limit: a sender who wrote-2147483648set such a loop going round some two
thousand million times, and on Linux, where_ttoikeeps the low 32 bits of a 64-bitlong, so did one who
wrote2147483648, which read as that same count, while4294967295read as -1 (in every version, and on Linux
from 6.3.0). A Sievenotifywent out whatever the count, carrying it + 1, so a count of2147483647made that
a signed overflow, undefined behaviour that in practice gave the notification the most negative count there is
(from 6.2.22). The header is now read in one place,RuleGuard::ReadLoopCount: as a count only when it is
digits and nothing else, and never above the limit; anything else - a sign, a letter, an empty value - reads as
the limit, so the message is delivered and none of the generated messages above is made from it; and a
notification is no longer sent once the count has reached the limit, as a vacation reply is not. A message
without the header, or with a count below the limit, is treated as before. - The count a forward or a redirect reads is the one the message carries when it runs, after the edits before it. An
account's Sieve script coulddeleteheaderthe header, oraddheadera 0 above it, ahead of aredirect(from
6.2.22, which broughteditheader), and a rule's Set header value action could set it ahead of a forward (in
every version) - an account may change its own rules over COM - so that one such account and an ordinary forwarder
looped between local accounts without bound. And a line break in a value either of them wrote went into the
message as it stood, writing a second field: the count, reset, under a name nothing had checked - a sender's to
write where a script copied a body test's:matchescapture into a header. A script that edits the header, the
name in any case, is now refused when it is stored or checked (ManageSievePUTSCRIPTandCHECKSCRIPT, the
self-service filter, COM'sCheckSieveSyntax), asReceivedandReturn-Pathare, with the reason; the edit is
not made at delivery either, of the name as the script's variables made it, and a script stored before this
release still runs, without that edit, which the application log names. A Set header value action on the header,
or on a name with a line break, a colon or a character outside ASCII in it, is refused byRuleAction.Save,
Rule.Saveand the REST API's rule routes, and one stored before is skipped, with a line in the application log.
Event scripts, which are the administrator's own code, may still set the header. A script's edit of
Auto-Submittedis held the same way, refused when stored and not made at delivery: the field says a program
sent the message, and every automatic responder, this server's vacation, notification and out-of-office among
them, reads the first one and does not answer (RFC 3834 section 2). RFC 5293 section 6 says a script MUST NOT be
permitted to delete it, and RFC 3834 section 5.1 allows a message one, so anaddheaderofnoabove it is a
deletion by another name; either, ahead of aredirect, handed an automatic reply to the next mailbox's
responder as a person's message to answer (from 6.2.22). - A line break in a header value is now written as spaces unless it folds the line, a space for each CR and each LF,
by the MIME writer itself, so that no value writes a field of its own. That was not the rules' and the scripts'
edits alone: every message the server generates is built from values that are not its own, and the field a line
break wrote sat above the ones the generator set, so it was what was read. A Sievenotifywhose:messageor
:fromheld line breaks wroteAuto-Submitted: noand a count of 0 above the notification's own (from 6.2.22),
so that a script notifying its own account notified about its own notifications for ever; a copy rule's name,
which the copy carries inX-CopyRule, reset the count at every copy (in every version), so that an account
copying its own mail made copies without end; and a rule's reply subject, an out-of-office subject, and the
sender's own subject decoded into a vacation reply or a bounce reset it on the message they made. COM's
Message.Saverefuses a credential's message whose values hold a line break that does not fold them, and says
why, rather than send it changed (6.3.6 sent a line break that began a field of its own as that field, and checked
it as it checks the others). Nor does the writer fold a value before white space that ends it, which left a line
of nothing but white space - the end of the header to some programs - and whichMessage.Saverefused, from
6.3.6, as a line break the program never gave: a subject of 78 letters and a space. - An out-of-office reply, an account's or a domain's, carried a count of 1 whatever the message it answered
carried, where every other reply carries that count + 1: it now does too, and none is sent once the count has
reached the limit, as a Sievevacationreply is held. So aRuleLoopLimitof 0, which already stopped every
forward, redirect, rule reply and copy and every Sievevacationreply, now stops out-of-office replies too: a
server set to 0 to make no generated mail sends none, where until now it still answered every message to an
account that was out of the office. The setting's description in the REST API says what 0 does.
LoopCountTesterandLoopCountEditTester(business-object-testson Linux) hold the reading, the edits and the
writer to examples,MimeTester(report-parser-testson Linux) the writer's folds, andRules.cs,
API/RestApiRules.cs,API/ComCallingProcess.cs,Sieve/SieveEditheaderDelivery.cs,Sieve/ManageSieve.cs,
Sieve/SieveNotifyDelivery.cs,Sieve/SieveVacationDelivery.csandSMTP/OutOfOfficeScope.cssend them through
storing and delivery, four loops among them. Found by the project's own review
(work item 81, CVE-2026-107569). - Nothing takes the header off mail that arrives from outside, and the server read it with
-
Fixed: a Sieve
notifywas sent from whatever its:fromsaid, so that any account with a Sieve script could
send mail through this server as any address. Avacationreply's:fromis used only when it is an address
that reaches the account - the account's own, or an active alias whose value is it - and otherwise the reply goes
from the account's own address, with a line in the application log. A notification's:fromwas not checked at
all (from 6.2.22, which broughtenotify): a script of three lines sent a notification to any address, on this
server or anywhere, from another account's address or anyone else's, and the server signed it with its DKIM key
when it hosts the domain of the address it claimed.notify's:fromis now held to the rulevacation's is, by
the one function both use (SieveFromAddress), whatever the notification method. One that is not an address at
all - none, a display name, an address followed by a line break - is refused when the script is stored or checked
(ManageSievePUTSCRIPTandCHECKSCRIPT, the self-service filter, COM'sCheckSieveSyntax), asvacation's is,
with the reason; and at delivery one that does not reach the account is replaced by the account's own address,
with a line in the application log naming the account and what its script asked for. A script stored before this
release whosenotifyhas a:fromthat is not an address still runs, and its notifications go from the
account's own address. And the address the server finds for a:from, avacationreply's too, must now be the
one the script wrote, letters' case aside: on MySQL, whose collation ignores accents, a:fromwith an accent on
a letter of the account's address or of an alias's was found as that address and used as the script wrote it.SieveFromAddressTester(business-object-testson Linux) holds the rule, the check at storing and a stored
script's run to examples, andSieve/SieveNotifyDelivery.csandSieve/ManageSieve.cssend an alias, another
account's address and a:fromthat is not an address through storing and delivery. Found by the project's own
review (work item 82, CVE-2026-107568). -
Fixed: an account's Sieve script could redirect a message as from any address, and have this server sign it
with that address's domain's DKIM key.editheaderlet a script add or delete any field butReceived,
Return-Pathand the loop count, and aredirectsends the message on as the script left it (RFC 5293 7). The
server signs a message as the domain itsFromnames wherever it signs for that domain and the message carries no
signature of it, so a script that wrote aFrom- another account's address, or anyone's at a domain hosted here -
had the redirected copy sent from this server in that name and signed as that domain; and one that deleted the
DKIM-Signaturea colleague's message came with, changed itsSubjectand redirected it had the server sign the
changed message as the colleague's domain (from 6.2.22, which broughteditheader). A script may no longer add or
deleteFrom,Sender,Resent-From,Resent-SenderorDKIM-Signature, the name in any case, which RFC 5293 6
leaves to the server: one that does is refused when it is stored or checked (ManageSievePUTSCRIPTand
CHECKSCRIPT, the self-service filter, COM'sCheckSieveSyntax), as one that editsReceivedis, with the
reason; the edit is not made at delivery either, of the name as the script's variables made it, and the
application log names it; and a script stored before this release still runs, without that edit.Reply-To,
which names no sender, and every other field may still be edited.SieveFromAddressTesterholds the refusal, a stored script's run and the edits at delivery to examples, and
Sieve/SieveEditheaderDelivery.cssends them through storing, delivery and a redirect between two accounts. Found
by the project's own review (work item 83,
CVE-2026-107566). -
Fixed: an account's own rule could reply as any address, or set the
Fromof a message it forwarded, and have
this server sign it with that address's domain's DKIM key. An account may change its own rules over COM, signed
in as itself, and a domain's administrator, or a REST key confined to the domain, the domain's. A rule's Reply
went out from whatever address its action named, and its Set header value could set any header but the loop
count,From,Sender, theResent-pair andDKIM-Signatureamong them, ahead of a forward or a copy (in every
version); the server signs a message as the domain itsFromnames wherever it signs for that domain. Which
collection a rule is in is recorded, and who saved it is not, so the two actions are now held to the rule's owner:- a Set header value of
From,Sender,Resent-From,Resent-SenderorDKIM-Signature, the name in any case
and with blanks round it, is for the server's rules alone, which only the server administrator saves - a domain's
or an account's rule that sets one is refused, whoever saves it; - a Reply goes from an address the owner may send as: for an account's rule, one the account may send as - its
own address, an alias of it, a mailbox that granted it the post right, an address it proved, a masked address of
its own, the rule authenticated SMTP and the webmail's send apply - and for a domain's rule an address of the
domain or of one of its domain aliases; one email address in either case. An account's rule the server
administrator saves is held to the account's addresses too, so that what is saved is what is sent; - and the reply's name is written as one quoted name before its address: a name holding a comma and an address was
written as it stood, and added that address to the reply'sFrom. A reply from no address, the null sender, has
its name before an empty<>, and a name with an@in it is left out: the reader the server picks aFrom's
address with takes a name that is one email address for the address, so a reply from no address under the name
ceo@a domain hosted here was signed as that domain.
COM's
Rule.SaveandRuleAction.Saveand the REST API's domain and account rule routes refuse such an action,
with the reason. One stored before this release, or restored from a backup since, is not taken at delivery - the
header is not set, the reply not sent - and the application log names the rule and why; the rule's other actions
are taken. To setFromon a domain's mail, make the rule a server rule with a condition on the domain.RuleOwnerTester(business-object-testson Linux) holds the two checks and the reply'sFromto examples, and
Rules/Rules.csandAPI/RestApiRules.cssave the rules over COM, signed in as the account and as the
administrator, and over REST, and deliver a reply and aFromstored before the check;AntiSpam/DKIM/ArcSealing.cs
delivers a reply from no address under the name of a hosted domain's address, unsigned and naming no address.
Found by the project's own review; the rule engine's case is part of
work item 82 and
work item 83 (CVE-2026-107568 and CVE-2026-107566).A rule's Warn sender notice, and the notice a Reject met after acceptance sends, carry the rule's name and its
owner's text, and came frompostmaster@the sender's own domain (from 6.3.4, which brought them): an account's or
a domain's rule wrote to a sender of another domain hosted here in that domain's name, and the server signed it with
that domain's key. A notice now comes from the postmaster of the rule owner's domain - the account's domain for an
account's rule, the domain for a domain's rule - and is signed as that domain, as mail the server makes is. A server
rule's notice, the administrator's, comes from the postmaster of the server's default domain when one is set and
hosted here, else of the hosted domain the message was addressed to, else of the sender's own.
Rules/OutboundContentPolicy.cshas an account of one hosted domain warn a sender of another, and finds the notice
from its own domain's postmaster and signed as it. - a Set header value of
-
Fixed: mail from outside whose
Fromnamed a domain hosted here was signed with that domain's DKIM key, and so
was every forward of it. The server signed a message as the domain itsFromnames on its first delivery pass,
whatever the message was (in every version that signs): a sender anywhere who wroteFrom: ceo@a domain hosted
here had the message signed as that domain before local delivery, and an account's forward of it, a rule's
forward or copy, a Sieveredirect, an alias or a distribution list to an address outside, the mirror copy or a
journal rule's copy left the server as a forgery that passes DMARC. A message is now signed as itsFrom's domain only when the server is
entitled to: a submission by a signed-in user, and every copy of it, unchanged; a message the server makes itself;
and one an IP range lets in as one of this server's domains without signing in - the session the server is the
submission server for, asMy computer's web sites and scripts are. Anything else came in from outside - an SMTP
session that did not sign in and was not such a submission, a download from an external account, a message
imported from a file - and is recorded so on the queued message, never in a header, and on every copy made of it,
through a restart (hm_messages.messageauthdomainscarries the mark ahead of the DMARC domain, with no schema
change). It leaves with the signature it came with, and withArcSealingEnabledon, the ARC set the relaying
domain seals. A managed list does not sign its copies of such a posting as the list's domain when an address of
the list domain's organisation is anywhere in itsFromfields, which DMARC would align. The quarantine keeps
whether a message it holds came from outside in the name of the copy it stores, and a release reads it from there
rather than from the message, whoseReceivedfield a domain's rule may have rewritten before holding it; a
message held before the upgrade is from outside unless it has noReceivedfield, or the one on top is this
server's own for a session that signed in, or the send API's - so that a download from an external account, whose
top field is the other server's, is released unsigned. Messages in the queue at the upgrade were recorded without
the mark and are signed as before.SigningOriginTester(business-object-testson Linux) holds which messages are signed, the mark on every copy,
on the queue row and in a quarantined copy's name, a release's reading of a header, and the list's decision to
examples;AntiSpam/DKIM/ArcSealing.cssends a signed-in submission (signed) and mail from outside with a hosted
Fromthrough an account's forward, an account rule's forward, a Sieveredirectand a distribution list (none
signed, the forwards sealed) and a signed-in member's posting to the list (signed);AntiSpam/Quarantine.cs
releases mail from outside that a domain's rule claimed a signed-in client for and then held, and finds its forward
not signed. Found by the project's own review
(work item 84, CVE-2026-107567). -
Fixed: a forward, a redirect, a list's copy or an alias's copy of mail from outside is given an envelope sender -
or, for a null one, sent under a host name - in the organisation of a domain itsFromnames only when the message
passed DMARC for that organisation when it arrived. The rule holds for an account's forward, a rule's forward, a
Sieveredirect, a posting a managed list distributes, and an alias or a distribution list expanding to an address
outside, and reads the organisation as the list's signing decision does: the public suffix list's organisation of
everyFromfield, every address in one and every domain after an@. The pass is the one this server's DMARC
check recorded on the message when it arrived (hm_messages.messageauthdomains, beside the mark of mail from
outside, through a restart), and a copy the server makes of the message carries it - a forward's, a list's, a
journal rule's, one held in the quarantine since the upgrade and released - so that what such a copy meets further
on, another account's forward of it, an alias or a list, is held to the same pass. Otherwise:- the
SRSor envelope-from rewrite a forward or redirect would make into that organisation is withheld, and the
copy keeps the envelope sender the message came with; - a copy that would still carry such an identity - a null envelope sender under a host name of that organisation,
or the envelope sender the message came with when that is at this server's host name or at a domain whose
organisation is hosted here (a hosted domain or a subdomain of one) - is not sent on to an address outside, and
the application log names each recipient it was withheld from; - a managed list does not distribute the posting, as a spam-marked one is not, and a distribution list's bounce
address is not given to it.
No pass is recorded for a message the check did not judge - with
ASDMARCEnabledoff, from a sender on the
anti-spam white list or an IP range with spam protection off, larger thanantispammaxsize(1024 KB as shipped),
already scored by the tests before it at the higher of the spam thresholds, imported from a file or downloaded -
nor for aFromwhose domain publishes no DMARC record, and such a message is held to the rule as one that did not
pass.A download from an external account whose
Fromis an address hosted here keeps a null envelope sender, as one
whoseFromnames no address does, since a download is not a message this server is the source of; the sender
the spam checks read is theFrom's address, as before. A forward of mail that passed DMARC for itsFrom's
organisation, and an ordinary forward of third-party mail, areSRS-rewritten and sent on as before, and a
forward of such a forward is sent on as before.CopyEnvelopeTester(business-object-testson Linux) holds the organisations aFromnames, the DMARC passes a
message's record carries, which envelope senders are this server's own, the choice, and the pass a copy carries -
through a copy of a copy, a queue row and a quarantined copy's name - to examples, andSMTP/CopyEnvelopes.cs
sends mail from outside whoseFromnames the hosted test domain, without a DMARC pass for it, through an
account's forward, a rewritten forward, a null-sender forward, a Sieveredirect, a forward that keeps an envelope
sender at a subdomain of that domain, a forwarded external download and a second forward, and finds none sent on
under an identity of that domain, beside a forward that passed DMARC, a second forward of it, a forward of it to an
alias that expands to an address outside and an ordinary third-party forward, all still sent;SMTP.MailingLists,
AntiSpam.QuarantineandSMTP.Journalingfind a list member's forward of a posting that passed, the forward of a
message that passed released from the quarantine, and a journal mailbox's forward of its copy of one, sent on.
Found by the project's own review (work items 90 and 91; CVE-2026-114478 and CVE-2026-114474). - the
-
Changed: an authenticated SMTP submission is signed as another domain hosted here only when its account may send
as the address itsFromnames. Authenticated SMTP does not hold theFromheader to the account
(SmtpAuthenticatedSenderCheck, off by default, holdsMAIL FROMalone), and the server signed a signed-in user's
message as whatever domain hosted here itsFromnamed, so an account of one domain that wroteFrom: ceo@another
had the message signed with that domain's key, which DMARC then passed (in every version that signs). The webmail's
send and COM'sMessage.Savealready hold aFromto the account. Such a submission is now signed as theFrom's
domain when that is the account's own domain, or one of its domain aliases, whatever the address in it - an account
writing asinfo@its own domain is signed as before - and as another hosted domain only when the account may send
as that address: its own, an alias pointing at it, a mailbox that granted it the post right, an address it proved, a
masked address of its own (SendAs.md). Otherwise the message is still accepted and sent, unsigned as that domain,
and so is every copy of it, through a restart - it carries the mark mail from outside does - and the debug log says
why. A session a trusted upstream signed in throughXCLIENT LOGINunder a name that is no account here is held to
the same rule with the name in the account's place: signed as the domain the name is at, and as another hosted
domain only for an alias that points at the name. Upgrading: an installation where accounts send as another
hosted domain's addresses without an alias or a grant should give them one, or the mail goes unsigned.
SigningOriginTesterholds the mark and the address the signer reads to examples, andAntiSpam/DKIM/ArcSealing.cs
sends a signed-in account's message as another hosted domain's address (not signed, nor its forward), as an alias of
its own and as its own domain's other address (both signed). Found by the project's own review; it is part of
work item 84 (CVE-2026-107567). -
Fixed: an ARC seal attested whatever SPF result the first
Received-SPFfield of the message claimed. With
ArcSealingEnabledon, theARC-Authentication-Resultsthis server seals over mail it relays readspf=from
that field (from 6.0.0, which brought ARC sealing), which is anybody's to write: the sender's own, on mail this
server wrote noReceived-SPFfield for - it writes none unlessReceivedSpfHeaderEnabledis on - and on mail it
did, a field a Sieveaddheaderor a rule's Set header value placed above it ahead of aredirector a forward.
The seal then told every receiver that trusts this server's ARC sets that SPF had passed for a message whose check
never ran or failed.spf=is now the result this server's own check reached when the message arrived, recorded
on the queued message beside where it came from and carried by every copy of it, through a restart -nonewhen
the check did not run - and no field of the message is read for it. The quarantine keeps it in the name of the
copy it stores, beside whether the message came from outside (<GUID>.spf-fail.outside.eml), and a release puts
it back, so that a released message and its forward are sealed with the result the message arrived with.
Messages queued before the upgrade, and messages held before it, carry no result and are sealed withspf=none.ArcAuthenticationResultsTester(business-object-testson Linux) holds the value, and the result on every copy,
the queue row and a quarantined copy's name, to examples;AntiSpam/DKIM/ArcSealing.csrelays mail
from outside with aReceived-SPF: passof the sender's through an alias, and with one a Sieve script added through
aredirect, and finds neither attested, andAntiSpam/Quarantine.csreleases a message whose SPF check failed and
finds the failure attested over its forward. Found by the project's own review. -
Fixed: which hosted domain's key sealed an ARC set over relayed mail could be chosen by whoever wrote a header or
the envelope. WithArcSealingEnabledon, a message this server relays and does not sign as its author's domain
is sealed by a domain hosted here, and the sealing domain was the first with a DKIM key among the envelope sender,
the recipients' original addresses and theX-Original-Rcpt-Tofield (from 6.0.0, which brought ARC sealing): the
field a sender writes when this server does not (AddXOriginalRcptTo, off by default), and a Sieveaddheaderor a
rule's Set header value can write above this server's own; and an outside sender's envelope may name a hosted
domain. Whoever wrote it chose which hosted domain's key vouched for the seal, though the results it attested stayed
this server's. The sealing domain is now the one the server records as sending the message on, where it makes the
message, on the message itself: the forwarding account's domain for an account's forward, its rule's forward or
copy and its Sieveredirect; the domain of a domain's rule, of a journal rule and of a managed list; for a server
rule, the domain the message was received for; the submitting account's domain for its own message; and the hosted
domain an alias or a distribution list that expanded to an address outside belongs to. It is carried by every copy,
kept on the queue row (relay=beside the outside mark) and in a quarantined copy's name (.relay-<id>), and a
message with none recorded goes unsealed: no header and no envelope is read for it. Messages queued or held before
the upgrade are sealed by an alias's domain, or a submitting account's, and otherwise not at all.SigningOriginTesterholds the record on every copy, on the queue row and in a quarantined copy's name to examples,
andAntiSpam/DKIM/ArcSealing.cssends an account's forward of a message carrying a forgedX-Original-Rcpt-To, and
an alias's expansion of one whose envelope names another hosted domain, and finds each sealed by the domain that
relayed it. Found by the project's own review. -
Fixed: an ARC seal's results did not say whose they were. The
ARC-Authentication-Resultsthis server seals
saiddkim=passandspf=passwith no property (from 6.0.0), so a receiver could not tell a pass for the sender's
own domain from one for the author's. Each now says, as RFC 8601 section 2.7 and RFC 8617 section 4.1.1 have it:
adkim=pass header.d=for each domain whose signature verified when the server sealed the message, each named once,
and the domain the SPF check evaluated - the envelope sender's, the local part left out - assmtp.mailfrom=. That
domain is recorded with the result when the message arrives, since a forward's envelope may be rewritten (SRS,
RewriteEnvelopeFromWhenForwarding, a list's bounce address), and travels as the result does: on every copy, on the
queue row (mailfrom=, left out when it does not fit beside the DMARC domain, which keeps its place) and, for a
message held as it arrived, as a mark in the quarantined copy's name that it was the sender's domain. A check that
did not run, or ran for a null envelope sender, names none, nor is a value that is not a domain's characters ever
written. A value too long for one line is folded between its results. A message's record of where it came from
costs it less than before: the two strings are kept apart from it, made only when set, so that every message an
IMAP folder's cached listing holds is 24 bytes smaller.ArcAuthenticationResultsTesterholds the value, its folds
and the domain's travel to examples, andAntiSpam/DKIM/ArcSealing.csandAntiSpam/Quarantine.csfind both
named in a seal. Found by the project's own review. -
Fixed: the anti-virus action Remove all attachments and deliver the text delivered a message whose one part is an
attachment, and an S/MIME message signed as one part, as they arrived, with what the scanner had found in them, in
every version; it now replaces each with its note, and deletes a message it cannot read. A message whose one part is
not its text - a file, a message carried whole, or a text file declared an attachment, which are attachments as a part
of a multipart message is one (MimeBody::IsAttachment) - is rewritten as a message with no text part is: its header,
the subject marked with the server's VIRUS_FOUND text, and the VIRUS_ATTACHMENT_REMOVED note with nothing after
it, the part's ownContent-fields left out with the part, so that the note reads as text. A message the stripper
cannot read - its file held by another program or gone, or its parse failed - has nothing in it that can be kept, and
is deleted, as Delete the whole message deletes it: each of its recipients is sent the "message deleted" notice
(VIRUS_NOTIFICATION) in place of the note, whatever the two notice settings say, and its sender is not told, as this
action never tells the sender. The notice is made from the message's header, read once for all of its recipients; when
that cannot be read either, nothing is written to the error log, and the notice gives the envelope's sender and the
recipient as the message's From and To. The notices of Delete the whole message, which read the file for
themselves and say in the error log when they cannot, now fill the two fields from the envelope too when it cannot be
read, where they left them empty: a recipient's notice names that recipient alone, and the sender's notice each
address the message was taken for, once and before the server resolved it - for mail received by SMTP theRCPT TO
addresses, a distribution list by its own address and not its members; for mail an external account downloaded, the
account it was downloaded into, or, with Deliver each message to the accounts its headers name on, the addresses its
header and Received fields named that it took the message for - and not an address that reached only recipients an
earlier address had already reached. An S/MIME message signed as one part -application/pkcs7-mimeholding
signed-data, which is opaque to the server's other readers and is not encrypted - is one such part, and is replaced by
the note too: the stripper asksSealedMessage::IsEncrypted, which isIsOpaqueless signed-data. A message that is
one text part is delivered as it is, having no attachment to remove, and so is an encrypted one; the application log
says which of these each message was.AntiVirus/StripAction.csdelivers a message that is one attachment in four
shapes, an S/MIME message signed as one part and one encrypted as one part, and one that is one text part, and holds a
message's file while the stripper reads it and finds the notice naming the message's sender and recipient and nothing
in the error log, and while the delete action's notices are made and finds each recipient's naming that recipient
alone and the sender's naming theRCPT TOaddresses of mail received by SMTP, the account a download went into, or
the address a download's header named for it, and not an address that reached no recipient of its own;
SealedMessageTesterholdsIsEncryptedto the S/MIME and PGP/MIME shapes
(SealedMail.md, "The "delete attachments" anti-virus action"). Found by the
project's review of this release (work item 92; CVE-2026-114479). -
Fixed: a mailbox key encrypted at rest stayed open, its private half in memory, past
MailboxKeyIdleMinutesfor
as long as the server's other scheduled tasks held up their minute. The sweep that closes an idle key was the first
step ofRemoveExpiredRecords, one of the Scheduler's tasks, and the Scheduler runs every task that is due one after
another on one thread and sleeps its minute after the last of them (from 6.3.4, which brought encryption at rest).
So the sweeps came a minute plus whatever every task of the minute took apart: a webhook pass may take a minute by
design, ten seconds for each subscription whose endpoint does not answer, and the hourly message-store consistency
check runs on the same thread as long as the store takes. The sweep now has a thread of its own and runs every ten
seconds from the start of the servers to their stop, touching no database and waiting on nothing, so that a key
closes within ten seconds of its idle minutes whatever the scheduled tasks are doing.EncryptionAtRest.mdsays
so.Infrastructure/EncryptionAtRest.cssigns an account in at the start of a webhook pass that six subscriptions
to an endpoint that never answers hold for a minute, and finds its key closed within ninety-five seconds of the
sign-out, beside the endpoint reached six times ten seconds apart; with the sweep among the scheduled tasks the key
closed at the turn after the pass, some two minutes on. Found by the batch's regression gate. -
The Windows installer's MySQL and MariaDB client is MariaDB Connector/C 3.4.11, from 3.4.9, which has
CVE-2026-107819. Connector/C 3.4.1 to 3.4.9 had a flaw in the authentication switch a server asks for after TLS
(6.2.3 to 6.3.6 shipped 3.4.9): when the client checked the server's certificate and the check failed only on the
host name - a certificate a trusted authority issued, for another name - the switch did not refuse a plugin that
sends the password unhashed, so a man in the middle with such a certificate could ask formysql_clear_password
and read the database password inside the TLS connection it stood up (Medium; fixed in Connector/C 3.4.10 by
MariaDB's CONC-846, commit e8c0a16d94dd). hMailServer reaches the flawed path only withTlsMode=verify-full, no
TlsCaFile(so the system's store decides trust) and the database on another host whose name is notlocalhost;
the default,prefer, does not check the certificate and so never enters it, and a namedTlsCaFilerefuses any
mismatch before the login. 3.4.11 and not 3.4.10: 3.4.10 is a tag in MariaDB's repository that MariaDB never
released, so 3.4.11 is the first release with the fix; it also undoes a regression the same change brought into
3.4.10, a CA file making the client check the certificate with checking turned off (CONC-854), which hMailServer,
passingTlsCaFiletoverify-fullalone, would not have met, and it bounds two reads of what a server sends
(CONC-842, the row reader; CONC-850, the GSSAPI plugin). MariaDB publishes 3.4.11 as source only, and no MariaDB
Server release bundles it yet, so the files are now built from MariaDB's signed source by
libraries/build-mariadb-connector-c.ps1, the way MariaDB Server's own build builds the Connector/C it bundles,
reproducibly (hmailserver/docs/ThirdPartyBinaries.md, "Connector/C 3.4.11, built from MariaDB's source"). The
client ships with its seven authentication plugins, two fewer than 6.3.6, and the installer names each of them
where it took whateverExtras\pluginheld, so that a checkout which once fetched the 3.4.9 files cannot ship them
again: the archive the 3.4.9 files came in also heldauth_named_pipe.dll, MariaDB Server's ownnamed_pipe
server plugin, which no client loads, andauthentication_windows_client.dll, the GPL client of MySQL Enterprise
Edition's Windows authentication, neither of them Connector/C. An upgrade removes both fromBin\plugin. So a
database account that signs in with MySQL Enterprise's Windows authentication (authentication_windows) can no
longer sign in after the upgrade, and the server cannot open its database - no such setup is known to the project,
andThirdPartyBinaries.mdsays what one would have to do. The Linux server loads the distribution's
libmariadb.so.3, which the distribution patches; the Docker image and the snap stage Ubuntu 24.04's, which
predates the flaw. Renovate work item 47; CVE on the GitLab dashboard (record 404650204). -
Fixed:
--import-stored-secrets-keyleft copies of the stored-secrets key in memory freed without being written
over, and on Windows every read of the key from its line left one. The verb read the key file with the server's
general text-file reader and wrote over only the last copy it made: the C runtime's buffer for the file, the buffer
the file was read into, a narrow copy of that and the wide string returned - each the whole file, the key's line in
base64 among it - went back to the heap as they stood, on Windows and Linux alike, and an exception out of the
import freed the last one too (from 6.3.4, which brought the verb). The file is now read by the operating system's
own reads into one buffer, never grown, and that buffer and every copy made of the key on the way to its line under
the machine's store - its base64, its bytes, its hex, the line itself - are written over withOPENSSL_cleanse
however the import ends;--export-stored-secrets-keywrites over the file's text the same way, an exception
between the text and the write included, where the verb's fill came after the write. And the key's line, opened by
the machine's store - DPAPI, or the Linux key file - was returned byCrypt::DeCryptbeside its other returns,
which a compiler answers with a copy where it does not build the string where it is returned: MSVC 19.51 as the
Windows server is built does not, nor does GCC 15, and clang does. So the opened line, the whole key in hex, went
back to the heap as it stood at every start of the service and whenever a verb read the key (from 6.3.4); the 6.3.6
change to that read left it. The line is now built where it is returned. On Linux the scratch buffer in which iconv
measures a conversion - the key's line among what it measures, on its way into the machine's store and out of it -
is written over as the measure ends, where the last of what it converted stayed on the thread's stack. And the key a
process holds once it has read it from its line - the service's for as long as it runs, a verb's until it returns -
is written over as the process ends, where it went back to the heap as it stood, the whole key, at every stop of the
service and as each verb that read it returned (from 6.3.4). A key file saved with a UTF-8 byte order mark, which
the import refused as not a key file, is now read as one, as a UTF-16LE file with its mark already was; a file
larger than 64 KiB is refused, as no key file is that large; and one that cannot be read says so, where it was
called not a key file.mailbox-crypto-testswatches every block the import, the export and the reading of the key
from its line give back to the heap - the key file saved with LF, CRLF and a UTF-8 mark, and in UTF-16LE - for a
piece of the key as narrow text, as aString, in UTF-16LE or as its bytes, and finds none, the blocks the process
gives back as it ends included; with the import's old read it finds three blocks for a key file; built with GCC
before the change, the copy of the line; and without the wipe at the end of the process, the block of the key the
process held. The server's self-test (StoredSecrets::Test, run on Linux by the same program) reads each of those
files through to its key, refuses one over 64 KiB and says one that is not there is unreadable, and
Security/SecretsAtRest.csimports the key file saved again with a UTF-8 mark and in UTF-16LE, and refuses it with
64 KiB of blanks after it. Found by the project's own review. SecretsAtRest.md,
"Carrying it to another node". -
Fixed: with SRS on, mail to an SRS address this server had handed out was not held to the relay rules. The
Sender Rewriting Scheme (SRSEnabled, off by default) gives a message forwarded to the outside a Return-Path
SRS0=...@one of this server's domains, signed so that no one can make one up, and a bounce to that address is
passed back to the original sender. Every message to a valid address was taken as delivery to a local recipient, not
only a bounce (from 6.2.1, which brought SRS). Only a bounce, with a null reverse-path (RFC 5321 4.5.5), is taken so
now; anything else sent to the address is mail for the original sender's domain and is held to the relay rules like
any other. ItsRCPT TOis answered as it always was (but554 5.6.3in a session that has not authenticated and
asked forBODY=BINARYMIME, as for any outside address), so that a server verifying the address with a sender of
its own - Postfix'sreject_unverified_senderasks asdouble-bounce@- is still told it exists and takes the
forwarded message; where the relay rules refuse it, the transaction is refused atDATAorBDATwith the reply
theRCPT TOwould have had, and the SMTP log says why. Such an address keeps its transaction to itself: a
recipient named after it is answered452 4.5.3, which asks the client to send to it in a transaction of its own,
rather than lost with it atDATA, and named after a recipient already taken it is refused at once, as any outside
address is.SMTP/Srs.csholds each, the refusal atBDATas well as atDATA. Found by upstream's review of its
own SRS (upstream PR 649), which shared the shape and refuses theRCPT TOitself (work item 94; CVE-2026-114473). -
Fixed: an SRS address or a BATV tag almost a whole cycle old was accepted as fresh, and a signature was compared
in a time that said how much of it was right. Both carry their day as a number that wraps - every 1024 days for
SRS, every 1000 for BATV - and both accepted a day one ahead of today as clock skew, which modulo the cycle is also a
day 1023 (SRS) or 999 (BATV) days old (from 6.2.1 and 6.2.3, which brought them). Tomorrow's stamp is taken now
only in the last hour of today, for a clock a little ahead of this one, and a stamp older than the window - 21 days
for SRS, 7 for BATV, to the end of the last day by this server's clock as before - is refused, except in that last
hour of a day, when a stamp 1023 days old (BATV: 999) carries tomorrow's number and is taken for tomorrow's: no
number can tell the two apart. The signature is compared in constant time (HashCreator::HexDigestsMatch), as
password hashes are. And SRS no longer rewrites a sender into an address no server would take - a quoted local
part carried into the unquoted SRS one, or a path past RFC 5321's 254 characters - but forwards the sender as it
came. The README says so. Held by the in-process tests at fixed times (ClassTester, which
Infrastructure.MainOperationsruns). Found by upstream's review of its own SRS (upstream PR 649), whose clock it
moves an hour forward for the whole test, which would have ended every address's last day an hour early here. -
Fixed: a SASL PLAIN client that asked to act as another account was signed in as itself and told it had
succeeded. RFC 4616 has a PLAIN message name the identity to act as (the authzid) beside the one to sign in as,
and the server verify that the credentials permit the first. SMTP, POP3 and ManageSieve read it and set it aside;
they refuse it now unless it names the account signing in (prepared with SASLprep as the authentication identity
is, domain aliases and the default domain applied, case aside), before the password is looked at and without
counting it as a guess at one. IMAP, which lets the master user act as another account, answered an authzid
naming the client itselfBAD No master user defined.; it takes it as none.
Security/SaslPlainAuthorizationIdentity.csasks each of the four. Found by upstream issue 652 (upstream PR 656). -
Fixed: the SMTP log wrote some credentials in the clear. The server hid the credential of
AUTH PLAINonly when
it came afterEHLOand in one exact form, and the password line ofAUTH LOGIN. AnAUTH PLAINsent before
EHLO, or with a space too many, the initial response ofAUTH XOAUTH2andAUTH OAUTHBEARER- a bearer token -
and the continuation lines of SCRAM-SHA-256, OAUTHBEARER and GSSAPI went into the log as the client sent them, base64
and readable to anyone who could read the log. The scrubber the POP3 and IMAP logs go through had an SMTP part that
nothing called. Every line of a SASL exchange is hidden now but the user name ofAUTH LOGIN, andAUTH PLAINkeeps
its authentication identity, as before.Security/SaslCredentialMasking.cssends each and reads the log; the
evidence pack says what the logs hold. Found by upstream issue 654 (upstream PRs 656 and 661;
work item 95; CVE-2026-114471).
What changes on upgrade without a switch
- Mail from outside is no longer signed as the hosted domain its
Fromnames, and nor is any copy of it. A
message is signed as itsFrom's domain only when the server is entitled to: a submission by a signed-in user,
held to item 2, and every copy of it; a message the server makes itself; and one an IP range lets in as one of this
server's domains without signing in. Mail that came in from outside - an SMTP session that did not sign in and was
not such a submission, a download from an external account, a message imported from a file - leaves with the
signature it came with, and so does an account's forward of it, a rule's forward or copy, a Sieveredirect, an
alias or a distribution list to an address outside, the mirror copy and a journal rule's copy. A managed list does
not sign its copies of such a posting as the list's domain when an address of the list domain's organisation is in
itsFromfields. Messages in the queue at the upgrade are signed as before; a message the quarantine held before
it is taken for mail from outside unless it has noReceivedfield, or the one on top is this server's own for a
session that signed in, or the send API's (Security). - An authenticated submission is signed as another hosted domain only where its account may send as the
From's
address. A signed-in user's message is signed as itsFrom's domain when that is the account's own domain or one
of its domain aliases, whatever the address in it, and as another hosted domain only when the account may send as
that address: its own, an alias pointing at it, a mailbox that granted it the post right, an address it proved, a
masked address of its own. Otherwise it is still accepted and sent, unsigned as that domain, and so is every copy
of it; the debug log says why. A session a trusted upstream signed in throughXCLIENT LOGINunder a name that is
no account here is held to the same rule with the name in the account's place. Before upgrading, give every
account that sends as another hosted domain's address an alias or a grant, or that mail goes unsigned (Security). - A rule or a Sieve script no longer edits who a message is from, its loop count or
Auto-Submitted. A Sieve
script that adds or deletesFrom,Sender,Resent-From,Resent-Sender,DKIM-Signature,Auto-Submitted
or the loop count is refused when it is stored or checked - ManageSievePUTSCRIPTandCHECKSCRIPT, the
self-service filter, COM'sCheckSieveSyntax- with the reason. A Set header value ofFrom,Sender, the
Resent-pair orDKIM-Signatureis for the server's rules alone, and no rule sets the loop count or a name with
a line break, a colon or a character outside ASCII:RuleAction.Save,Rule.Saveand the REST API's rule routes
refuse them. A script or a rule stored before the upgrade, or restored from a backup since, still runs without that
edit, and the application log names it. To setFromon a domain's mail, make the rule a server rule with a
condition on the domain. Event scripts may still set the loop count (Security). - A rule's Reply goes from an address its owner may send as, and a rule's notices from its owner's domain. An
account's rule replies from an address the account may send as - its own, an alias of it, a mailbox that granted it
the post right, an address it proved, a masked address of its own - and a domain's rule from an address of the
domain or of one of its domain aliases, one email address in either case; an account's rule the server
administrator saves is held to the account's addresses too. Such a rule is refused when saved, and one stored
before, or restored from a backup since, sends no such reply, which the application log names; the rule's other
actions are taken. The reply's name is one quoted
name before its address, a reply from no address has its name before an empty<>, and a name with an@in it is
left out. A rule's Warn sender notice, and the notice of a Reject met after acceptance, come from the postmaster
of the rule owner's domain and are signed as it; a server rule's from the postmaster of the default domain when one
is set and hosted here, else of the hosted domain the message was addressed to, else of the sender's own
(Security). - A Sieve
notifygoes from the account's own address unless its:fromreaches the account, as avacation
reply already did: its own address, or an active alias whose value is it. A:fromthat is not an address is
refused when the script is stored or checked; a script stored before still runs, its notifications from the
account's own address. The address found for a:from, avacationreply's too, must be the one the script
wrote, letters' case aside, so that on MySQL a:fromwith an accent on a letter of an address no longer finds it
(Security). - The rule loop limit holds for every message. A loop count that is not digits alone, or is above the limit,
reads as the limit: the message is delivered, and no forward, reply, copy, redirect, vacation or notification is
made from it. A Sievenotifyis not sent once the count has reached the limit; an out-of-office reply, an
account's or a domain's, carries the count + 1 and is not sent at the limit. SoRuleLoopLimit0, which already
stopped every other generated message, now stops out-of-office replies too (Security). - A line break in a header value is written as spaces, a space for each CR and each LF, unless it folds the
line, in every message the server makes, and COM'sMessage.Saverefuses a credential's message whose values hold
a line break that does not fold them, and says why, where 6.3.6 sent a line break that began a field of its own
as that field (Security). - On Windows,
/ProtectFoldershardens the program folder,BinandControlPanelat every installation and
upgrade: SYSTEM and the Administrators with full control,BUILTIN\Userswith Read & execute where every user
could read them before, nothing inherited, and the Administrators as the owner of one somebody else owned. What it
does not change - an entry of a file's own, a folder above the program folder that others may rename - is named on
aCHECKline with theicaclsthat removes it. Under Program Files the first run makes the three folders'
inherited entries their own, and nobody may do there anything they could not before. For an installation at a
drive's root, read What 6.3.7 does not fix, above (Security). - An ARC seal attests this server's own results, and says whose they are. With
ArcSealingEnabledon,spf=is
the result this server's own check reached when the message arrived,nonewhen the check did not run, and no
field of the message is read for it; the sealing domain is the one the server records as sending the message on -
the forwarding account's, the rule's, the journal rule's or the list's domain, the submitting account's, the
domain of an alias or a distribution list that expanded to an address outside - and a message with none recorded
goes unsealed; and the results nameheader.d=for each domain whose signature verified and the SPF domain as
smtp.mailfrom=. Messages queued or held before the upgrade are sealed withspf=none, by an alias's or a
submitting account's domain, and otherwise not at all (Security). - The quarantine's file names carry where a message came from. A quarantined copy's name carries whether it
came from outside, its SPF result, the relaying domain and whether its SPF domain was the sender's
(<GUID>.spf-fail.outside.eml,.relay-<id>), which a release reads back; a tool that reads the quarantine
folder by name meets the longer names (Security). - A copy of mail from outside keeps its envelope sender unless the message passed DMARC for the organisation its
Fromnames. As Before you upgrade, above, says: a forward, a redirect or a rule's forward of such mail that
did not pass is notSRS-rewritten into that organisation; a copy that would still carry an identity of it - a
null envelope sender under a host name of that organisation, or the envelope sender the message came with when
that is at this server's host name or at a hosted domain or a subdomain of one - is not sent on to an address
outside, and the application log names each recipient it was withheld from; a managed list does not distribute
such a posting, and a distribution list's bounce address is not given to it. A download from an external account
whoseFromis an address hosted here keeps a null envelope sender. A forward of mail that passed, and an
ordinary forward of third-party mail, are rewritten and sent on as before (Security). - Strip action. Under Remove all attachments and deliver the text, a message whose one part is an attachment,
and an S/MIME message signed as one part, arrive as the action's note alone; a message the server cannot read, or
whose file it cannot rewrite, is deleted, and each of its recipients is sent the "message deleted" notice whatever
the two notice settings say. A message that is one text part, or encrypted, is delivered as it is. The notices of
Delete the whole message fill the message's From and To from the envelope when the message cannot be read,
and its recipients' notice now reaches the recipients of a message with an empty envelope sender (Security;
Anti-spam, anti-virus and mail flow rules). - SASL PLAIN that asks to act as another account is refused. SMTP, POP3 and ManageSieve refuse an authzid that
does not name the account signing in, before the password is looked at and without counting a guess; IMAP takes
an authzid naming the client itself as none (Security). - With SRS on, mail from a sender to an SRS address is held to the relay rules. Only a bounce, with a null
reverse-path, is passed back to the original sender; anything else sent to the address is relayed only as the
relay rules allow, and otherwise refused atDATAorBDATwith the reply theRCPT TOwould have had, and such
an address takes a transaction of its own (452 4.5.3). SRS and BATV take tomorrow's stamp only in the last hour of
today, so of two servers that share the secrets and whose clocks are more than an hour apart, the one behind
refuses, near midnight (UTC), the stamps the other made after its own midnight
(ActiveActive.md); and SRS forwards a sender it cannot rewrite into an address a
server would take as it came (Security). - The SMTP log hides every line of a SASL exchange but the user name of
AUTH LOGINand the authentication
identity ofAUTH PLAIN: a log reader that read a bearer token or a SCRAM, OAUTHBEARER or GSSAPI line there
finds it hidden (Security). - MariaDB Connector/C 3.4.11, and two plugins fewer. An upgrade on Windows removes
auth_named_pipe.dlland
authentication_windows_client.dllfromBin\plugin, so a database account that signs in with MySQL
Enterprise's Windows authentication (authentication_windows) can no longer sign in, and the server cannot open
its database: ThirdPartyBinaries.md says what to do before upgrading
such a server (Security). - A confined credential, a cross-site request and a
serveradminrole with a list of domains. A credential
confined to named domains is refused a domain's rename, a domain alias, a custom role's creation, change or delete,
a domain's or an account's rule'sbind_to_address,send_using_routeorscript_functionaction, a domain's
relay host changed to an address that is not public, and the server-wide metric history, over REST and over
COM. A credentialed
state-changing REST request a browser reports as cross-site (Sec-Fetch-Site, or anOriginthat is not this
server's) is refused, and so are the subscribe form and the password-reset request from another site; scripts,
hmctland the PowerShell module send no such header. Over COM an account assignedserveradminwith a list of
domains is confined to them, and a role is given or taken away only by a credential that takes in what the account
holds now and afterwards. Before upgrading, giveserveradminwith no list to an account that is to
administer the whole server over COM (Security). - A database write that failed is reported, and a lost connection reconnected. On MySQL, MariaDB, SQL Server
and SQL Server Compact an insert that forgives a duplicate key forgives that alone: any other failure is reported
(HM5032), where it was taken as done with no row written, and on MySQL and MariaDB a lost connection is
reconnected and the statement run again. TLS's error 2026, and MySQL's 4031 for an idle session it closed, are
taken for a lost connection too, so the statement after a database restart, a failover orwait_timeoutno longer
fails. On PostgreSQL a transaction in which a statement failed is reported as failed at its commit. An error log
that was quiet may now name failures that were hidden (Correctness). - A script's transaction that the database ended is refused, not carried on. Once the database has ended the
transaction of a COMDatabaseobject - a deadlock, MariaDB's error 1020, a row-lock wait that timed out where
innodb_rollback_on_timeoutis on, a connection lost or closed - every later statement, script and prerequisite of
it is refused with the reason,Database.CommitTransactionrolls back and fails, and
Database.RollbackTransactionsucceeds; a secondDatabase.BeginTransactionrolls back the open one. A script that
goes on after a failedDatabase.ExecuteSQLinside a transaction callsDatabase.RollbackTransactionand runs the
whole transaction again, or stops (Correctness). - A DNS lookup that fails is a failure, not a name with no addresses (upstream PR 640). The HELO and PTR tests no
longer score a name they could not look up, a DNS blacklist and SURBL no longer read a failed lookup as "not
listed" - a blacklist says so in the TCP/IP log and, at most every 15 minutes, the application log, and SURBL asks
the message's other names - and a DKIM selector that is a CNAME whose query failed is aTempFail, where it was a
PermFail. The never-ban list asks again a minute later; delivery whose mail servers' lookup failed so tells the
sender that the DNS query failed (4.4.4), and retries either way (Correctness). - With
DisconnectInvalidClientson, more refusals count towardsMaxNumberOfInvalidCommands: an over-long
line, TURN and VRFY, a command out of sequence, a message over the size limit or with bare line feeds or cancelled,
the spam score's refusals, an event script's554, ETRN for a domain that is no route and a refused XCLIENT. The
switch is off by default; the office profile turns it on with a limit of 20 (Delivery and SMTP). - A message over 80 MB goes through the rules, the forwards, a bounce and the spam headers by its header alone.
Header conditions and actions apply; the five conditions that need the body do not match, a rule's script function
is not run, and the disclaimers and the signature are not appended, each with a line in the application log; a
forward at the rule loop limit is not made. Everything else that reads messages refuses one over 80 MB as before
(Delivery and SMTP). - A link's host that is not ASCII is read by UTS 46, and an HTML part's character references as a browser reads
them, for the SURBL test and the sender lists' URL entries: messages whose links escaped the lists that way can
now be tagged or held. A URL entry whose host is not ASCII and has a label UTS 46 refuses is refused when it is
saved, and an entry written in Unicode is stored in itsxn--form and covers a link written either way
(Anti-spam, anti-virus and mail flow rules). - SpamAssassin is sent a collected message's own
Return-Path. spamd is sent the envelope sender ahead of the
message, never written into it, so a scan that fails leaves the message exactly as received and a completed one is
stored as SpamAssassin returned it; for a message collected from an external account it is sent the path the
other server recorded in the topmostReturn-Path, where it was the guess from the From field, so its SPF and
envelope rules may score such mail differently (Correctness). - IMAP.
COPYandMOVEby message number read their set in the client's numbering: a set that names a
message another session has just expunged is refused,NO [EXPUNGEISSUED], for the client to send again. ACOPY
orMOVEthat fails part way leaves the destination as it was, and a session that copies into the folder it has
selected is told of the copies.SEARCH,SORTandTHREADask a criterion named again beside itself once;
NOT TEXTis the complement ofTEXT; aSEARCHorSORTthat namesMODSEQcarries the item.FETCH BODY[N.MIME], andBODY[N]of a multipart, are the stored octets,BODYcarries no extension data, and
BODYSTRUCTUREdescribes a part too deep or broken to read as one part.APPENDUIDandCOPYUIDgo only to a user
who may select the folder, a copy or an append keeps only the flags the user may set there, and a refusal by the
access control isNO [NOPERM](IMAP, POP3 and the webmail). - POP3 accepts
UTF8after the login, until the session's firstSTAT,LIST,UIDL,RETR,TOP,DELEor
RSET, so that classic Outlook downloads its mail (work item 85; IMAP, POP3 and the webmail). - A rule's Run function is refused when it is saved unless it names a plain function, for a rule of every
owner, over COM and REST; one stored before is still refused when the rule fires, as since 6.3.4.Rule.Save
changes a rule around an action it would refuse, leaving the action as it is, and moving a rule or an action over
COM writes its place alone, where it stored changes made and not saved (Correctness). - Stops, starts and reinitialises of the servers take turns, and the stop gives the message indexer a minute.
Every stop, start and reinitialise holds one lock; COM'sApplication.StopandStart, and an event script's
Application.Reinitialize,StoporStart, wait at most five seconds for one under way and are then refused
with an error that says so, and anOnErrorscript's reinitialise on the indexer's own thread is refused. The
stop waits up to a minute for the indexer's thread; setup, the live update's silent install and the uninstaller
waitShutdownDrainSecondsplus 90 seconds for the stop, where they waited plus 30, and the Control Panel's
Restart the service follows the stop's own wait hint and starts the service once its process has ended
(Correctness, Operations, Control Panel). On Windows a stop or a reinitialise no longer waits for a thread of
a work queue past the queue's ten seconds - a delivery, a script, an external account's fetch or a lookup held by a
host or a database that has stopped answering - where it waited for that thread's task however long it took; it goes
on without the thread, and the servers a reinitialise starts listen on every port while the thread runs on
(Correctness). - Scheduled tasks run in the second they are due, and no longer wait an hour when the clocks go back; the mailbox
keys' idle sweep runs every ten seconds on a thread of its own (Correctness, Security). - With the full-text index on, a search asks the index a window at a time, and a start no longer holds the
database for minutes. The webmail's searches answer after about two and a half seconds where to go on from
(complete: false) and say how many message files they opened (opened); IMAPSEARCHand the eDiscovery search
ask a window of their messages at a time. The indexer's sweeps of the rows of messages that are gone run in slices
once it has caught up after a start, where they were one statement each before its first message (IMAP, POP3 and
the webmail, Operations). - An HTTP answer that outlasts the request deadline is written whole, a piece at a time, and one the server ends
part way ends with a reset, where it was cut off after its first 64 KB and closed in order (IMAP, POP3 and the
webmail). - The Control Panel's pages moved. Updates is a page of its own under Maintenance; the sidebar has Compliance and
Virus scanning groups, and Spam filtering, with its six allow and block lists under Lists; the two TLS pages are TLS
certificates and TLS versions & ciphers; API & monitoring is Monitoring & alerts, with the alerts, the webhooks
and SNMP, the Windows Event Log is on Logging and ManageSieve a tab of Protocols; the rule limits are a card on
Rules, the PROXY protocol and XCLIENT a card on Incoming relays, and the migrations a card on Backup, restore &
migration; REST API keys is under Sign-in & access; and before sign-in the Control Panel shows the sign-in card
alone. Ctrl+K takes every old title and route to its page. A Control Panel signed in to a server on another
computer shows and saves the server'shMailServer.ini[Settings]values, and no longer reads or writes this
computer's file for the[Database],[SendingLimits]and[LDAP]values, which it cannot read from there,
where it showed this computer's file or the defaults as the server's (Control Panel). - Smaller things a script, a monitor or a log reader may notice.
HM6281names the setting that holds an entry of a trusted address list that does not parse, and each entry of
each list is reported once (Correctness).- A quoted string ManageSieve sends writes a line break and a NUL as a space (Correctness).
- A criterion or an action added over COM to a new rule after its first save is stored under the rule, and the
REST API saves a rule's Reply with nofrom_address, as COM does (Correctness). - A
ScriptTimeoutabove 4,294,967 seconds is taken as that, where it ended every Windows event script at once
(Correctness). - A backup's outcome, its log line and its event are reported once the next backup may start (Correctness).
- The VBScript calls of
OnClientValidatePassword,OnExternalAccountDownloadandOnDeliveryFailedare quoted
as every other call is, andOnDeliveryFailed's error keeps its quotes (Correctness). - A DNSSEC validation that fails says where and why in the debug log, and the application log's lines for a
skipped host end with the reason in brackets (Delivery and SMTP). - New errors for a message over 80 MB:
HM7840,HM7841andHM7842(Delivery and SMTP), andHM5509says
that SpamAssassin's verdict could not be read (Correctness). - On Linux an unexpected exception on one of the server's own threads no longer ends the server, and a SIGTERM
during a SIGHUP's reinitialise is acted on at once (Correctness). - The REST API's OpenAPI document describes each path once, with every method it answers (Administration).
- Every Linux install carries the notices of the Unicode and HTML data compiled into the server, and the packages'
licence fields name them (Downloads and the release).
Correctness
-
Fixed: a
ScriptTimeoutof more than 4,294,967 seconds ended every VBScript and JScript event script in under a
second (release review finding CP-5). The Windows engines' watchdog is armed with a 32-bit count of milliseconds,
and the seconds were multiplied into it unclamped, so that a timeout of 4,294,968 seconds - an administrator's
"about fifty days" - armed a 704 ms watchdog, and 8,589,935 seconds a 408 ms one, and every script was interrupted
as it began. The seconds are clamped to 4,294,967 - forty-nine days and seventeen hours, the most whole seconds the
count holds - before they are made milliseconds (ScriptServer::WatchdogMilliseconds), as the DNS timeout is
clamped before it is scaled. The JavaScript engine of the Linux builds counts its deadline in 64-bit time and never
wrapped.ScriptWatchdogTester, which ClassTester runs on Windows andscript-binding-testson Linux, holds the
bound, and the Control Panel's field takes no more than it. -
Fixed: a stop or a reinitialise of the servers that gave up waiting for one of a work queue's threads freed the
queue with the thread still inside it, and with the TCP/IP and session queues the TCP/IP service too. A work
queue's stop waits ten seconds for each of its threads and then goes on without it, so that a task in a wait the
stop cannot interrupt - a scanner, a script or a host that has stopped answering - does not hold the stop for
ever. The queue was then freed all the same, five seconds later, and the TCP/IP service as soon as the stop let go
of it, though that thread, or a connection a task held, was still on its context: when the task ended, the thread
went on in freed memory - on Windows after the stop had waited for the task, however long it took, which the
re-check of 10 October 2026 saw end in an access violation and HM6075 in "work queue .". The manager of the work
queues now keeps a queue whose stop gave up on a thread until the thread has left (WorkQueue::HasRunningWorkers),
and the TCP/IP queue and the three session queues each keep the TCP/IP service alive until they are freed
themselves, so that the task ends in objects that still exist. The stop goes on without the thread - on Windows too,
where it had waited for the task - and without the five more seconds. The stop of the TCP/IP service closes every
one of its listeners itself, whatever the drain before it managed - the drain's request to close a listener waits on
that listener's strand, which a thread in its accept handler, running theOnClientConnectscript, holds - so that
the servers a reinitialise starts while such a thread is still there listen on every port. The start - a
reinitialise's - puts a new set of spam tests in place and leaves the one such a thread is still going through, in a
session's SPF check or the scan of a message being finalised, to that thread until it has finished with it, where it
freed it under the thread (SpamProtection::Load). Once the thread has left, the queue and the service are freed
within a minute (the queue health task) or at the next start or stop, and the connections still on the old service are
closed then.WorkQueueTeardownTesterremoves two queues while their tasks wait twenty seconds, inside the server on
Windows (ClassTester) and asworkqueue-teardown-tests, a tester the Linux build runs andlinux-sanitizeruns
under AddressSanitizer: on the tree before this change AddressSanitizer reports the use of the freed queue and the
freed object it kept, and here both are kept until the tasks end and freed after.IOServiceLifetimeTester, inside
the server on Windows, stops and starts the servers while a task of a session queue, and then a handler on a
listener's strand, is held past the stop's give-up, and holds the TCP/IP service to being kept until the thread has
left and freed after, and the start to listening on that listener's port.API.EventsandAPI.EventsJavaScript
reinitialise while anOnClientConnectscript runs on, or anOnAcceptMessagescript finalising a message, and
AntiSpam.SpfEvaluation, in the Windows and the Linux suites, while a session's SPF check runs on, its eight lookups
each answered three seconds after it is asked; each requires port 25 to greet while the script or the check still
runs, and again once it has ended, and theOnAcceptMessagetests a message to be taken and delivered after it. -
Fixed: on Windows, a work queue's stop that gives up on a thread still inside it did so after about twenty
seconds, not the ten it promises, and the destructor's five more were about ten. The wait was a count of passes,
four hundred of 25 ms for the stop, each pass taken for its interval - and a pass is longer than that: its sleep and
its one-millisecond join each end at a tick of the system timer, 15.6 ms apart on Windows unless something has asked
for finer ones (the process itself from Windows 10 version 2004 and Server 2022; any process before, where the
resolution is the system's), so that a pass took about 50 ms and the four hundred about twenty seconds (since
the interval became 25 ms; at the 250 ms before it the ticks added little). A stop of the servers with a thread
stuck in each of several queues - a delivery, a script, a lookup held by a host that has stopped answering - waited
twice as long as the log says, queue after queue, and a service stop that long is one the service controller gives
up on. The wait is now bounded by the clock, after at least one pass; the interval and the diagnostic line's cadence
stay as they were. Found by thewindows-gate-s1vmhalf of the batch's pipeline on 17 October 2026:
WorkQueueTeardownTester's two stops (above), whose tasks wait twenty seconds so as to outlast a ten-second stop,
returned only as the tasks ended, 19.99 seconds after each had begun, andMainOperations.TestInternalsfailed,
where the runs before it had passed by the few milliseconds the join ended ahead of the task. The tester holds the
stop to returning before its task ends, on Windows and inworkqueue-teardown-testson Linux. -
Fixed: on MySQL and MariaDB, the statement after the database had ended a TLS session, or after MySQL had closed
one for being idle, failed instead of being run again on a new connection. The database layer runs a statement
again on a new connection when the old one turns out to be gone, which it recognised by the client's errors 2006 and
2013 alone. A session the database ends - a restart of it, a failover, aKILL- is reported over TLS, which the
server asks for by default ([Database] TlsModeprefer) from any MySQL 8 or MariaDB 11 that offers it, as TLS's
error 2026 (withTlsModedisable it is 2013, which was reconnected already); and MySQL 8.0.24 and later answer the
first statement after closing an idle session (wait_timeout, eight hours as shipped) with error 4031, TLS or not.
So after either, the next statement on each connection of the pool failed, and what it was for with it (HM5032), and
only the one after it, which read 2006, reconnected. 2026 now means what 2013 means, and so does 4031 from MySQL
GitHub shows at most 125,000 characters of release notes and these are 810,467. The complete notes are on GitLab: https://gitlab.com/hmailserver/hmailserver/-/releases/v6.3.7. The copy attached below as release-notes-v6.3.7.md is the notes as they stood on 18 October 2026, when this release was copied here; a release here is immutable, so the copy cannot be replaced, and it lacks the changes made to the notes on GitLab since then, the CVE identifiers GitLab's CNA assigned after the release among them.