Event scripts: every event and its arguments, the limits, and the 6.3.4 security fix
-
Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) refuses a rule's Run function action when the rule is saved unless it names a plain function, for a rule of every owner, over COM and REST, and setting one on a domain's or an account's rule is held to the server administrator alone, where up to 6.3.6 a credential confined to named domains, a domain administrator among them, could set it. An event script that goes on in a transaction after
Database.ExecuteSQLhas failed is refused every statement after it once the database has ended the transaction, andDatabase.CommitTransactionthen fails: callDatabase.RollbackTransactionand run the whole transaction again, or stop. AScriptTimeoutabove 4,294,967 seconds is taken as that, where it ended every Windows event script at once, and the VBScript calls ofOnClientValidatePassword,OnExternalAccountDownloadandOnDeliveryFailedare quoted as every other call is. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) adds a sixteenth event,OnIMAPMessageMoved, called once for each message an IMAPMOVEorUID MOVEmoved between two folders of the account signed in, after the command and off the IMAP thread, with the account and thirteen values, in VBScript and JScript on Windows and JavaScript on Linux; it is off as shipped, andIMAPMessageMovedEvents(COMSettings.Scripting.IMAPMessageMovedEvents,imap_message_moved_eventsinPUT /api/v1/settings/scripting) switches it on (EventScripts.md). Work that outlasts a stop of the servers now writes its interruption to the debug log, not the ERROR log, soOnErroris not called for it, and an event script's call that meets a stop fails with "The server is stopping. The request was not completed.", its script error going to the debug log. See the 6.3.8 announcement.This topic covers event scripts in hMailServer 6.3.4: the languages, switching scripting on, the fifteen events, the objects, the limits and errors, and the 6.3.4 security fix. If you run a Windows server older than 6.3.4 with event scripting on, that fix is the one thing to know first: a value written into a JScript handler's call, a password among them, could run code inside the server, and so could the name in a rule's Run function action, in either language. The section on CVE-2026-100741 below says what to do until the server is upgraded.
What an event script is
An event script is one file,
EventHandlers.vbsorEventHandlers.js, in the server's Events directory, holding functions named after the events they handle. When scripting is on, the server calls the function for each event as it happens, hands it the objects the event is about (the client, the message, the account) and reads back what the function decided through theResultobject. A rule's Run function action calls a function of the same file by name, and it too needs scripting on.Scripting is off as shipped, and with it off no handler is called. The file is loaded all the same: at start, at every reload, at
Reload()and atPOST /api/v1/settings/scripting/reload. Loading a file runs its code outside functions, once to compile it and once more for each of the fifteen handlers looked for, so keep the top level to declarations. A top level that throws, or runs past the timeout, is reported as a load that failed (HM5016,HM5710orHM5021), whether scripting is on or not.A handler runs inside the server, on the thread that is doing the work, as the service's account: LocalSystem on a Windows installation that began before 6.3.4,
NT SERVICE\hMailServeron a new one from 6.3.4, thehmailserveruser on Linux. Everything behind a slow script waits, so try a script first on a server not carrying your mail.Which language runs where
Language Windows Linux Engine File VBScriptruns refused at load Windows Active Scripting ( vbscript.dll)EventHandlers.vbsJScriptruns refused at load Windows Active Scripting ( jscript.dll), ECMAScript 3EventHandlers.jsJavaScriptrefused at load runs QuickJS 2025-04-26, built into the server, ES2023 EventHandlers.jsUp to 6.3.3 a Linux server had no script engine and refused any script; since 6.3.4 it runs JavaScript in QuickJS, with the same fifteen events, function names and objects. Windows keeps VBScript and JScript unchanged.
The language is one setting:
Settings.Scripting.Languageover COM (Windows only),"language"inPUT /api/v1/settings/scriptingover REST, and the Language (VBScript, JScript or JavaScript) field on the Control Panel's Advanced page. Since 6.3.4 it accepts all three words on both platforms, in any case, and refuses any other word without storing anything. It is seededVBScripton every platform, so a Linux server switching scripting on setsJavaScriptfirst.Storing the language loads nothing. The script in force goes on running, in the language it was loaded in, until the next load. A language the build does not run is refused when the script is loaded: one line in the ERROR log,
HM5016at a start orHM5710at a reload (the earlier script then stays in force), naming the language to use, once per load. While scripting is switched off this refusal is not reported, so switch scripting on and reload before reading the log for it.GET /api/v1/statussays which languages a build runs, underbuild.script_languages:["VBScript","JScript"]on Windows,["JavaScript"]on Linux.Switching it on
On Windows there are two Control Panel pages under Maintenance: Advanced, whose Scripting tab turns the engine on and chooses the language, and Event scripts, which edits the file. Save & reload writes a
.bakfirst, then reloads and checks the syntax. Over COM the switch isSettings.Scripting.Enabled;Settings.Scripting.Reload()re-reads the file andCheckSyntax()returns the error text, where empty means clean. There is one file and no include mechanism.On Linux, from the command line:
- Set the language:
hmctl settings set scripting --set-string language=JavaScript - Write the file as the service's user:
sudo -u hmailserver tee /var/lib/hmailserver/events/EventHandlers.js <<'EOF' function OnAcceptMessage(client, message) { if (message.Subject.includes('[URGENT]') && !client.Authenticated) { Result.Value = 2; Result.Message = 'Urgent mail comes from people who have logged on.'; } } EOF- Switch scripting on:
hmctl settings set scripting --set enabled=true - Confirm which file the server reads:
hmctl settings get scripting current_script_file - Load it with
POST /api/v1/settings/scripting/reload.POST /api/v1/settings/scripting/checkanswers{"result":""}when the file compiles and the error, with its line, when it does not.
The fifteen events
Every handler is optional. When the script is loaded the server checks which of these names the file declares and fires only those. Names are matched exactly:
OnErrorLogis not a handler, the name isOnError.Handler Fires when Called with What Result.ValuedoesOnClientConnect(client)any TCP connection is accepted, before a greeting the client 1closes the connection at onceOnHELO(client)the SMTP client sent HELO or EHLO the client 1answers554 5.7.1 Rejected;2answers554 5.7.1withResult.Message;3answers453 4.7.0withResult.MessageOnClientLogon(client)an SMTP, POP3 or IMAP client finished a logon attempt, successful or not the client; client.Authenticatedsays how the logon wentnothing: the logon has been decided OnClientValidatePassword(account, password)a password is being checked, before the built-in check the account and the password typed 0lets the client in;1turns it away;2(as given) leaves it to the stored passwordOnSMTPData(client, message)the DATA command arrived, before the body the client and the message as OnHELOOnAcceptMessage(client, message)the body has been received and is about to be queued the client and the message as OnHELOOnRecipientUnknown(client, message)a RCPT TO named an address this server does not host the client and the message nothing OnTooManyInvalidCommands(client, message)an SMTP client sent too many invalid commands the client and the message nothing OnDeliveryStart(message)the first step of a delivery attempt, before virus scanning the message 1deletes the messageOnDeliverMessage(message)after virus scanning and global rules, before the recipient split the message 1deletes the messageOnDeliveryFailed(message, recipient, error)once per failed recipient; may fire several times per message the message, the recipient, the error text nothing OnExternalAccountDownload(fetchAccount, message, uid)a message was fetched from an external POP3 or IMAP account the fetch account, the message or null, its UID on the remote server1deletes it on the remote server now;2afterResult.Parameterdays;3neverOnBackupCompleted()a backup finished nothing nothing OnBackupFailed(reason)a backup failed the reason nothing OnError(severity, code, source, description)anything is written to the ERROR log the error, as the ERROR log has it nothing OnError's severity is1Critical,2High,3Medium,4Low, and its code is theHM<nnnn>number without the prefix.OnClientValidatePasswordsetting0is a full authentication bypass for that account, on every protocol. A handler that bridges to another directory should fail closed: set1when the lookup errors, never0.The objects a handler is given
The objects an event has are also globals of the same names,
HMAILSERVER_CLIENT,HMAILSERVER_MESSAGE,HMAILSERVER_ACCOUNT,HMAILSERVER_FETCHACCOUNT,ResultandEventLog, and the parameter and the global are the same object. An object an event does not have isnullas a parameter; as a global it may not be defined at all (the message of an external download that failed), and reading an undefined global throws aReferenceError, so a handler reads its parameter.Do not use
Resultin the seven events that are notifications. EventScripts.md says it is not defined at all inOnClientLogon,OnRecipientUnknown,OnTooManyInvalidCommands,OnDeliveryFailedandOnError; the public documentation adds that the server makes noResultforOnBackupCompletedandOnBackupFailed.Object Members On JavaScript (Linux) Client Port,IPAddress,Username,HELO,Authenticated,EncryptedConnection,CipherVersion,CipherName,CipherBits,SessionIDthe same, all read-only Message ID,Filename,Subject,From,FromAddress,To,CC,Date,Body,HTMLBody,Charset,Recipients,AddRecipient(name, address),ClearRecipients(),HeaderValue(name),HasBodyType(type),Flag(flag),Size(KB),State,DeliveryAttempt,UID,InternalDate,Copy(folderId),RefreshContent(),Save(),Attachments,HeadersAttachmentsandHeadersare not there;SetHeaderValue(name, value)andSetFlag(flag, on)write;InternalDateis textResult Value,Message,ParameterValueandParametertake a whole Number onlyEventLog Write(text)the same FetchAccount the COM FetchAccountreadable, not assignable; Password,Save(),DownloadNow()andDelete()are not thereAccount the COM Account, freshly loadedthe properties that describe the account are readable, not assignable; Password, the collections and every method are not thereSave()is what writes a change to disk. Changes to the subject, the body and the headers are written to the message file bySave(), on both platforms; a handler that does not call it changes nothing on disk. Do not call it inOnSMTPData: the public documentation says the message is still being processed there and saving corrupts it, and that only the character set, the file name, the envelope sender and the recipients are reliable at that point. Change a message inOnAcceptMessage.Resultis read after the handler returns. On JavaScript a string ('1'too), a boolean,null,undefined,NaN, a fraction or an object assigned toValueorParameterthrows aTypeError, and the event goes on with theResultit had. So inOnClientValidatePassword,Result.Value = decisions[user]for a user the table does not have leaves2, the stored password. On Windows, COM turns a boolean,undefined, a fraction and a string of digits into numbers, andundefinedinto0: the same line in JScript lets that client in.EventLog.Writeis the debugger. It appends a line tohmailserver_events.login the log directory, and there is no breakpoint.A rule's Run function
A rule's Run script function action calls the named function as
<function>(HMAILSERVER_MESSAGE), then reloads the message from disk, so anything the function saves is seen by the rest of the rule set. Since 6.3.4 it works on Linux, where the function is a JavaScript one inEventHandlers.js.Since 6.3.4 the action names the function alone, as a plain identifier: ASCII letters, digits,
_and$, not starting with a digit.StampReceivedDatequalifies;StampReceivedDate(x)does not. On Windows, in VBScript or JScript, any other name is refused asHM5018each time the rule matches and the function is not run. On Linux such a name is never evaluated and finds no function: nothing is run, and the ERROR log has aScript Errorline, notHM5018.A ported handler
JScript and JavaScript share the file name, so a JScript file carried from Windows to Linux is the file the JavaScript engine is given. EventScripts.md's example starts from VBScript on Windows:
Sub OnAcceptMessage(oClient, oMessage) If InStr(oMessage.HeaderValue("X-Spam-Flag"), "YES") > 0 And Not oClient.Authenticated Then oMessage.HeaderValue("X-Quarantine") = "yes" oMessage.Subject = "[SPAM] " & oMessage.Subject oMessage.Save EventLog.Write("Tagged spam from " & oClient.IPAddress) End If End SubThe same handler for a Linux server, in
EventHandlers.js:function OnAcceptMessage(client, message) { if (message.HeaderValue('X-Spam-Flag').includes('YES') && !client.Authenticated) { message.SetHeaderValue('X-Quarantine', 'yes'); message.Subject = `[SPAM] ${message.Subject}`; message.Save(); EventLog.Write(`Tagged spam from ${client.IPAddress}`); } }What changes in a port:
- Parameterised properties have no assignment.
message.HeaderValue('X') = 'v'becomesmessage.SetHeaderValue('X', 'v'), andmessage.Flag(n) = truebecomesmessage.SetFlag(n, true). The reading forms are unchanged. - Names are case-sensitive and spelled as the IDL spells them. JScript found
message.FileName; JavaScript needsmessage.Filename. - No
CreateObject, nonew ActiveXObject. Both exist on JavaScript only to throw aTypeErrorthat says so. null, notNothing, for an object an event does not have.
The limits
A fresh engine for every event, on Windows and Linux alike: the whole file is parsed and run, the handler is called, and the engine is freed before the event goes on, so nothing a script makes outlives the event.
No file system, no network, no processes on JavaScript. QuickJS keeps these in
quickjs-libc.c, and that file is not in the tree. No module loader is installed, soimport()is refused and animportstatement is a syntax error; there is noprintand noconsole. What a handler can change is what its objects change: a message's file throughSave(), a copy into a folder throughCopy(), the event log throughEventLog.Write.On Windows,
ScriptAllowedObjectsdecides what a script may create withCreateObjectornew ActiveXObject. Its shipped value is*, every class. Empty means no class beyond the server's own objects, and a comma-separated list of ProgIDs means only those. Up to 6.3.3 it applied at a service restart; since 6.3.4 a save applies it. It has nothing to apply to on Linux.ScriptTimeoutis in seconds, with0for no limit. It is kept in the settings store: set it withPUT /api/v1/settings/ini/ScriptTimeout, COMSettings.SetIniSettingor the Control Panel, not by editinghMailServer.ini.Limit Platform Value When a handler reaches it ScriptTimeoutWindows 60 seconds by default HM5019; the interrupt cannot release a handler blocked inside a COM callScriptTimeoutLinux 60 seconds by default HM7341; the event goes on with whateverResultthe handler had setMemory Linux 64 MB for each run, a constant HM7342; the event goes onStack Linux 512 KB of the calling thread's stack a runaway recursion throws an InternalErrorThe Linux timeout does not stop one call into a built-in that loops in the engine's C code without polling, such as
includesorindexOfon an object whoselengthis huge: do not call an array method on an object whoselengththe handler has not checked.Errors, and where they are written
What Where A script that does not compile, or whose top level throws HM5016at start (nothing loaded),HM5710at a reload (the earlier script stays in force)A top level that runs past the timeout HM5021, and the load refusedAn exception a handler does not catch one Script Errorline in the ERROR log; the event goes onThe load is all or nothing on every engine, so a typo in a reload never switches a working
OnClientValidatePasswordoff.The 6.3.4 security fix: CVE-2026-100741
Up to 6.3.3 a password could run code inside the server. On Windows, with event scripting on, the language JScript and an
OnClientValidatePasswordhandler inEventHandlers.js, any client that could reach a sign-in and name an existing, active account could have JScript of its own run insidehMailServer.exe, with the rights of the service's account, before it had authenticated. The server built each call as source text and escaped the apostrophe but not the backslash before it. WithScriptAllowedObjectsat its shipped*, that script could create any COM object,WScript.Shellamong them.Path Who could use it Open in The password given to OnClientValidatePasswordany client that can reach a sign-in JScript The UID given to OnExternalAccountDownloada remote POP3 server JScript The error text and failed recipient given to OnDeliveryFaileda remote mail server JScript The name in a rule's Run function action whoever may edit a rule, a domain's own administrator included JScript and VBScript Which servers are affected. The flaw is CVE-2026-100741; the record was not yet public when this was written. The flaw is in every release from 6.0.0 to 6.3.3: the escaping was checked at every one of those tags (GitLab issue 27). Treat any server older than 6.3.4 with event scripting on and JScript as its language as affected. A rule's Run function action wrote its function name into the call unescaped, in either language, so VBScript servers too.
What 6.3.4 changes. Every JScript value is escaped in one helper,
ScriptServer::EscapeForJScriptLiteral: every backslash first, then both quotes, CR, LF, U+2028, U+2029 and NUL;OnBackupFailed's reason, which neither language escaped and the table above does not list, is escaped in both. A rule's Run function name must be a plain identifier. On Linux the handler is called with values as values, so nothing is quoted into source text. A JScript handler now receives every value exactly as it was, backslashes included; one that undid the old half-escaping by hand must stop doing so.Until you can upgrade, switch event scripting off. That is the only measure that closes every path, in either language. A server that cannot do without its scripts can narrow what is open:
- Take the
OnClientValidatePassword,OnExternalAccountDownloadandOnDeliveryFailedhandlers out of a JScriptEventHandlers.jsand reload the scripts. Check the reload: one that fails (HM5710in the ERROR log) keeps the earlier script in force, those handlers included. - In either language, let nobody but the server administrator make or edit a rule with a Run function action. Roles.md lets an account's rules be written over COM by the server administrator, an administrator of its domain, or the account itself, so "whoever may edit a rule" is wider than administrators.
- Narrow
ScriptAllowedObjectsfrom*, which limits what an injected script can create. The key exists from 6.2.25, and before 6.3.4 it applies only after a service restart.
Moving a handler to VBScript closes the password, UID and error-text paths and leaves the rule path open.
Before you upgrade, check your rules' Run function actions and put the function's bare name in any that does not qualify. After it, an
HM5018in the ERROR log (on Linux, aScript Errorline naming the function) means one was missed. Upgrading to 6.3.4 covers the upgrade itself.Reporting a vulnerability goes through a confidential GitLab issue or the Service Desk address
contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, and not through a public issue or this forum.What it does not do
OnClientValidatePasswordis not consulted for SCRAM, which has no password to give it.
Where to read more
- EventScripts.md: the member-by-member contract of every object, and the engine.
- The COM API and Scripting, sections 23.6 to 23.10.
- Rules and Sieve for the Run script function action, and Security Hardening, section 17.11.
- The public documentation's event scripts chapter.
- On this forum: hMailServer on Linux: what you get and what you do not, the 6.3.4 security announcement, the COM API on Windows and rules and the Run function action.
- Set the language:
-
C chris pinned this topic on
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login