Skip to content

hMailServer 6.3.6

Choose a tag to compare

@RainbowWizard-coder RainbowWizard-coder released this 18 Oct 05:26
· 1774 commits to master since this release
Immutable release. Only release title and notes can be modified.

Re-published on GitHub on 18 October 2026 from the GitLab release v6.3.6 of 2026-10-06. 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.6-x64.exe: the installer, Authenticode-signed by Progressive Robot Ltd. It installs a new server, or upgrades an existing one (6.3.5, 6.2 or older) in place: read the notes below and back up the database and the data folder first. SHA-256 2f673913e5f127d688090e98f6001ca567cd45560d452aebc74421b5533cfe53.
Windows on ARM64 (preview) hMailServer-6.3.6-arm64.exe, signed the same way. A preview: it has not run on ARM64 hardware, and a new installation is set up against Microsoft SQL Server or PostgreSQL, since SQL Server Compact has no ARM64 build. SHA-256 59f8edd259bdef2e31dd83da152c3f748d67a27a19e6c3c8f5a2ade2ff84227c.
Linux The .deb and .rpm packages for each distribution and the two AppImages, under Assets below, in Packages; hmailserver-linux-6.3.6-SHA256SUMS.txt lists them.
Container image registry.gitlab.com/hmailserver/hmailserver:6.3.6, for linux/amd64 and linux/arm64.
Checking a download Each file the release pipeline made is signed with Sigstore, its bundle beside it as <file>.cosign.bundle; Verification, at the end of these notes, gives the identity to check it against.

hMailServer 6.3.6

Everything that landed on master after the v6.3.5 tag, from 29 September 2026 (compare v6.3.5 with
v6.3.6
). It is the last release for the
time being: after it the project makes security releases only, and the 6.3 line receives security fixes until 30
September 2027 (below). 6.3.6 fixes the security flaws the project's own reviews of the code found, CVE-2026-103011
among them, and takes OpenSSL 4.0.3 into the Windows installers (both below), fixes what 6.3.5's release and the first
live updates from 6.3.4 found, replaces with the project's own code the last code in the server whose licence a
distribution would have had to settle, and carries every file the release pipeline makes, the ARM64 Windows installer
among them. The schema moves to 6108 in three steps: 6106, which changes no column, gives every external account's
days to keep messages the one meaning both fetchers now read, keeping each account doing what it did or stopping it
deleting (What changes on upgrade without a switch, item 22); 6107 finishes the greylisting key schema 6072 began
(item 23); and 6108 adds the never-ban list by reverse name and, for each SMTP listener, what it does with AUTH, On
for every listener an upgrade finds (item 25). The security notes come first, then what changes on upgrade without a
switch. The operator documents named below are in hmailserver/docs, which has twelve more of them than it had at
6.3.5.

Releases pause after this one. 6.3.6 is the last release for the time being, by the owner's decision of 28
September 2026, and no feature release follows it until the maintainers say otherwise in SECURITY.md,
"While releases pause". A confirmed vulnerability still ships, in a release of its own that is 6.3.6 with fixes -
6.3.7, 6.3.8 and so on - on the targets SECURITY.md gives: a critical or high one as soon as its fix has passed the
gates, and none held past 90 days. Other defects are fixed on master and ship with the next security release, or
when releases resume. The 6.3 line receives security fixes until 30 September 2027; before that date the
maintainers write in SECURITY.md whether support continues, and if nothing is written, it ends then. There are
still no maintenance branches: an installation of 6.3.5, 6.2 or anything older is upgraded in place to the newest
release.

What the release carries: 62 files and 160 links (ContinuousIntegration.md, "What a release carries"). Beside
the x64 installer is hMailServer-6.3.6-arm64.exe, for Windows on ARM, Authenticode-signed like it. It is a
preview: the ARM64 build is cross-compiled on the project's x64 Windows runner and has never run on ARM64 hardware,
so read it as built to work (WindowsOnArm.md). If you install it on an ARM64
machine, please say how it went, working or not, in an issue:
the project has no ARM64 machine of its own. SQL Server Compact has no ARM64 build, so a new ARM64 installation is
set up against SQL Server or PostgreSQL, and a silent one is given its database on the command line. For Linux the
release carries the generic .deb and .rpm for x86-64 and, for the first time since 6.3.3, for AArch64 -
cross-compiled and tested under emulation, since no AArch64 machine has run them - and each Debian 12 and 13, Ubuntu
24.04, Fedora 44 and 43, EL 10 and 9, openSUSE Leap 16.0 and Tumbleweed release's own package for both
architectures. It also carries both AppImages, with the source of the libraries they carry and of those their
runtime links; the source tarball, hmailserver-6.3.6.tar.gz, reproducible and with nothing non-free in it, from
which the Debian source package and the RPM spec in the tree build the server as a distribution does
(SourceTarball.md, SourcePackages.md);
and the Helm chart. Every file has a Sigstore bundle beside it, every compiled file and the tarball an SBOM, and
SLSA build provenance names every file by its SHA-256 (SECURITY.md, "Verifying a release" and
"Build provenance"). The apt and dnf repositories and the other channels publish from master once their keys and
accounts exist; none is live as this release is made (Channels.md).

The first container image: registry.gitlab.com/hmailserver/hmailserver:6.3.6, also tagged 6.3 and latest,
one index over linux/amd64 and linux/arm64, each platform's image built from this release's .deb of its
architecture. It is pushed from this release's own pipeline once the release is published, by a job started by
hand, signed with cosign as that pipeline, each platform's image with its SBOM attested to it; until then the
registry holds no image. In the container the update check names the image to pull and installs nothing
(ContainerImage.md).

A Nix flake and Alpine Linux: a Nix flake of the project's own at the repository's root, with a NixOS module
(services.hmailserver), and an APKBUILD and an OpenRC service for Alpine Linux, on whose musl the server now
builds and runs; both are built without the live update and name the command that updates them, and neither is in
a distribution's archive yet (Nix.md, Alpine.md).

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.6, issuer https://gitlab.com, with the
project's id, 86730559, in the certificate: the first release signed there since the project moved into the group
hmailserver on 29 September 2026. Every server from 6.3.4 on has that signer compiled in and requires the id for
it, so a server on 6.3.4 or 6.3.5 installed from the x64 installer or the release's x86-64 .deb or .rpm is built
to install this release by its live update: from 6.3.5 with 6.3.5's own file as its rollback image, and from 6.3.4,
which carries no Sigstore bundle, without one. No live update had installed a release signed at the group's path
when these notes were written. The update is applied by the version already installed, so three things of 6.3.4
and 6.3.5 still hold for it:

  • a Windows service running as NT SERVICE\hMailServer downloads and verifies the installer, then leaves the
    install to an administrator (HM7463), whose Install update on 6.3.4 and 6.3.5 cannot do it: install this
    release by hand, once, as the next paragraph says, and every release after it with one click;
  • on Linux, a backup destination inside /var/lib/hmailserver fails the backup taken before an unattended apply
    (HM5014), and the apply is refused until the destination is moved or the release installed by hand
    (UpdateFeed.md, "From 6.3.4 on, by the live update");
  • a server whose UpdateSigningIdentity, UpdateSigningIssuer and UpdateSourceRepository still 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 this release. Clear the three first - in the Control Panel, with DELETE /api/v1/settings/ini/<name>, or
    on Linux with hmailserver --set-setting <name>= - and not in the file, which 6.3.4 and 6.3.5 put back.

A server from 6.2.28 to 6.3.3 trusts only the GitHub workflow that signed the releases up to 6.3.3, and is upgraded
to this release by hand, once, after the backup Upgrading.md describes; so is a
server installed from an AArch64 package or an AppImage, whose last release was 6.3.3. One pointed at this
project's feed with UpdateFeedUrl downloads the installer at every check and refuses it, so keep
UpdateAutoDownload off there until then. The three settings that let such a server install 6.3.5 do not reach
6.3.6, and must never name the group's path (UpdateFeed.md, "A server from
6.2.28 to 6.3.3"). No release before this one carried the ARM64 installer, so no ARM64 Windows server has a live
update to take it; from 6.3.6 one finds its installer by name, and an x64 server is never offered it. A server
installed from one of this release's per-distribution packages is offered the next release's generic package by its
live update, which apt refuses on Debian 12 and dnf on EL 9, so there it is upgraded with its package manager and
its own release's package of the next version, not by the live update
(UpdateFeed.md, "The download links").

One click installs an update where the service cannot. A Windows service running as NT SERVICE\hMailServer,
the account a new installation has had since 6.3.4, is not an administrator and cannot run an installer, so up
to 6.3.5 Install update told the administrator to download the release and run it by hand. From 6.3.6 the
Control Panel on the server installs it: Install update on the Status page, one Windows prompt, and everything
else happens by itself - the installer is checked, the backup taken, the version to go back to fetched, the
server drained, the release installed, the service started, the previous version put back if it does not start,
and the Control Panel opened again, signed in, on the outcome. What runs elevated trusts nothing the service
wrote: hMailServer.exe /InstallUpdate checks that only administrators can change the installation, copies the
installer out of the service's Updates folder into AdminUpdate, which only administrators can change, and
checks the copy itself - the Authenticode signature with the publisher pinned to Progressive Robot Ltd and the
Microsoft chain it is issued under, revocation online, the version and the architecture, a release newer than
the running one, and the Sigstore bundle, in a child process with the administrator's rights taken away.
SeDebugPrivilege, which the stage needs to read a Control Panel of another user's and the Updater to end the
programs running from the installation folder, is switched on for that alone and off again before anything else
runs, the installer included. The Control Deck, the REST API and a Control Panel connected to another computer say
where to click. The service
never makes an administrator or SYSTEM run anything (ServiceAccount.md,
"The live update"). On LocalSystem the live update is as it was.

A server on 6.3.4 or 6.3.5 under NT SERVICE\hMailServer still says to install this release by hand, since that
is compiled into it; nothing in the feed or the release can change what it does. Download
hMailServer-6.3.6-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 it "prevented an unrecognized app from starting": it
judges a file by how many machines have run it as well as by its signature, and a new release is new to it.
More info names the publisher, Progressive Robot Ltd; with the signature checked, Run anyway installs it.
The one-click install never meets SmartScreen: the installer is not downloaded by a browser, and the Control
Panel's updater starts it itself.

Security

The code of the legacy Blowfish scheme of stored secrets that the project inherited from the original hMailServer,
which this release replaces with its own (under Operations), had two heap buffer overflows. The one in its
encryption is in every version up to 6.3.5, and 6.3.6 fixes it. The one in its decryption is in every
version up to 6.3.3, and was fixed in 6.3.4, whose notes did not say so. Both were found by the project's own
review. The same release makes the helpers of COM Utilities that do work in the server answer only a caller that
has signed in, or an event script running in the service (What changes on upgrade, item 1). GitLab, the CVE
Numbering Authority for projects on GitLab.com, has assigned them CVE-2026-103011 (the encryption, fixed in 6.3.6)
and CVE-2026-103010 (the decryption, fixed in 6.3.4);
work item #49 gives the details from the day of the
release. Upgrade every server running 6.3.5 or earlier.

Calendar messages delivered by mail (iMIP) - a cancellation, an attendee's answer, an updated invitation - changed
the event stored in the recipient's calendar without the server checking who had sent them, in 6.3.4 and 6.3.5;
6.3.6 applies one only when the server verified that it came from the event's organiser, or from the answering
attendee or a delegate of theirs, and delivers anything else as an ordinary message; an invitation filed from a
sender it could not verify no longer keeps the organiser's own out of the calendar (What changes on upgrade,
item 17). A room or a piece of equipment took a DMARC pass for an outside organiser's domain as the organiser's
own, whichever sender at that domain had written the message, in 6.3.4 and 6.3.5; 6.3.6 believes that organiser
only when the message's From is the organiser's address. A room or a piece of equipment also took an invitation
an application sent through the send API, with a key allowed to send as the organiser's address, as the
organiser's own, in 6.3.4 and 6.3.5, where its documentation said such a message is not believed; in 6.3.6
neither a room nor a calendar takes a message sent through the send API as an account's own.

The Control Deck is now served under a script policy that lets its own script run and nothing else - the SHA-256 of
its one inline script, which the server takes from the page it serves - with Trusted Types, whose one policy refuses
markup the Deck never builds, and with X-Frame-Options and Cross-Origin-Opener-Policy, where 6.2.28 to 6.3.5 allowed
any inline script on the page and earlier versions sent it with no policy at all; a proxy in front of the server
must now pass the page through unchanged (ResponseCompression.md, Behind
a reverse proxy).

The webmail's pages are now served with Trusted Types, whose one policy takes out of what a page puts into itself
the elements the page never builds and admits only the page's own two script addresses, and with X-Frame-Options and
Cross-Origin-Opener-Policy, none of which any earlier version sent.

An account's formatted signature is now given to the webmail only as the subset the webmail's own editor admits -
basic formatting, links to http(s) and mailto, inline images, a simple table, and nothing else - whatever is stored,
and the account holder's own save is stored that way. The webmail puts a signature on the screen as markup,
including in an administrator's support session as the account, and earlier versions cleaned it only in the browser
that saved it, so a signature written another way could carry script into the browser that later rendered it. The
signature an administrator sets in the Control Panel, over COM or over the account route is stored as it is written
and appended to the account's outgoing mail with its styles and hosted images, as before; a domain's signature,
which no browser renders as markup, is unchanged.

A message attachment is now served only under a media type from a list of those no browser runs, applies or renders
as a page - pictures, sound, video, PDF, text, the office formats and the archives - and as application/octet-stream
otherwise. Earlier versions kept any declared type that was not one of a few named ones, which left the
executable-JavaScript media-type aliases a browser also runs; the attachment already carried
Content-Disposition: attachment, nosniff and a sandbox policy.

The REST administration listener's plain loopback port now answers only a request whose Host it expects and refuses
any other with 421 before the request is authenticated, so a web page that points a name of its own at this machine
can no longer reach the console through a browser on it (CVE-2026-104659). It answers localhost, an IP address, the
server's host name and the host of each address configured as the one the webmail is reached at from outside. A reverse
proxy that forwards the browser's own Host needs that name to be one of these: upgrading behind such a proxy, set the
webmail's outside address (for example confidential_base_url) or the server's host name to it first
(DevicesAndSessions.md, Which Host the listener answers). A listener with its
own certificate is unchanged.

A wrong administrator password over REST now costs a growing delay, capped at ten seconds, before the next one from
the same source is looked at - on every route that takes it, the password asked again at an administrator passkey's
registration among them, and from the loopback address too, where the brute-force protection of the mail ports does
not reach and where every browser behind a reverse proxy on the same machine arrives. Earlier versions looked at
every guess at once from there. A source is an IPv4 address, ::1, or any other IPv6 address's /64, and an
address that carries an IPv4 client's own - ::ffff:a.b.c.d, the NAT64 and SIIT prefix 64:ff9b::/96, Teredo's
2001::/32 - counts as that IPv4 address, since one /64 of those is every client of the translator; the
listener's own short refusal after the auto-ban's limit covers the /64 of an IPv6 address as well, and such an
address as its IPv4 address, as ::ffff:a.b.c.d always was (What changes on upgrade, item 35). It is a delay,
cleared by the right password: a source that keeps guessing keeps it waiting while it does. Behind a reverse proxy
on the same machine every browser shares one source, so one client's wrong guesses keep the password sign-in waiting
for everyone while they last; the administrator's API keys, passkey and single sign-on are not touched by it
(DevicesAndSessions.md, The administrator password's delay). Found by the
project's own review and its second review of the release (CVE-2026-104659).

A response sent to a browser that came over HTTPS now carries Strict-Transport-Security for a year, which no earlier
version sent, so the browser does not go back over plain HTTP where the credentials could be read; it is never sent on
the plain loopback path, and it does not include the names under the one the browser used.

Creating or revoking a REST API key, and enrolling or disabling the administrator's second factor over REST or COM,
are now recorded on the hash-chained audit trail the Deck's Audit view reads, where earlier versions wrote them only
to the rolling application log or not at all; the key's token and the second-factor secret are never recorded.

The Windows installers, x64 and ARM64, carry OpenSSL 4.0.3, published on 29 September 2026 with fourteen advisories
against 4.0.x, where 6.3.5's x64 installer carried 4.0.2. Of the two rated above Low, CVE-2026-84782 (High) is in
DTLS, which hMailServer does not use, and CVE-2026-84783 (Moderate, in 4.0.0 to 4.0.2 only) applies to the Windows
server: a use-after-free when several threads first use the same certificate at once, which a remote peer can turn
into a crash when connections build their first chains to the same trusted CA certificate together. In the Windows
server those are the connections to a port that asks for client certificates, whose certificates are all checked
against that port's CA file. Its outgoing connections share none: the one context its SMTP, POP3 and SpamAssassin
clients share holds no trust anchor, since a remote server's certificate is checked by Windows' own chain
verification or against DANE records, and each of its other TLS clients loads its own for each connection. Of the
twelve Low ones, nine are in QUIC, DTLS 1.2, the CMP client and SM2, which the server does not use, and one in
SSL_set_SSL_CTX during a handshake, which it never calls. The other two are fixed with the rest: CVE-2026-35189,
the memory a certificate with many relative CRL distribution points makes OpenSSL allocate, which a server that
hMailServer connects to can send, as can a client on a port that asks for client certificates; and CVE-2026-54872, a
timing leak in ECDSA signing on curves other than NIST's. Every Windows installer from 6.0.0-B1 to 6.3.5 carried a
4.0.x that CVE-2026-84783 affects: 4.0.1 up to 6.2.24, and 4.0.2 from 6.2.25. The Linux packages and the AppImages
use the OpenSSL of the distribution they run on, and the container image its base distribution's, and take OpenSSL's
fixes from there.

The webmail now hands the browser an attachment it decrypted itself, of an S/MIME message (since 6.3.2) or an
OpenPGP one (since 6.3.4), as a file of no type (application/octet-stream) rather than under the type the message
named, so that the browser only ever saves it; every version from 6.3.2 to 6.3.5 is affected (CVE-2026-103647). Such a
part that names no file is saved as part with the extension of the type it declares when that type is on a short
list - plain text, CSV, a calendar, a contact card, PDF, ZIP, a key, a forwarded message, PNG, JPEG, GIF, WebP, MP3, MP4
and the three Office documents - and as part alone otherwise; no extension a browser renders as a page or a desktop
runs is ever given, and the file's type stays application/octet-stream.

In the same way, a file the webmail saves for a second account shown beside the first is now always of no type, and
a photo it shows for that account, or an attachment its offline store answers without a connection, keeps the type
the server named only when that is an image type the page shows (or, offline, a PDF); 6.3.4 and 6.3.5 used the type
as it came.

One more hardening change, found by the project's own review: a mailbox key encrypted at rest - an account's or a
domain's recovery key, opened, made or sealed again - and the keys derived from it are written over when the code
holding them is done, however it ends: a return, and now an exception thrown through it as well (from the database,
an allocation, the log), which left such a key in memory freed without being written over, in 6.3.4 and 6.3.5.
The lines of a stored-secrets key file that --import-stored-secrets-key reads, the key's own line among them, are
written over the same way, the key's base64 is no longer copied into a string that was freed as it stood, and the line
being read no longer grows a character at a time into larger buffers, freeing each smaller one - most of the key in
base64 - as it stood. And a key put into base64 - the stored-secrets key --export-stored-secrets-key writes, an
account's mailbox key made when its encryption is switched on and held under the machine's own key until its first
sign-in - no longer leaves the whole of it in base64 in the buffer it was encoded in, freed as it stood, as it did in
6.3.4 and 6.3.5. Nor does the stored-secrets key read from its line in hMailServer.ini, as the service reads it at
its start and each key verb when it runs, leave the whole of it in hex in a copy freed as it stood, as it did in 6.3.4
and 6.3.5.

The Control Deck is held tighter to what the server hands it, found by the project's own review. It draws an
address - the release page the update feed names, the documentation wiki's, an eDiscovery export's - as a link only
when it is an http or https address, where 6.3.4 and 6.3.5 drew the address as it came. The update check keeps a
release's page only as an https address, where every version from 6.2.28 to 6.3.5 kept whatever the feed gave. The
sign-in card shows a single sign-on error carried in the page's address only in a tab that started a sign-in at the
identity provider in the last hour and has not loaded the Deck since, inside a sentence of its own, where 6.3.4 and
6.3.5 showed the text of any such address; the webmail's sign-in page is held to the same rule, where 6.3.4 and
6.3.5 showed any such text there too. A translation that is not text, or carries a character HTML reads, is left
out when the Deck loads a language, as the build already refused it, where 6.3.4 and 6.3.5 used the server's copy
of the catalogue as it was. And the ids and the counter three of its views take from the server are drawn as
numbers whatever the answer holds.

A file sent from the webmail as a link (/files/{token}) is now served under the type its sender declared only
when that type is on a list of types no browser runs, applies or renders as a page - pictures other than SVG, sound,
video, PDF, plain text and CSV, the office documents and the archives - and as application/octet-stream
otherwise, the files links already hold included; every version from 6.3.2 to 6.3.5 refused a few named types and
served any other as declared. A contact's photo is served under its card's type only when that is one of the same
list's pictures, and as application/octet-stream otherwise, where 6.3.4 and 6.3.5 served it under the type the
card declared. Found by the project's own review.

The size an account declares for a file sent as a link (since 6.3.2) or for a password-protected message (since
6.3.4) is now checked against the most either may be before it is made a whole number, so that every declared size
counts against that ceiling and against the account's own; found by the project's own review.

A read of another account's mail through the REST API by the server's administrator, a domain administrator or a
role holding view_mailbox, view_archive or view_discovery, and a support session opened as an account, are now
recorded on the audit trail; 6.3.4 and 6.3.5, the versions with the trail, recorded none of them (Administration,
below).

When the server declines to reach an address that is not public for somebody who named it - a list's one-click
unsubscribe and a correspondent's key from their domain's Web Key Directory in the webmail, the server of another
account a reader adds there, the delivery of an inbound webhook route an API key made - the reason it gives now names
the host as it was given and never the address the server resolved it to, and for another account's server says the
same whether the name does not resolve or leads inside the network. From 6.3.2 the unsubscribe's answer, and from
6.3.4 the others, named the private address a host led to. A refusal by HttpProxy, which these requests go
through from 6.3.6 (What changes on upgrade, item 20), is reported to such a person as the setting's, by its status
code alone, and neither by the proxy's address nor in the words of its answer. Found by the project's own review.

Expanding a recurring event now looks each occurrence up among the event's excluded dates (EXDATE) by halving a
sorted list, where it went through every one of them in turn. An event can carry tens of thousands of excluded
dates in the megabyte an invitation may have, and an invitation is filed in the calendar on delivery, so such an
event cost about a third of a second each time a calendar was read over a span it covers (the webmail, CalDAV, a
room's booking check) - and, before this release, several seconds of a delivery thread for a cancellation of some
of its occurrences (What changes on upgrade, item 18), which also asks of the event a few expansions fewer than it
did. Found by the project's own review.

The Linux live update's apply step, hmailserver-update, which runs as root on a request the server's service account
writes, took the unit it stops from that request, and for an AppImage request the image - the program that checks the
new file's signature - as well, in 6.3.4 and 6.3.5. A service account that had been taken over could therefore have
root run a program of its choosing, on a .deb or .rpm installation with the live update's path unit started (the
packages' default) as on an AppImage. 6.3.6 takes the unit and the image from the helper's own command line alone,
refuses an AppImage request where that names no image, checks after the stop that the server's unit is down and that
nothing of the service account still runs, and verifies, runs and installs only copies it has made after the stop,
which nobody else can read until they have verified (What changes on upgrade, item 24). Windows is not affected. An
AppImage run under the path unit keeps the script copied to /usr/lib/hmailserver/ when its update units were
installed, which the AppImage's own update does not replace: copy 6.3.6's there and give it --image. Until a Linux
server's helper is 6.3.6's, systemctl disable --now hmailserver-update.path turns the apply off, and the check and
the download go on; install 6.3.6 with the package manager. Found by the project's own review (CVE-2026-104658).

The same helper went on writing into the service account's Updates directory by name after it had started the server
again, in 6.3.4 and 6.3.5: it appended each line of its log to the log's file, gave the log and the outcome to the
service account with chown and chmod, which follow a symbolic link, and renamed both onto names through which a
symbolic link to a directory would have led them into that directory. A service account that had been taken over,
and was running again by then, could therefore have root append the log's lines to a file of its choosing, give that
file to the service account or change its mode, or put root's file into a directory of its choosing. 6.3.6 opens the
directory, and makes and opens both files, while the server is confirmed stopped; gives them to the service account
through the open files before it starts the server; writes them through the open files alone; and renames each into
place through the open directory, which replaces a symbolic link and never follows it. Windows is not affected. Found
by the project's own review (CVE-2026-104658, with the helper's first flaw above).

Some COM objects that carry no credential were not refused what only a signed-in caller may do. A message, its
attachments and a fetch account that a program creates for itself carry no credential, and neither do the objects an
Application handed out once a later Authenticate on it fails. With an administrator password set, a local Windows
user with no hMailServer credential could through them queue mail, read and write files as the service's account, and
attach a fetch account to any account. This is in every version up to 6.3.5, as in the original hMailServer. Outside
the server's own event scripts these are now refused, unless the program's own process holds an Application that
signed in, whose credential they then take; reading or writing a file on the server's disk through a message is the
server administrator's alone, and mail a signed-in account queues claims to be from only an address it may send as
(What changes on upgrade, item 26). Found by the project's own review (CVE-2026-104660).

An IMAP session that has turned compression on (COMPRESS=DEFLATE, RFC 4978) no longer has the server inflate
whatever one read of its compressed input expands to. Every version from 6.2.28, the release that brought the
extension, inflated all of it at once, and from 6.3.4 kept what did not fit the receive buffer and took each command
line off the front of it, copying the rest - and the full receive buffer - for every command. One read is at most 512
compressed octets (Boost.Asio's read into the emptied buffer the connection used), so what waited was at most about
half a megabyte, about what 6.3.6 itself lets wait, and the copying cost a few microseconds a command: at deflate's
highest ratio, 0.18 s for the 66,000 eight-octet commands of one read, beside what serving them costs (measured on
linix, 3 October 2026). The server now inflates no more than twice its receive buffer ahead of the commands it has
read, keeps the rest compressed until those are taken, and takes each line at a cost of its own length. Found by the
project's own review.

Outbound DANE (RFC 7672) used TLSA records of usage DANE-EE (3) alone. A recipient's MX host whose DNSSEC-validated
records were all of another kind - DANE-TA (2), the usual record for a certificate from an authority, or records this
server cannot use - was delivered to as if it published none: over TLS when it was offered, and in the clear when it
was not seen. The server now verifies DANE-TA as well as DANE-EE, through OpenSSL's own DANE support, and treats a
validated set none of whose records it can use as section 2.2 of the RFC asks: TLS is required to that host, a
delivery that cannot have it is deferred, and TLS reports count the host under the policy type tlsa. Such a set asks
for no authentication, so the host's certificate is not judged for it; it is still verified where something else asks
for that - an MTA-STS policy in enforce mode, a remote-domain policy or a rule, or this server's own outbound security,
SmtpDeliveryConnectionSecurity at SSL or STARTTLS (required), or a route's, with VerifyRemoteSslCertificate on
(What changes on upgrade, item 28). In every version up to 6.3.5; found by the project's own review and its second
review of the release (CVE-2026-104704).

Outbound DANE did not hold against anybody who could disturb a recipient domain's DNS: on the path between this
server and its resolver, a resolver that answered falsely, or on the path between the resolver and the domain's own
servers. A lookup of the MX or TLSA records that did not complete - no answer, SERVFAIL, a reply that would not
parse - and an answer from a signed zone that came without its signatures, or with no records and no proof that none
exist, were all read as "not signed", and the destination was delivered to as one that publishes no DANE: over TLS
with any certificate when it was offered, and in the clear when the offer was taken away. And the hosts a delivery
used came from the plain, unvalidated MX lookup even when the validated one named others. The validating resolver now
walks from the root to the name and reads an answer as unsigned only below a delegation a signed zone proves
unsigned; under a signed zone such a lookup is indeterminate and the host is not used, as RFC 7672 section 2.1.1
asks, a delivery with no host left is deferred, a validated MX RRset decides which hosts are used, and an empty
answer counts as no records only with the zone's NSEC or NSEC3 proof of it (What changes on upgrade, item 36). In
every version up to 6.3.5; left so, it would have undone the DANE-TA and unusable-record changes above as well.
Found by the project's second review of the release (CVE-2026-107584).

The ManageSieve service (off by default, and on the loopback when it is turned on) served one connection at a time,
and each read on a connection had a limit of its own with nothing bounding a command line, the TLS handshake after
STARTTLS or the time before authentication as a whole, so one client that had not signed in could hold the service
for as long as it kept its connection going, and a signed-in client sending NOOP every 29 seconds held it for ever.
Since 6.2.3, the release that brought ManageSieve. Every wait now runs to a deadline (What changes on upgrade, item
29); connections are served side by side, an address has one served at a time before it signs in, a connection that
runs out of time before signing in counts towards the auto-ban, and a signed-in session and an account are bounded
(item 37). Found by the project's own review and its second review of the release.

The URI blocklists (SURBL, URIBL, the Spamhaus DBL) were asked about the first 16 names a message's links gave, the
plain-text part's before the HTML part's, so that links ahead of a listed one could keep it from every list. The
SURBL test now reads every link of both parts and asks each list about every registrable domain they name, up to 200
for a message, up to sixteen lookups at a time within the list's ten seconds, so that links beside a listed domain
do not make it less likely to be asked about. A message whose links name more than 200 domains is not asked about
whole, and scores SurblTooManyDomainsScore instead, so that hiding a listed link among made-up domains does not
come free: 5 by default, the stock mark threshold and more than one list's stock score of 3 (What changes on
upgrade, item 30). A list that lists a host name and not its domain is asked about the host names there is room left
for, two drawn under a domain and in an order the links do not set, so made-up host names can still make a listed
one less likely to be asked about. In every version up to 6.3.5; found by the project's own review.

The REST listener's tables of sign-in state no longer let one person push other people out of them. A full table
of browser sessions dropped its least recently used session whoever held it, so any account holder signing in over
and over with their own password could sign out every webmail tab and every other browser on the server that sat
idle for a minute, from 6.2.28 to 6.3.5, and the administrator's Control Deck with them from 6.3.0. The pending
halves of a single sign-on and of a passkey sign-in, which anybody may start, were held the same way, so a client
asking for sign-in starts a few times a second - a hundred a second for passkeys - made every other person's OpenID
Connect, SAML or passkey sign-in fail when their browser came back, in 6.3.4 and 6.3.5. In 6.3.6 a holder of browser
sessions at its cap gives up its own oldest and a full table the oldest of whoever holds the most, among holders tied,
one of the domain holding the most: an account or the administrator holds at most fifty sessions, and the
administrator's are never taken for anybody else's sign-in (What changes on upgrade, item 32). The pending sign-ins are
no longer held on the server at all: the browser carries each one in its cookie, sealed with AES-256-GCM under a key
of the server process's, and only a finished sign-in is recorded, to make it good once, so no number of starts can
push anybody's out; a passkey registration's challenge, which only a signed-in credential asks for, is held by that
credential, fifty at most (item 33). Found by the project's own review and its second review of the
release (CVE-2026-107586 and CVE-2026-107585).

An account holding a role with manage_settings could put a key of the [Settings] section back to an earlier value
through the audit trail's put-back, in 6.3.4 and 6.3.5, where that section answers to the administrator password
alone everywhere else; and the breach check's endpoint test, asked by an API key or such an account, reached
addresses on the server's own network and reported whether and how fast something answered there, in 6.3.4 and
6.3.5. Both are now the administrator password's: the put-back refuses such a row to anybody else, and their test
reaches public addresses only (What changes on upgrade, item 34). manage_settings remains the server
administrator's equal - it names the hosts the server connects to and the commands it runs - and is documented so
(Roles.md, What manage_settings reaches). Found by the project's own review.

A webmail or Control Deck page loaded before the upgrade to 6.3.6, and still open after it, could have mail deleted
on the other server that its reader had asked to leave there: the restart ends the page's session and it signs in
again in place, with its old script, and 6.3.5's webmail sends -1 for Leave the mail there and its Deck's hint says
-1 keeps every message, where 6.3.6 reads -1 as delete each message there once it has been collected, for IMAP as for
POP3 (What changes on upgrade, item 22). The 6.3.6 webmail and Deck now say what they mean by the number
(days_to_keep_meaning, 6106), and a negative days_to_keep_messages from a browser session without it is refused
with 409 and nothing saved - on /api/v1/me/fetch-accounts, its import's start and the administrator's
/api/v1/accounts/{address}/fetch-accounts - so such a page says to reload. An API key, a password and COM, which send
the documented meaning, are not asked for it. Found by the project's second review of this release; no released
version reads -1 as 6.3.6 does.

The webmail kept the S/MIME certificate of a signed message it opened as a correspondent's certificate, to encrypt
replies with, whether or not the server found its chain trusted, under the first address the certificate named rather
than the message's From address, and beside any different certificate already kept for that address without asking.
Every later message the account encrypted to that address was then encrypted to such a certificate as well, so whoever
held its key could read it. This is in 6.3.2 to 6.3.5. In 6.3.6 a certificate is kept only when its chain is trusted,
only under the From address the signature was checked against, and a different one for an address that has one only
when the reader chooses Keep this certificate (What changes on upgrade, item 38). Certificates kept before the
upgrade stay where they are: the Security page lists each with its address and fingerprint, and Remove takes one
out (SmimeAuthority.md). Found by the project's own review (CVE-2026-107587).

The webmail's view of a message's HTML (GET /api/v1/me/messages/{id}/html) put the images the message embeds back in
place with work that grew with the square of the number of references to them, and wrote an image out again for every
reference while counting it against the inline budget once, so one message could hold one of the REST listener's four
workers for minutes and have it build a document of gigabytes; with a few of them the webmail, the Control Deck and
the REST API stopped answering anybody. This is in 6.3.2 to 6.3.5. In 6.3.6 the route reads the HTML twice, once for
the images it names and once as it writes them, in a time that grows with the HTML's size alone, and writes 24 MB of
images at most in all, every reference counted (What changes on upgrade, item 39). Found by the project's own review
(CVE-2026-107583).

The webmail inflated a compressed OpenPGP message in the reader's browser tab with no limit on its size, so a small
message could take the tab's memory each time it was opened, in 6.3.4 and 6.3.5; the server was not affected. In 6.3.6
each compressed layer is inflated to 32 MB at most and a message past that is refused with a sentence that says so
(What changes on upgrade, item 40). Found by the project's own review.

Where a message has an HTML part and no text part, the server reads its text with the tags dropped and the entities
decoded: for the snippet the webmail's message list shows, for an IMAP client's PREVIEW, and for the page of a
message that expires. The decoding replaced each entity in place and moved the whole rest of the text along every
time, so its time grew with the square of their number, and one message of a few megabytes of entities held the REST
worker that listed the folder for minutes - no click needed, since the list is the first thing the webmail asks for -
and held an IMAP thread the same way; with a few of them the webmail, the Control Deck and the REST API stopped
answering anybody. This is in the message list from 6.3.3, the page of a message that expires from 6.3.4 and PREVIEW
from 6.2.22, up to 6.3.5. In 6.3.6 the server's string replacement, which these and the rest of the server use, writes
a result of another length in one pass, and its removal of a character takes one pass too; what they make is as it
was. Found by the project's own review (CVE-2026-107582).

An IP range's, an incoming relay's or a white-list entry's address set over COM - the Control Panel's IP ranges,
incoming relays and white list pages, or a script - that the server could not read as an address was stored as
0.0.0.0 without a word, in every version up to 6.3.5: a range whose lower address was mistyped began at 0.0.0.0,
with whatever it allows - relaying, delivery without authentication, no spam tests - and a relay or a white-list
entry so written covered every address below its upper one. 6.3.6 refuses such an address with the reason, and the
object keeps the address it had, as the REST API answers one with a 400. On Windows it matters more in this
release, which reads more texts as no address there (What changes on upgrade, item 42): 192.168.001.001, which
Windows read as 192.168.1.1, would otherwise have become 0.0.0.0. Found by the project's own review.

Parsing one IMAP FETCH command from a signed-in account could hold an IMAP worker thread - shared with SMTP and
POP3 - for minutes, and far longer. Reading the sections of a BODY[] item ran the server's case-ignoring string
search and replacement, which copied and lower-cased the whole item once for each occurrence, so the time grew with
the square of the number of BODY.PEEK[ in the item; and splitting the item list cut each item off the front of the
rest by copying the rest every time, so it grew with the square of the number of items. One command line carries
about a quarter of a megabyte - some twenty thousand BODY.PEEK[, or over a hundred thousand items - which is already
seconds of one thread, and a FETCH is not held to one line: the text after each literal on its line joins the
command, which can so reach the eleven megabytes the command buffer takes, where the time grows by the square (40,000
occurrences took three minutes, 200,000 items two and a half). A few such commands from a few sessions stopped the
IMAP, SMTP and POP3 ports answering anybody. This is in every version from 6.0.0 up to 6.3.5. In 6.3.6 the
case-ignoring search, replace and contains take one pass, in a time that grows with the string's length alone however
long what is looked for (a SEARCH TEXT key sent as a literal of a megabyte, over a message of a few in the client's
own mailbox, was compared afresh at every position); the FETCH item parser walks the command by offsets and finds
each space once; and the other parsers of a client's text that had the same shape were made to take one pass as well:
the message set of FETCH, STORE, COPY, MOVE and UID EXPUNGE and its session's UID lookups, the THREAD and
SORT base subject, a STORE's keyword list, the per-line scan of the command buffer that masks a password in the
log, the sets of SEARCH's UID and sequence-set keys and of UID FETCH's VANISHED, read once per command rather
than once per message, and a HEADER.FIELDS list, read once per command rather than once per message and name, each
section then reading the message's header once, field by field; and a FETCH's part numbers, each part list read and
each encapsulated message parsed once for all of a message's sections, where each section listed the parts from the
first to the one it named and parsed a forwarded message afresh on its way to a part in it.
What each produces is as it was. Commands that named something over and over now cost what naming it once costs: $
(the saved search result) in a message set or as a SEARCH key stands in once, where each $ copied the saved
result; a SORT criterion named again is left out, where each made a key for every message; and a FETCH whose
sections of one message add up to more than sixteen times the message is answered NO [LIMIT], where all of them
were read and held before any was sent - each section counted by what it reads or makes, not only by what it keeps:
BODY.PEEK[HEADER]<0.1> read the message whole for its one byte, and HEADER.FIELDS, HEADER.FIELDS.NOT, MIME,
TEXT and a part's content cut short read or made theirs whole, as often as a command named them. A $ written into
an element with anything else - 1:$ - is answered BAD, as no set. Found by the project's own review (CVE-2026-107581).

The commands an administrator runs no longer go where the service's account can lead them. Since 6.3.4 a new Windows
installation runs the service as NT SERVICE\hMailServer, which has Modify on the program
folder's Logs, Data, Temp and Database folders, and the commands Setup, DBSetup and the Control Panel run
elevated - /GrantServiceAccess, /Register (on a registration that fails), --apply-new-install-defaults,
--apply-profile and --create-stored-secrets-key - and those an administrator runs from an elevated prompt -
--upgrade-database, the stored-secrets and store commands and the rest - wrote their errors to the error log in
Logs, and armed the crash oracle, whose record and dump go there too. --apply-new-install-defaults wrote the
self-signed certificate and its private key, and set the key's permissions, in Data\Certificates by path, and the
store commands that rewrite, remove or add messages (--compress-store, --decompress, --encrypt-store,
--decrypt-store, --recount-store) and the copies to and from an object store worked by path in Data, a copy
to the store handing what it read to a store the service's account reads. A program running as the service's
account - one that had already taken it over, through some other flaw - could put a junction or a mount point where
such a write was going and have an administrator's process create, overwrite, delete or change the permissions of
a file it chose, or read one, and so act with the administrator's rights. In 6.3.6
every command line but the service's own (RunAsService) and the two a developer runs (/Debug, /Test) writes
nothing to the error log, the Application log or an event script, has no crash oracle, and, where Windows has it
(Windows 11 22H2, Windows Server 2025), follows no junction a user who is not an administrator made; each command
says what it did and why it failed on its own output, which Setup, DBSetup and the Control Panel keep in their logs.
The certificate and its key are made below the Certificates folder held open from the drive's root down - no folder
of it a link - and each file is checked to be in it before a byte is written; and every store command, --measure-store
included, and every copy to or from an object store, first walks the data folder and stops, naming it, on a junction,
a mount point or a symbolic link in it that the administrators do not alone control. A link they alone control - owned
by BUILTIN\Administrators, SYSTEM or TrustedInstaller, and changeable or removable by no one else - is gone
through, and the path it holds is held from that volume's own root, every link on it - the folder it leads to
itself included - asked about in its turn, and checked to be where the link led; so a data folder, a folder above it, or a
domain's folder kept on another drive through a junction an administrator made and gave permissions only the
administrators may change is served, while one the service's account could have made or could turn elsewhere - a
link made in the data folder inherits the service's write rights until it is given others (docs/ServiceAccount.md) -
is refused with the reason. A folder no administrator may list, such as System Volume Information at a drive's
root, is skipped when the administrators alone control it rather than refused. --measure-store, which only reads and
reports to the administrator, is held to the same rule because it reads every file with an administrator's rights.
The store commands also reached each file by the name the database gives it, which the service's account may write:
a message row naming a file outside the data folder - a full path, or a part that is .. - had an administrator's
--compress-store or --encrypt-store rewrite that file, or --copy-store-from-object-storage create it, and a held
part's name in hm_storeblobs could send a recount's removal anywhere. Now such a row stops every store command,
naming the message, before it reads or changes a file; an object store's key whose name would lead out of the
folder is not listed; and a held part's name is its SHA-256 in hex or names no file. A full path below the folder a
junction of the data folder leads to is in it; a legacy full path elsewhere is moved with the Data Directory
Synchronizer, which the message names.
On Linux the same commands run as root were confined to the data folder by path but followed a symbolic link the
service's account (the unit's User=, which owns the data and log folders) planted in it - sudo hmailserver --compress-store
would then read, rewrite or remove whatever the link led to, as root. The store commands, the object-store copies,
--sample-domain, the profiles and --apply-environment now drop to the service account - the data directory's owner,
with its groups - before they open anything when run as root, so each reaches only what the service reaches through any
link and leaves no file the service cannot open; a data directory whose way is not root's and its owner's alone - a
folder above it, or above where a link on the way points, that another account or group may write - is refused, exit
2, since a link put there could choose which owner, and so which account, that is; over a data directory root owns - a
service run as root - they run on as root only where that directory and everything under it, the log, temporary, event
and database folders and everything under them, the configuration that names them, and the ways to them are root's
alone, no link in them another account's and no default access control list giving another account a write, and make
what they make with every other account's write taken away whatever the umask, and are refused, naming the folder,
file or link, otherwise; and every other command line run
as root writes nothing to the log directory (its report goes to standard error instead), as the Windows commands do.
--create-database makes its .new-installation marker without following a link of that name.
build/check-elevated-verbs.py holds all of it. The Windows commands were affected in 6.3.4 and 6.3.5, and the Linux
root store commands' symlink following was too; it needs a program already running as the service's account and an
administrator who then runs one of them (CVE-2026-107578). /GrantServiceAccess also no longer leaves a Critical
HM5028, "Check your database settings in hMailServer.ini", in the Application log when the administrator running it
cannot open a SQL Server database reached with Windows authentication: the installer smoke VM showed one on every
such move on 3 October 2026. Found by the review of the installer's fixes and the sweep after it.
ServiceAccount.md, "What an administrator's command writes".

Every user of a Windows computer that runs hMailServer could read its mail. The installer makes the Data, Logs,
Temp, Database and Events folders in the program folder, C:\Program Files\hMailServer by default, and left
them and Bin\hMailServer.INI with the permissions Program Files hands down, which give BUILTIN\Users Read. Any
account that can sign in to the computer - a Remote Desktop server's users, a shared machine's, a service running as
an ordinary account - could read every message; the logs, with their addresses, subjects and SMTP, IMAP and POP3
conversations; the built-in database, with the accounts' password hashes and the stored secrets' sealed values,
whenever the service that keeps its file open is stopped; a key file an administrator put in Data\Certificates; the
event scripts; and hMailServer.INI, with the database's password - sealed with the machine's DPAPI key, which any
account on the machine may use to unseal it - and the administrator password's hash and second factor. A data folder
an administrator moved under a folder at a drive's root, behind a junction, took that folder's permissions instead
once anything rewrote its own: on Windows Server, Users' Read and the right to make files and folders in it; on a
Windows client, Authenticated Users' Modify. Every release of this project, 6.0.0 to 6.3.5, installs so, as the
original hMailServer's installer did. 6.3.6's installer makes the six private to SYSTEM, the Administrators and the
service at every installation and upgrade, whatever account the service runs as (What changes on upgrade, item 45).
On Linux the AppImage made its folders under the user's umask, 0755 under the usual 022, in a home other users may
enter on many distributions; it now makes them 0700. The packages' folders were 0750 and their configuration 0640
already. Found by the project's own review (CVE-2026-107573). ServiceAccount.md,
"What the other users of the computer may read".

With bounce processing or complaint processing on, or a mailing list the list manager runs (none is by default),
reading an incoming delivery status notification (RFC 3464) or abuse feedback report (RFC 5965) could hold a delivery
thread for a minute and more. The reader of the returned message's headers part dropped the blank lines some senders
leave at its front two bytes at a time, copying the rest of the part on each, so the time grew with the square of
their number: 400,000 of them took eleven seconds, and the reader's 2 MB cap holds a million. A message shaped like a
bounce to a local address or to a list's -bounces address, or like a feedback report to the complaint mailbox, is
read this way without anyone opening it. The blank lines are now counted and cut off once. This is in every version
that reads these reports, 6.3.4 and 6.3.5. What is read is as it was. Found by the project's own review (CVE-2026-107579).

A header field read as text - the Subject and From of the webmail's message list, the header keys of IMAP SEARCH,
SORT and THREAD, a rule that tests a header, the spam tag put in front of a Subject, the abuse report reader -
was unfolded by finding each line break from the end of the value and moving everything after it, so the time grew
with the square of the number of line breaks, which are the sender's: an RFC 2047 encoded word may decode to as many
as it likes. A value of 1.6 MB of short lines took half a minute, each time it was read, and the default largest
message holds over ten times as many, so a stranger's message held the REST worker that listed its folder, or the IMAP
thread that searched, sorted or threaded it, for minutes and more, and a few such messages stopped the webmail, or the
IMAP, SMTP and POP3 ports, answering anybody. This is in every version from 6.0.0 up to 6.3.5. In 6.3.6 the value is
unfolded in one pass, to the same result. The reading of a message's header from its file had the same shape at a
larger scale: after each 4,000 bytes it searched all it had read for the header's end, some five seconds for a header
of 20 MB, the default largest message, at each of the several reads of a message; it now searches only what each read
adds. Found by the project's own review (CVE-2026-107580).

A review of the whole server for the same shape - input a remote party controls, read in time quadratic in its size -
found more of it, all fixed in one pass with the result unchanged, each proven against the previous code over generated
input and held to a single pass's time (InputCostTester, the Windows ClassTester and the Linux input-cost-tests).
Removing a MIME header parameter whose value is empty and directly followed by a ; - a Content-Disposition such as
filename=a.bat; filename=; - did not terminate at all: it found the ; after the empty value as the parameter's
own start, removed nothing and found the same parameter again, holding a worker thread at full load until the service
was restarted. It is reached when the attachment blocker renames a blocked attachment (not on by default) or a filename
is set over the REST API, from one received message; ten such messages could hold all ten default delivery threads for
good. The parameter's start is now taken as the last ; before its value. In every version from 6.0.0. The same
removal was also quadratic in a Content-Disposition of many filename*<n> RFC 2231 continuations, now one pass.
Decoding a header field holding many RFC 2047 encoded words of an encoding other than base64 or quoted-printable
searched each to the end of the value, though such a word is copied as text and the search's result is never used
(a 1.1 MB header took 101 seconds); it is now searched only for a word that is decoded. Deleting a header field by
name - the spam classification removed on delivery, the list manager stripping a posting's fields, the external-sender
and impersonation taggers - took the fields one at a time, each a scan of the header and a move of its tail, so a header
of many fields of one name cost their square (80,000 took 18.6 seconds); they are removed in one pass. All in every
version from 6.0.0 up to 6.3.5 (CVE-2026-107577).

Verifying a received message's DKIM or ARC signatures built the canonical header and chose the fields the signature's
h= names in time growing with the square of the message's header: the "simple" canonicalisation put each continuation
line of a folded field in front of the ones already gathered, and both canonicalisations searched the gathered fields
from the bottom for each h= name. A message whose header holds very many fields, or a field folded over very many
lines, with a signature the sender signs for a domain they own so it reaches verification, held a worker thread for
tens of seconds, and up to ten signatures are verified per message by each of the DKIM and DMARC tests, on the threads
that serve delivery and SMTP. Each field is now built once and each h= name found in an index by name; what is hashed
is unchanged byte for byte. In every version from 6.0.0 up to 6.3.5 (CVE-2026-107576). Expanding an SPF mechanism's domain-spec cut a name
too long to look up (RFC 7208 7.1) one label at a time, copying the rest each time, so a record the sender publishes
with a macro that expands to a very long name made the SPF check take several seconds (a domain-spec of 2,000 %{s}
took 4.9 seconds); the cut is now made once. In 6.3.4 and 6.3.5, where that SPF implementation arrived (CVE-2026-107575). The JSON reader kept the first of each member name by searching the members already kept, so an object of
many members cost their square (64,000 took 2.4 seconds); it reads the REST API's bodies and, where domain reports are
on, a TLS report (RFC 8460) mailed in by anyone, of up to 16 MB, on the delivery queue, where a 4 MB report took
about 300 seconds - a few such reports stopped all delivery for over an hour each. It now keeps a hash set. Since
6.2.28 (CVE-2026-107574). Two more of the shape are fixed that need the attacker's own account or script rather than a
stranger's message: a Sieve :matches test backtracked over every length of every * (*a*a*a*b over 800 characters took
44 seconds) and deleteheader erased its chosen fields one at a time, both over a sender's header but driven by the
account holder's own filter script, on the delivery threads every account shares (since 6.2.24; CVE-2026-107572); and
an IMAP LIST de-duplicated its result lines by searching a list
for each (40,000 folders took 1.4 seconds a command), over the signed-in account's own folders (since 6.2.0). The Sieve match is now linear in the value times the
pattern, and LIST keeps a hash set. The calendar's handling of an organiser's CANCEL of a series from an occurrence on
took the stored event's RDATE values, rule parts, overrides and the server's own marks out one at a time, each a copy
or a move of the rest; a stranger's invitation is filed on delivery, so its RDATE can be as long as the 1 MB a message
is read for a calendar within allows, some 60,000 dates, in time growing with the square of their number (200,000 dates,
more than that limit allows, took 21.6 seconds). Each is now one pass. The RDATE, rule-part, override and mark loops
of the calendar are batch42's own and were never released; the METHOD and attendee SENT-BY loops beside them are in
6.3.4 and 6.3.5. All found by the project's own review.

The same review, checked again, found the rest of that shape. A date of a calendar object in a time zone the object
carries was placed by looking for the zone among the object's components and building it from its observances - and an
observance's own RDATE list was read to its end though only 500 of its values are kept - once for every date placed:
once for each RDATE value and each override's RECURRENCE-ID an organiser's CANCEL reads, and once for each
occurrence it names. Under the 1 MB limit, 20,000 dates and 1,500 overrides in a zone of 25,000 transitions took 20
seconds, and a CANCEL of 1,990 occurrences of a series in a zone of 60,000 transitions 4.2 seconds. Each zone is now
built once for all the dates of one reading (ICalTimeReader) and its RDATE list read no further than the 500 values
kept; what each date is placed at is unchanged, and the same two readings take 0.1 seconds. Those CANCEL paths are
batch42's own and were never released. A room's or equipment's booking, in 6.3.4 and 6.3.5, took a CANCEL of many
occurrences one occurrence at a time, reading the RECURRENCE-ID of every override the booking holds, its zone built
afresh, for each occurrence, and again for each to compare their SEQUENCEs; each override is now read once, with its
zone built once, and the overrides named removed in one pass, with the same result - the booking's own organiser's
CANCEL, so a lesser matter. The list manager removed a posting's own List-*, Precedence,
X-hMailServer-List-Member, Return-Path and DKIM-Signature fields one at a time and stopped after 64 of a name, so
that many could not cost their square: a posting that brought a 65th kept it in every member's copy, beside the list's
own field and under the list's signature (6.3.4 and 6.3.5). Every one is now removed, in one pass. Found by the
project's own review.

What changes on upgrade without a switch

  1. COM Utilities: the helpers that do work answer a caller that has signed in. BlowfishEncrypt,
    BlowfishDecrypt, ResolveMXRecords, GetMailServer, IsLocalHost, CheckSieveSyntax, EvaluateSieveScript
    and CriteriaMatch refuse a COM client that has not called Authenticate, with "You do not have access to this
    property / method", whether it reached them through Application.Utilities or created hMailServer.Utilities
    itself. They answer, as before, an account of any level or the administrator that has signed in, and an event
    script running in the service, which needs no change. MD5, SHA256, GenerateGUID, IsStrongPassword,
    IsValidEmailAddress, IsValidIPAddress and IsValidDomainName stay open to any caller, and a server with no
    administrator password and no second factor answers every caller as it did. A program or script outside the
    service - a scheduled VBScript, a PowerShell script, a tool of your own - that calls one of the eight creates
    hMailServer.Application, calls Authenticate with an account or the administrator, and uses that object's
    Utilities: a Utilities created on its own has no way to sign in. The Control Panel and the shipped
    DecryptBlowfish.vbs already sign in. In Utilities the type library changes help text only; no member or
    dispid of it is added, removed or moved.

  2. The live update's trust is read from hMailServer.ini alone. UpdateSigningIdentity,
    UpdateSigningIssuer, UpdateSourceRepository, UpdateTrustRootsFile and UpdateLogPublicKeyFile are read
    from the file's [Settings] section on every platform, and never from the database, where 6.3.4 and 6.3.5 kept
    them. At the first start a value those versions stored in the database is taken out; where it said something the
    file does not, the error log names it once as HM5809, with both values, and from then on the file's value
    applies. A line for one of the five in [SettingsOverride] is ignored and named as HM5809 at every start. COM
    and the REST API (409) refuse a change to them, and so the Control Panel, the Deck, a settings profile and the
    audit trail's put-back; a write of the value the file already holds is taken. Most servers set none of the five
    and see nothing. One that set a signer of its own over REST, the Deck or the Control Panel sets it in the file:
    on Windows with an editor run as an administrator, on Linux with sudo hmailserver --set-setting NAME=VALUE,
    then restarts the service. Where several servers share a database, the first 6.3.6 to start takes the rows out
    for all of them, so put the value in every server's file before the first is upgraded. hmctl config export and
    Export-HmConfiguration leave the five out, and diff and apply of an earlier export skip them with a line
    naming them; a backup does not carry them either (UpdateFeed.md, "Where the
    trust is read from").

  3. The MX-record test falls back by the Public Suffix List. When a sender's domain has no MX, the test judges it
    by its registrable domain's, which the server now finds with the Public Suffix List it already compiles in for
    DMARC (the list of 25 July 2026, its PRIVATE section included), where it cut the name to two labels, or three
    under a name in SURBL's list of 2006: mail.example.act.edu.au falls back to example.act.edu.au, not the
    suffix act.edu.au. A sender at a public suffix itself that has no MX - github.io, co.uk - now fails the
    test, since there is no registrant's domain above it to fall back to; under the old list github.io failed and
    co.uk passed. A name of one label is not judged, as before. Nothing to set.

  4. The SURBL test reads a link as a browser does, and a list's error answer is never a listing. On a server
    with URI block lists (SURBL servers):

    • A link's host is the one after its user name - http://anything@evil.example/ is asked about as
      evil.example, where it was anything - percent-decoded as UTF-8 and without its port, with a soft hyphen,
      a zero-width space and the other characters a browser deletes from a host deleted, and an IPv4 address in any
      form a browser takes is that address, an IPv4-mapped IPv6 address ([::ffff:192.0.2.1]) among them. A link
      with no scheme is read where a browser or a redirector follows one: an HTML attribute's value that begins
      with two slashes (href="//evil.example/"), and a query parameter's (?u=//evil.example). The host is asked
      about whole and cut to its registrable domain by the Public Suffix List, as above, and a host under a
      shared-hosting suffix also as the domain the list's ICANN section registers (github.io for
      tenant.github.io), as 6.3.5 asked. Messages whose links escaped every list in any of these ways can now be
      tagged.
    • A link to an address is asked about reversed, as a DNS list expects, and only so: it was also asked about in
      its own order.
    • The lists' error codes - 127.255.255.x, 127.0.0.1, 127.0.1.255 and 127.0.2.255 - never tag a message,
      whatever the expected result. A server that asked the Spamhaus DBL or ZRD with no expected result, or with
      127.0.1.* or 127.0.2.*, no longer tags every message holding a link to an address. A URI list of your own
      that answers 127.0.0.1 for a listing no longer tags anything: have it answer another address, such as
      127.0.0.2. A list that refuses the server's queries, or answers 127.0.0.1 to every name of a message, is
      named in the application log at most every 15 minutes.
    • A host DNS cannot carry - a link that ends in a full stop, a label of 64 characters - is not asked about, where
      its failed lookup ended the list's check for the message, and the links are asked about in the order they come
      in the message, not sorted.
    • The sender lists' URL entries are matched against the same host, an address in its own order: an entry for the
      name before an @ no longer matches, and nor does an address entry written reversed (1.2.0.192 for a link to
      192.0.2.1). Write such an entry as the address itself. They are matched against every link of a message
      within the anti-spam size limit (item 21), where only the first 16 hosts were, so a message with an entry's link
      after sixteen others is now held or marked as the entry says.
  5. Backups leave out the live update's folder, and a destination inside the data directory works. A backup no
    longer carries Updates - the installer or package waiting to be applied, the rollback image, the apply token
    and the last apply's outcome, most of the size of a backup taken before an apply - and a restore leaves this
    machine's own folder as it is and puts back no older archive's copy. A destination, the temp directory or the
    run's staging folder inside the data directory is left out of the copy, recognised by the directory itself and
    not by its spelling, and a restore leaves them, and the folder of the archive it restores from, where they are;
    the backup log names each. Up to 6.3.5 such a destination failed every backup (HM5014), and on Linux every
    unattended update with it; the nested copy a failed run left in the staging folder is now removed rather than
    archived. The same change removes two faults of the Linux packages, whose temp directory is inside the data
    directory, found by reading the code: an encrypted backup with messages failed the same way, and a restore of
    messages deleted the store it had just extracted. Nothing to set; a destination on another disk is still the
    better place (Backup.md, "A destination inside the data directory").

  6. tlds.txt is removed. The Windows installer deletes Bin\tlds.txt, and the Linux packages no longer carry
    /usr/bin/tlds.txt, so apt and dnf remove it. Nothing reads it: a copy edited by hand has no effect any more,
    and the Public Suffix List compiled in decides. Error 4335, the list not read, is retired.

  7. The project's address. The project is at gitlab.com/hmailserver/hmailserver since 29 September 2026, with
    its id, and the old addresses redirect. The Control Panel's links, the policy link of the server's
    /.well-known/security.txt, the systemd units' Documentation= and the packages' home page name it. A new
    database gets HelpWikiBaseUrl https://gitlab.com/hmailserver/hmailserver/-/wikis/; a database set up before
    keeps the value it has, as it keeps any setting - https://gitlab.com/Progressiverobot/hmailserver/-/wikis/
    unless an administrator changed it, which GitLab redirects, so the help panes' Open on the wiki still works. It
    is changed on the Control Panel's Advanced page under General, on the Deck's Settings page, as
    help_wiki_base_url over REST or as Settings.HelpWikiBaseUrl over COM (Help.md,
    "The wiki link"). This release and every one after it are signed at the group's path (above).

  8. The live update keeps its outcome, tries a failed check again once, and keeps a verified installer.

    • After an update, Status.UpdateApplyOutcome, apply in GET /api/v1/update and the Control Panel keep the
      outcome until the service stops or the next outcome replaces it; on 6.3.4 and 6.3.5 the check a second after
      the start erased it. The update that installs 6.3.6 is the first whose outcome stays.
    • A scheduled check that fails, when the one before it did not, is due again ten minutes later and read at the
      quarter-hourly task's next run, then every UpdateCheckHours as before; GET /api/v1/update gains
      nextCheck, when the scheduled check is next due.
    • An answer that ends before its headers is reported with who ended the connection, after how many bytes and
      seconds, the TLS handshake's share and how the read ended, where it said "The response carried no HTTP
      headers.", for every request of the server's HTTP client but the installer's and the bundle's downloads. A
      monitor that matches the old sentence needs the new one.
    • With UpdateAutoDownload=1, an installer and bundle already in the Updates folder are verified again where
      they are and not fetched again, and a copy that no longer passes is deleted and fetched; a service that cannot
      install an update itself (NT SERVICE\hMailServer, HM7463) no longer downloads the whole installer, 88 MB
      for 6.3.5, at every start (UpdateFeed.md).
  9. On Linux the backup and event logs are UTF-8, as the application and error logs are; they were UTF-16LE,
    which grep and tail read as every character followed by a NUL. The server converts hmailserver_backup.log
    and hmailserver_events.log in place the first time it opens each; the copies logrotate made before stay as they
    were. Windows keeps UTF-16LE.

  10. The Linux packages. The .deb and the .rpm require ca-certificates, which apt and dnf install where it
    is missing. Every Linux install puts hMailServer's AGPL-3.0 text at
    /usr/share/hmailserver/licenses/AGPL-3.0.txt beside QuickJS.txt, and a copyright file at
    /usr/share/doc/hmailserver/copyright from the .deb or /usr/share/licenses/hmailserver/copyright from the
    .rpm. The .rpm's License names every licence of what is compiled in, where it named the AGPL alone, and
    its %changelog is this release's entry, where it was CPack's placeholder of 2010. The update helper now runs
    apt-get quietly, so the journal no longer holds dpkg's progress as "[614B blob data]"; the helper already
    installed is the one that runs an update, so this shows from the update after 6.3.6.

  11. Text in the Control Panel can be selected and copied (work item 48): on every page and dialogue by dragging,
    double-clicking, Ctrl+C or a right-click Copy, and every plain list copies its selected rows, one row per line.
    The words on the controls - a check box's or a button's caption, a drop-down's value, a tab's or a page's name,
    a label - are copied whole from their own right-click Copy or Ctrl+C; a drop-down's right-click menu, which
    offered Copy, Cut and Paste and could run none of them, now offers Copy. Live logs selects several lines,
    follows new lines only while its end is in view, and no longer splits a line the server is still writing into
    two rows. Keyboard navigation and screen-reader output are unchanged.

  12. Scripting off runs no script engine. With scripting off the server no longer compiles the event script at
    every start, reinitialise and reload; switching scripting on compiles it first, and a script that does not
    compile is reported then rather than at each start while scripting is off.

  13. Install update under NT SERVICE\hMailServer, and what a script sees of it. Where the service is not an
    administrator, the Control Panel's Install update installs (above), and:

    • Status.InstallUpdate returns false and leaves UpdateState at 3, with UpdateLastError saying who
      installs the update and where; it made the state 5, a failure, which took Install update away and
      offered the download again. POST /api/v1/update/install answers 409 with code
      update_install_by_administrator and the same sentence, and the state stays 3. On LocalSystem both are
      as they were.
    • UpdateState has two new values, 6 while the service prepares an administrator's install and 7 when it is
      ready, and COM Status six new members, UpdateNeedsAdministrator, UpdateInstallAdvice,
      PrepareUpdateForAdministrator, UpdatePreparation, BeginUpdateForAdministrator and
      CancelUpdateForAdministrator (dispids 20 to 25; nothing existing moves). The three that act answer only
      the server's administrator, and only from this computer. GET /api/v1/update adds installedBy
      (service, administrator or elsewhere), installAdvice, serviceAccount, serverName and
      preparation, and the Deck's Update page shows the sentence where its Install button stood.
    • The checklist's update_apply_by_administrator ends "installed by an administrator with Install update in
      the Control Panel on this server", and HM7463 says that an administrator installs it with Install update in
      the Control Panel on this server, where they said an administrator installs them.
      HM7465 to HM7469 are new, and only an administrator's install raises them; its outcome is an
      application-log line, HM5480 when it did not succeed, and an audit event of object type update under
      the Windows account that approved it, interface Windows
      (AlertsAndAuditTrail.md).
    • The program folder gains AdminUpdate, made by the first administrator's install, holding each install's
      copies, log and outcome, which only administrators can change; anyone may read the folder's own
      permissions, which the Control Panel checks before every later install's prompt. The uninstaller removes it.
      hMailServer.exe loads the libraries it links from Bin and System32 only.
  14. Smaller things a script, a monitor or a log reader may notice.

    • GET /api/v1/update also answers installKind, liveUpdate, updateCommand and updateAdvice, which the
      Control Deck shows (LiveUpdateKinds.md).
    • The REST API writes to the application log which method and path it answered 500 after an exception, and the
      crash oracle's record adds, after the fields it always had, what an access violation read, wrote or executed,
      the image and offset of the fault and up to twelve frames of the faulting thread.
    • Putting a switch back from the audit trail (POST /api/v1/audit/{id}/revert, Utilities.RevertAuditedChange,
      Put back) sets the switch; on 6.3.4 and 6.3.5 it wrote the setting's text column, which the server does not
      read, and changed nothing.
    • Over COM, Recipients.Item and DiagnosticResults.Item refuse a negative index, a look-up that finds nothing
      no longer leaves an object behind, and a date made from a NaN reads as no date.
    • HM4229 reports the same codes in new words, and HM7277 names LegacyBlowfish::EncryptToString as its
      source.
    • A TLS scanner sees three things change (Operations, every TLS context switched on every OpenSSL option from
      bit 32 up
      ): the Windows server's outgoing TLS 1.3 handshakes no longer carry an Encrypted Client Hello GREASE
      extension; TLS 1.2 handshakes in both directions offer only the uncompressed elliptic-curve point format, on
      Windows and wherever the Linux server's OpenSSL is 3.6 or later; and on Linux with OpenSSL 3.2 or later the
      MTA-STS policy fetch and the OAuth2 token request no longer offer certificate compression.
    • The error log says six failures it did not (Operations, six failures that stopped a pass or a step), two of
      them with new codes, HM7554 and HM7555.
    • A device's count of sign-ins (sign_ins) stops at 2,147,483,647, the most its column holds; the next sign-in
      would have made it negative.
    • A file sent as a link whose declared type is not on the list under Security goes out as
      application/octet-stream, which a browser saves under the file's own name, and its record's type in
      /api/v1/me/files says so, for a link recorded by an earlier version too; one declared text/plain with a
      charset, which went out as application/octet-stream, goes out as text/plain. A contact's photo whose card
      declares a type that is not one of the list's pictures goes out as application/octet-stream, and the webmail
      no longer shows a photo in SVG.
  15. Event scripts on Linux run in QuickJS 2026-06-04, Fabrice Bellard's release, in place of 2025-04-26. It fixes
    memory-safety flaws in the engine that a script can reach: CVE-2025-62494 (a type confusion when a string is
    added to a value whose conversion changes it, High), CVE-2025-62493 (a BigInt written as a string, Medium) and
    CVE-2025-62492 (indexOf and lastIndexOf of a typed array with a tiny negative start, Medium). It fixes two
    more, both High, that a script could not reach in the server, because each needs more memory than the 64 MiB
    ceiling allows it: CVE-2025-62495 (an integer overflow compiling a regular expression of over 2 GB) and
    CVE-2025-62496 (a BigInt made from a string of some 67 million digits or more). Each needs the script to do the
    thing; message text a script only reads and compares does not. Two advisories a scanner may list against QuickJS
    do not apply: CVE-2025-12745 (ArrayBuffer.prototype.slice and a buffer resized under it) is in the resizable
    ArrayBuffers that 2026-06-04 adds, and fixed there, and 2025-04-26 had none; CVE-2026-88378 (rated Critical,
    published on 24 September 2026 and fixed in no release yet) is in the reader of compiled bytecode, which the
    server never calls, and the VEX document says so. The release adds newer language features -
    iterator helpers, the Set methods, RegExp's v flag, modifiers and RegExp.escape, Float16Array, Promise.try
    and Error.isError among them - and runs scripts faster (42% on bench-v8 over the release before it, by its
    author's measure). The objects the server gives a script, the time limit and the memory ceiling are unchanged.
    Windows' VBScript and JScript are not affected.

  16. Three more web requests go through HttpProxy, the MTA-STS policy fetch reaches only public addresses, and on
    Windows the listeners keep their ports to themselves.
    The OAuth2 token request (OutboundOAuth2TokenUrl), the
    MTA-STS policy fetch and the ACME client now use the server's one HTTPS client (Operations, on Linux, some of
    the server's network waits had no timeout
    ), so when HttpProxy is set they go through it, as the update check,
    the JWKS fetch and the server's other web requests do; they went direct before. The policy fetch, whose host the
    recipient's domain names, is made only to a public address, judged by the addresses mta-sts.<domain> resolves
    to: a policy host at a loopback, private, link-local or otherwise reserved address is not asked, and the domain
    is treated as one with no policy, as RFC 8461 treats a policy host that cannot be reached. Through HttpProxy
    the proxy is asked for a tunnel to one of those addresses and not to the name, so that what it reaches is what
    was judged; a proxy that allows a tunnel only to a name refuses that, with the same outcome. The policy host is
    taken as a host name and nothing more, so a domain cannot name a port or a path for it. With it a port in
    OutboundOAuth2TokenUrl is used, where it was taken for part of the host name and the request failed; an MTA-STS
    policy answer longer than 64 KB is no policy, where what had been read by then was parsed; and an ACME request
    names itself hMailServer, not hMailServer-ACME, and accepts any type of answer. The token request and the
    policy fetch also take the server's TLS settings - the protocol versions, the cipher lists and the key-exchange
    groups, with TLS 1.2 still the floor - as the ACME client and the other web requests already did, and on Windows
    too each of the three is given up once its deadline has passed for the whole request (15, 20 and 30 seconds),
    where there each read had that long. On Windows the metrics, ManageSieve and ACME http-01 listeners and the two
    web listeners (the REST API with the webmail it serves, and web services) no longer set SO_REUSEADDR, as the
    SMTP, POP3 and IMAP listeners have not for years: given a port that another program listens on with that option,
    such a listener now fails to bind and says so in the application log, where it bound the port and reported itself
    started while the connections went to the other program. Linux keeps the option, which there only lets a listener
    start again while its last connections linger. On Windows the SMTP, POP3 and IMAP listeners, ManageSieve, the
    metrics listener, the two web listeners and the SNMP agent also claim their ports with SO_EXCLUSIVEADDRUSE: no
    other program can then bind one address of a port such a listener holds on every address and take the connections
    made to that address, which Windows otherwise allows a program of the same account; and a listener that binds
    every address of a port another program already holds on one address now fails to bind and says so, where it
    started and served the other addresses. A listener started again on its own address - a service restart, a
    reinitialise, a drain that is left while sessions go on - binds as before, whatever its last connections are
    doing (measured on Windows 11). It is left off in two places: a port number listed twice among the TCP/IP ports
    in one address family (on 0.0.0.0 and again on 127.0.0.1, say), both of which still bind, and the ACME http-01
    responder, which still takes every address of its port that the REST listener or another web server does not
    hold.

  17. A calendar message delivered by mail changes a stored event only from a sender the server verified. A
    CANCEL is applied only from the organiser the stored event names, an updated REQUEST replaces the stored
    copy only from that organiser, and a REPLY sets only the answer of an attendee the event already lists who
    sent it - or whose delegate did, kept as SENT-BY in the organiser's copy. A sender at this server's own
    domains is verified by a signed-in session (SMTP AUTH, the webmail, a room answering by itself) as that
    account or an address it may send as; a sender from outside by a DMARC pass for exactly the domain of a From:
    that is the sender's address, written as that one mailbox - a From: that another mail program could read as
    another address, with a second <address>, text after the > or an address in a comment or in the display
    name, verifies nobody. With DMARC off, from an address range spam protection skips, through the send API -
    whatever address its key may send as - or from an external account, nothing is verified. A REPLY from the
    account an alias the event lists points at is set on that alias; a REPLY adds nobody, to the event or to an
    occurrence the organiser left the guest out of, but a member of a distribution list of this server's that the
    event lists, answering themselves, is added beside the list with MEMBER naming it. A new invitation is filed
    as before, marked X-HMAILSERVER-IMIP-UNVERIFIED when its organiser was not verified, and such a copy does not
    hold off the organiser's own invitation by its SEQUENCE; anything else is delivered as an ordinary message,
    with a line in the application log, and the webmail's card for it says that the calendar still has the event as
    it was instead of saying it changed (Calendar.md, "Who may change a stored
    event"). A room or a piece of equipment believes an organiser from outside only when the DMARC pass is for a
    From: that is the organiser's address, and no organiser for a message sent through the send API; its own
    answers are recorded as its submission, so that an organiser's calendar on this server files them
    (RoomsAndEquipment.md, "Who is asking").

  18. A cancellation of occurrences takes those occurrences out of a guest's calendar and leaves the rest of the
    series.
    A CANCEL whose every event carries a RECURRENCE-ID (RFC 5546 section 3.2.5), from the organiser
    the stored event names, takes those occurrences out of the guest's copy: each one's override is dropped and an
    EXDATE written on the series, and from an occurrence named with RANGE=THISANDFUTURE on the series ends, its
    later overrides and RDATEs dropped and its RRULE given an UNTIL the moment before it in place of any
    COUNT. The copy is marked cancelled only when those occurrences are everything it holds, as for a CANCEL of
    the whole event. In 6.3.4 and 6.3.5 such a CANCEL marked the guest's whole series cancelled. The webmail's
    card for the message says the event is no longer in the calendar once its occurrences are out, and its "Remove
    from calendar" - POST /api/v1/me/messages/{id}/invitation/reply with remove, which then answers
    occurrences: true - takes out those occurrences alone, where it removed the whole series
    (Calendar.md, "Filed on delivery"). A room or a piece of equipment took
    occurrences out of a booking already.

  19. The answer from an invitation's card replaces a copy of its UID that nobody verified, whatever organiser that
    copy names.
    A copy filed from an invitation whose organiser the server could not verify - marked, item 17 -
    that names another organiser than the invitation no longer makes the reader's Accept, Maybe or Decline on the
    invitation's card fail with 422: the answer replaces it with the invitation's event, marked as the card's
    answers mark what they write. A copy planted under the UID of an invitation before it arrived, naming somebody
    else, so keeps the invitation out of the calendar only until the reader answers it. Delivery still replaces no
    copy that names another organiser, verified or not, since a sender verified as the organiser it names is not
    the owner of the UID; and a copy the server did not mark - the organiser's own, verified, one the reader or a
    phone put in, and every copy filed before 6.3.6 - still refuses an answer that names another organiser
    (Calendar.md, "Who may change a stored event").

  20. Five more web requests go through HttpProxy. A sender's brand logo, a correspondent's OpenPGP key from
    their domain's Web Key Directory, a Web Push notification, a one-click unsubscribe and the post of an inbound
    webhook route held to public addresses - one that anybody but the server's administrator made - go through
    HttpProxy when it is set, where until 6.3.6 they went directly whatever it said, so that a server that
    reaches the web only through its proxy could make none of them. Each is still made only to a public address,
    judged by the addresses its host's name resolves to on this server, and the proxy is asked for a tunnel to one
    of those addresses and never to the name, as for the MTA-STS policy fetch (item 16): a proxy that allows a
    tunnel only to a name refuses it, and the request fails as one with no way out. A route the server's
    administrator made, which may reach this server's own network, still posts directly.

  21. The sender lists' URL entries read a large message only as far as the anti-spam size limit. A message
    larger than the limit past which the SURBL test and the other tests after DATA do not look at a message
    (maximum_message_size_kb of /api/v1/settings/antispam, COM AntiSpam.MaximumMessageSize, 1024 KB unless
    changed; 0 is the MIME parser's 80 MB) is judged by the links in the first part of its text and of its HTML,
    as far as that limit into each, where every character up to the parser's 80 MB was read - some 8 seconds of a
    delivery thread for a 20 MB message. A block or allow entry for a link after the limit no longer decides such
    a message, and a link the limit cuts through is left out rather than read as the part of its host before the
    cut (SenderListsAndDigests.md).

  22. An external account's days to keep messages means one thing for POP3 and IMAP, and the upgrade starts no
    account deleting remote mail it was not deleting.
    The setting has always meant, to the POP3 fetcher, the COM API
    and the OnExternalAccountDownload event: -1 deletes each message on the remote server as soon as it has been
    collected, 0 leaves every message there, and a number of days N deletes a message N days after it was collected.
    From 6.2.25 to 6.3.5 the IMAP fetcher read 0 as "delete" and -1 as "keep"; the Control Panel (from 6.2.0), the
    Deck, the webmail's Other accounts and the REST API described that reversed meaning for both kinds of account;
    and the webmail sent -1 for Leave the mail there, its default. Who was affected:

    • an IMAP external account at 0 - the default of COM, the Control Panel and the REST API - had its remote
      INBOX emptied as it was collected, and every mirrored folder when it mirrors folders, where the setting's own
      meaning is to leave everything; one at -1, the webmail's Leave the mail there, kept what -1 deletes;
    • a POP3 external account added, moved or changed in the webmail (6.3.4 and 6.3.5) with Leave the mail
      there
      , or given -1 through the REST API or the Deck, which called -1 "keeps every message", had each message
      deleted on the remote server once collected; one given Delete it there kept everything;
    • an event script that answered 3 ("never delete") for a message of an IMAP account had it deleted, and one that
      answered 1 ("delete now") had it kept.

    A message deleted on a remote server had been collected first and is in the local mailbox; the upgrade cannot put
    it back on the remote server. Both fetchers now ask one rule (FetchAccount.h), and the upgrade to schema 6106
    translates what is stored so that every account goes on doing what it did or stops deleting, and none starts: an
    IMAP account's 0 becomes -1 and its negative number 0; a POP3 account made or moved by somebody other than a
    server administrator - in the webmail, by a reader, by a key restricted to named domains or by a domain
    administrator - has a negative number made 0, so that it stops deleting what that person asked to leave; every
    other value is as it was. A backup made before 6.3.6 is restored through the same translation, and one made by
    6.3.6 as it is. What the upgrade does not change is what an event script answers: an OnExternalAccountDownload
    script that answers 1 (delete now) for a message of an IMAP account had it kept, and from 6.3.6 has it deleted,
    as the event has always said - look at such a script before upgrading. After upgrading, look at your IMAP
    external accounts:
    one that was emptying its remote INBOX against your intention now shows -1, delete after
    download
    , and goes on doing it until you set it to 0. Look at your POP3 external accounts at -1 too: the
    upgrade cannot tell one a server administrator set to -1 through the REST API or the Deck to keep its mail, or one
    a reader set to Leave the mail there in the webmail on an account a server administrator made, from one set to
    -1 through COM or the old Administrator to delete it, so it goes on deleting until you set it to 0; one a domain
    administrator made over COM with -1 to delete it, as COM has always meant, now keeps its mail until it is set to
    -1 again; and one set to 0 in the Control Panel to delete after download has kept everything, and goes on keeping
    it until you set it to -1. A program or script that writes the value - over COM, the REST API or the Deck -
    now has -1 delete and 0 keep for an IMAP account too; one written to the IMAP fetcher's earlier meaning is to be
    changed before it next writes it. A webmail or Deck page loaded before the upgrade says the earlier meaning until
    it is reloaded, and the webmail's sends -1 for Leave the mail there: a change such a page sends with a negative
    number is refused with 409, reload the page, and nothing is saved, since the 6.3.6 pages say what they mean by
    the number (days_to_keep_meaning) and a browser session that does not is taken to be one of them; an API key, a
    password and COM are not asked. The Control Panel (Days to keep on server
    (0 = never delete, -1 = delete after download)
    ), the Deck, the webmail, the COM help text and the REST API's
    description now all say the same thing; the webmail shows an account's number of days that its list does not offer
    as that number, where it showed and saved back 30; and an account the webmail imports with a sign-in leaves the
    mail on the other server unless asked otherwise (Migration.md, step 3). Tests:
    ExternalAccounts/RemoteRetention (both fetchers at -1 and 0, and the event's 1 and 3 on IMAP and POP3, each IMAP
    case failing against 6.3.5's fetcher), FetchAccountTester (the rule, the 6106 translation and a backup from
    before and after it), BackupRestore, the 6106 step's rows on PostgreSQL and MariaDB in
    build/db-upgrade-gate.sh, and the webmail's and the Deck's script tests.

  23. The upgrade to schema 6107 finishes the greylisting key, and a greylisting race is never a pass (work item
    #50, the follow-up to GitHub discussion 281). Schema
    6072 left the triplets written before it without a key, to be found by their first 200 characters until they were
    matched once or expired - a second query for every new triplet for as long as one remained. The upgrade now gives
    them their key in the server, since no database can compute it, with the function new rows are written with;
    deletes the rows in which an address had been cut to the old columns' 200 characters, since such a row may stand
    for several senders and its key would match nobody; and leaves a triplet one row, the one that passed most. Then
    both key columns are NOT NULL and the key is unique - on MySQL and MariaDB over a stored generated column,
    glipaddress2key, and on PostgreSQL as two partial indexes, because both count one NULL glipaddress2, an IPv4
    triplet's, as different from another - and the lookup asks for the key and nothing else. A sender whose row was
    deleted is greylisted once more; the upgrade's output says how many rows were keyed and deleted, and why (with
    DBUpdater and UpgradeOnStart, in the application log when application logging is on). On a large
    hm_greylisting_triplets the keying runs before the step's transaction, each statement committed as it goes, so
    an upgrade stopped part-way keeps what it did and the next run continues from there. And when two connections
    present the same new triplet at once, the unique key refuses the second insert, which is read back and answered as
    the triplet that is there: deferred and counted while it is pending, let through once it has passed - never let
    through for the refusal (Greylisting.md, "The triplet key").

  24. The Linux live update's helper takes the unit and the AppImage from its own command line.
    hmailserver-update stops hmailserver.service, or the unit its --unit option names, and refuses a request
    whose unit= line names another. It takes an AppImage request only when its --image option names the image,
    which is then the program that verifies the new one, and refuses a request whose image= line names another
    file, and a .deb or .rpm request where --image is given. After the stop it checks that the unit is
    inactive and that no process runs as the owner of the Updates directory or of a directory above it, and
    otherwise starts the unit again and does nothing else. It verifies, reads and installs copies of the package and
    the rollback image, made once the service has stopped and only of plain files still inside the Updates
    directory, under /var/tmp (an AppImage's beside the image), which needs room there for both. A machine whose
    package database, or image, does not say which version is installed is no longer updated "over whatever is
    here": the outcome is failed. A server installed from the .deb or the .rpm under the packaged unit needs
    nothing. One under a unit of another name, through the path unit, needs --unit in a drop-in for
    hmailserver-update.service; an AppImage under the path unit needs --image there, and 6.3.6's script in
    /usr/lib/hmailserver/, which the AppImage's swap does not replace; an AppImage that runs as root under a unit
    of its own passes both itself. The update to 6.3.6 is applied by the helper of the version it replaces, and
    6.3.6's own applies the next one
    (packaging/README.md, An AppImage).

  25. Port 25 still takes AUTH, and the setup checklist says so (GitLab work item #54). The schema step 6107 to
    6108 gives every TCP/IP port an SMTP authentication and writes On into every row, so no listener of an upgraded
    server answers AUTH differently, and nobody is banned on the morning of the upgrade: clients that still sign in
    on 25 go on doing so. The setup checklist has a thirteenth item, No password on port 25
    (delivery_port_auth), action needed with no one-click fix while an SMTP listener on 25 is On - setting it to
    Honeypot or Off is what arms the ban, so move such clients to 587 first
    (SmtpAuthOnDeliveryPorts.md). GET /api/v1/ports gives each port
    smtp_authentication, and a PUT of a port that leaves it out writes on, as it writes the default of every field
    it leaves out. TCPIPPorts.SetDefault - Restore defaults - writes both SMTP ports On, and says so. A backup
    taken before 6.3.6 restores every port On. The type library adds TCPIPPort.SMTPAuthentication (dispid 12). The
    error HM6099, a ban's range that could not be saved, no longer says the address reached the failed-logon limit,
    which a spam trap's or a null sender's ban never did.

  26. COM: a message or fetch account a program creates itself answers with the credential of an Application the
    program signed in on, and is refused outside an event script when there is none; a signed-in account queues mail
    only as itself.
    Most scripts need no change: one that creates hMailServer.Application, calls Authenticate and
    then makes its message with CreateObject("hMailServer.Message") works as before, as long as it keeps the
    Application until the message is saved (a .NET program with GC.KeepAlive). With an administrator password set,
    Message.Save and Copy, FetchAccount.AccountID and Save, Account.Save and the other writes to an account's
    rules, folders and folder permissions refuse an object that carries no credential. A hMailServer.Message or
    hMailServer.FetchAccount a program creates with CreateObject or new carries none of its own: it takes the
    credential of an Application the same program holds and has signed in on - the most privileged, where it holds
    several - and has none in a program that never signed in, or once that Application is released or a later
    Authenticate on it fails. The objects an Application handed out before a later Authenticate on it failed are
    refused too. Attachments.Add, which reads a file from the server's disk, and Attachment.SaveAs, which writes
    one, answer the server administrator alone: a program signed in as a domain administrator or an account is refused
    them. Mail that a program signed in as an account queues, or saves again while it waits in the queue, claims to be
    from only an address the account may send as - its own, an alias of it, one granted to it or verified
    (SendAs.md) - in its envelope and in its From and Sender fields, with no line
    break inside a value of its header, and a domain administrator's from an address of a domain this server holds and
    it administers, other than an account there with authority it does not hold; to send as anybody, a program signs in
    as the server administrator, as it must to attach a file. The server's event scripts, which create these objects
    without signing in, work as before, and a server with no administrator password and no second factor answers every
    caller as it did. The new Application.CreateMessage() gives a program a message that carries the credential of
    the Application it is called on; a .NET program built against an earlier type library reaches it once rebuilt
    against 6.3.6's, or late-bound (Roles.md).

  27. The packaging Compose file's database is PostgreSQL 18. docker-compose.yml beside the Dockerfile runs
    postgres:18 with its hm-db volume at /var/lib/postgresql, where it ran postgres:16 with the volume at
    /var/lib/postgresql/data. PostgreSQL 18 does not open 16's data: a deployment made from an earlier copy of the
    file that takes this one finds db stopping at each start with "Error: in 18+, these Docker images are
    configured to store database data in a format which is compatible with "pg_ctlcluster"" and the server not
    started, 16's data unchanged, until the database is moved once - a dump taken on 16 and loaded into 18, four steps with a way
    back (ContainerImage.md, "PostgreSQL 16 to 18, in the Compose file").
    A deployment that keeps its own copy of the file keeps PostgreSQL 16, on which 6.3.6 runs as before.

  28. DANE-TA records authenticate a recipient's MX host, and a validated set of none this server can use requires
    TLS.
    A DNSSEC-validated TLSA record of usage DANE-TA (2) - selector 0 or 1, matching type 0, 1 or 2 - is now
    used, through OpenSSL's own DANE support: the host's certificate must chain to the anchor the record names (which
    the host sends in its chain when the record holds a digest), name the MX host and be in date. A DANE-EE (3) record
    is matched as before, against the certificate's key or the certificate alone. A delivery to a host that publishes
    only DANE-TA records, and whose certificate does not meet them, is now deferred where until 6.3.6 it went ahead as
    if no record were published; and such a host that does not offer STARTTLS is deferred, as a host with DANE-EE
    records now is too (item 31). A validated set none of whose records is usable here - PKIX-TA (0) and PKIX-EE (1),
    which SMTP does not use, and records of values or lengths no record has - requires TLS to that host without
    judging its certificate: a host that does not offer STARTTLS, or whose handshake fails, is deferred, never
    delivered to in the clear, and TLS reports (RFC 8460) count the host under the policy type tlsa. An MTA-STS
    policy in enforce mode, a remote-domain policy or a rule that asks for a certificate that verifies still has it
    verified, and so does this server's own outbound security, or a route's, at SSL or STARTTLS (required) with
    VerifyRemoteSslCertificate on: there the certificate of a host with only unusable records must verify, and a
    delivery to one whose certificate does not is deferred. Error 5510 is logged when a connection's records could not
    be given to OpenSSL; that delivery is deferred rather than made without them.

  29. A ManageSieve connection is held to deadlines. A command line must arrive whole within 30 seconds of the
    server waiting for it, which is also how long a session may sit idle between commands, as before; the TLS
    handshake after STARTTLS has 30 seconds; a literal (a script, a SASL response) and the writing of each response
    have 300 seconds; and a connection must complete AUTHENTICATE within 60 seconds of being accepted, or of
    UNAUTHENTICATE. A connection that runs past one is closed, with a debug log line naming the client. The figures
    are constants, far above what a client needs, and there is no setting for them.

  30. The URI blocklists are asked about every domain a message links to, and a message that links to too many is
    scored for it.
    The SURBL test reads every link of the text part and of the HTML part of a message within the
    anti-spam size limit, takes the hosts of one registrable domain (by the Public Suffix List) as one domain, and asks
    each active list about every registrable domain and address the links name - and, for a host under a shared-hosting
    suffix of the list's private section, the domain registered under its ICANN section, github.io for
    tenant.github.io - up to 200 for a message, the HTML part's before those only the text part names; then up to two


GitHub shows at most 125,000 characters of release notes and these are 421,302. The complete notes are on GitLab: https://gitlab.com/hmailserver/hmailserver/-/releases/v6.3.6. The copy attached below as release-notes-v6.3.6.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 CVE identifiers that the notes on GitLab now give for twenty-two of its flaws.