<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Event scripts: every event and its arguments, the limits, and the 6.3.4 security fix]]></title><description><![CDATA[<p dir="auto"><em>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 <code>Database.ExecuteSQL</code> has failed is refused every statement after it once the database has ended the transaction, and <code>Database.CommitTransaction</code> then fails: call <code>Database.RollbackTransaction</code> and run the whole transaction again, or stop. A <code>ScriptTimeout</code> above 4,294,967 seconds is taken as that, where it ended every Windows event script at once, and the VBScript calls of <code>OnClientValidatePassword</code>, <code>OnExternalAccountDownload</code> and <code>OnDeliveryFailed</code> are quoted as every other call is. See <a href="/topic/28277">the 6.3.7 announcement</a>. hMailServer 6.3.8 (2 November 2026) adds a sixteenth event, <code>OnIMAPMessageMoved</code>, called once for each message an IMAP <code>MOVE</code> or <code>UID MOVE</code> moved 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, and <code>IMAPMessageMovedEvents</code> (COM <code>Settings.Scripting.IMAPMessageMovedEvents</code>, <code>imap_message_moved_events</code> in <code>PUT /api/v1/settings/scripting</code>) switches it on (<a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.8/hmailserver/docs/EventScripts.md" rel="nofollow ugc">EventScripts.md</a>). Work that outlasts a stop of the servers now writes its interruption to the debug log, not the ERROR log, so <code>OnError</code> is 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 <a href="/topic/28278">the 6.3.8 announcement</a>.</em></p>
<p dir="auto">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 <em>Run function</em> action, in either language. The section on CVE-2026-100741 below says what to do until the server is upgraded.</p>
<h3><a class="anchor-offset" name="what-an-event-script-is"></a>What an event script is</h3>
<p dir="auto">An event script is one file, <code>EventHandlers.vbs</code> or <code>EventHandlers.js</code>, 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 the <code>Result</code> object. A rule's <em>Run function</em> action calls a function of the same file by name, and it too needs scripting on.</p>
<p dir="auto"><strong>Scripting is off as shipped</strong>, and with it off no handler is called. The file is loaded all the same: at start, at every reload, at <code>Reload()</code> and at <code>POST /api/v1/settings/scripting/reload</code>. 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 (<code>HM5016</code>, <code>HM5710</code> or <code>HM5021</code>), whether scripting is on or not.</p>
<p dir="auto"><strong>A handler runs inside the server</strong>, on the thread that is doing the work, as the service's account: LocalSystem on a Windows installation that began before 6.3.4, <code>NT SERVICE\hMailServer</code> on a new one from 6.3.4, the <code>hmailserver</code> user on Linux. Everything behind a slow script waits, so try a script first on a server not carrying your mail.</p>
<h3><a class="anchor-offset" name="which-language-runs-where"></a>Which language runs where</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Language</th>
<th>Windows</th>
<th>Linux</th>
<th>Engine</th>
<th>File</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>VBScript</code></td>
<td>runs</td>
<td>refused at load</td>
<td>Windows Active Scripting (<code>vbscript.dll</code>)</td>
<td><code>EventHandlers.vbs</code></td>
</tr>
<tr>
<td><code>JScript</code></td>
<td>runs</td>
<td>refused at load</td>
<td>Windows Active Scripting (<code>jscript.dll</code>), ECMAScript 3</td>
<td><code>EventHandlers.js</code></td>
</tr>
<tr>
<td><code>JavaScript</code></td>
<td>refused at load</td>
<td>runs</td>
<td>QuickJS 2025-04-26, built into the server, ES2023</td>
<td><code>EventHandlers.js</code></td>
</tr>
</tbody>
</table>
<p dir="auto">Up 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.</p>
<p dir="auto"><strong>The language is one setting</strong>: <code>Settings.Scripting.Language</code> over COM (Windows only), <code>"language"</code> in <code>PUT /api/v1/settings/scripting</code> over REST, and the <strong>Language (VBScript, JScript or JavaScript)</strong> 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 seeded <code>VBScript</code> on every platform, so a Linux server switching scripting on sets <code>JavaScript</code> first.</p>
<p dir="auto"><strong>Storing the language loads nothing.</strong> 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, <code>HM5016</code> at a start or <code>HM5710</code> at 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.</p>
<p dir="auto"><code>GET /api/v1/status</code> says which languages a build runs, under <code>build.script_languages</code>: <code>["VBScript","JScript"]</code> on Windows, <code>["JavaScript"]</code> on Linux.</p>
<h3><a class="anchor-offset" name="switching-it-on"></a>Switching it on</h3>
<p dir="auto"><strong>On Windows</strong> there are two Control Panel pages under <strong>Maintenance</strong>: <strong>Advanced</strong>, whose <em>Scripting</em> tab turns the engine on and chooses the language, and <strong>Event scripts</strong>, which edits the file. <strong>Save &amp; reload</strong> writes a <code>.bak</code> first, then reloads and checks the syntax. Over COM the switch is <code>Settings.Scripting.Enabled</code>; <code>Settings.Scripting.Reload()</code> re-reads the file and <code>CheckSyntax()</code> returns the error text, where empty means clean. There is one file and no include mechanism.</p>
<p dir="auto"><strong>On Linux</strong>, from the command line:</p>
<ol>
<li>Set the language: <code>hmctl settings set scripting --set-string language=JavaScript</code></li>
<li>Write the file as the service's user:</li>
</ol>
<pre><code class="language-sh">sudo -u hmailserver tee /var/lib/hmailserver/events/EventHandlers.js &lt;&lt;'EOF'
function OnAcceptMessage(client, message) {
   if (message.Subject.includes('[URGENT]') &amp;&amp; !client.Authenticated) {
      Result.Value = 2;
      Result.Message = 'Urgent mail comes from people who have logged on.';
   }
}
EOF
</code></pre>
<ol start="3">
<li>Switch scripting on: <code>hmctl settings set scripting --set enabled=true</code></li>
<li>Confirm which file the server reads: <code>hmctl settings get scripting current_script_file</code></li>
<li>Load it with <code>POST /api/v1/settings/scripting/reload</code>. <code>POST /api/v1/settings/scripting/check</code> answers <code>{"result":""}</code> when the file compiles and the error, with its line, when it does not.</li>
</ol>
<h3><a class="anchor-offset" name="the-fifteen-events"></a>The fifteen events</h3>
<p dir="auto">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: <code>OnErrorLog</code> is not a handler, the name is <code>OnError</code>.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Handler</th>
<th>Fires when</th>
<th>Called with</th>
<th>What <code>Result.Value</code> does</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>OnClientConnect(client)</code></td>
<td>any TCP connection is accepted, before a greeting</td>
<td>the client</td>
<td><code>1</code> closes the connection at once</td>
</tr>
<tr>
<td><code>OnHELO(client)</code></td>
<td>the SMTP client sent HELO or EHLO</td>
<td>the client</td>
<td><code>1</code> answers <code>554 5.7.1 Rejected</code>; <code>2</code> answers <code>554 5.7.1</code> with <code>Result.Message</code>; <code>3</code> answers <code>453 4.7.0</code> with <code>Result.Message</code></td>
</tr>
<tr>
<td><code>OnClientLogon(client)</code></td>
<td>an SMTP, POP3 or IMAP client finished a logon attempt, successful or not</td>
<td>the client; <code>client.Authenticated</code> says how the logon went</td>
<td>nothing: the logon has been decided</td>
</tr>
<tr>
<td><code>OnClientValidatePassword(account, password)</code></td>
<td>a password is being checked, before the built-in check</td>
<td>the account and the password typed</td>
<td><code>0</code> lets the client in; <code>1</code> turns it away; <code>2</code> (as given) leaves it to the stored password</td>
</tr>
<tr>
<td><code>OnSMTPData(client, message)</code></td>
<td>the DATA command arrived, before the body</td>
<td>the client and the message</td>
<td>as <code>OnHELO</code></td>
</tr>
<tr>
<td><code>OnAcceptMessage(client, message)</code></td>
<td>the body has been received and is about to be queued</td>
<td>the client and the message</td>
<td>as <code>OnHELO</code></td>
</tr>
<tr>
<td><code>OnRecipientUnknown(client, message)</code></td>
<td>a RCPT TO named an address this server does not host</td>
<td>the client and the message</td>
<td>nothing</td>
</tr>
<tr>
<td><code>OnTooManyInvalidCommands(client, message)</code></td>
<td>an SMTP client sent too many invalid commands</td>
<td>the client and the message</td>
<td>nothing</td>
</tr>
<tr>
<td><code>OnDeliveryStart(message)</code></td>
<td>the first step of a delivery attempt, before virus scanning</td>
<td>the message</td>
<td><code>1</code> deletes the message</td>
</tr>
<tr>
<td><code>OnDeliverMessage(message)</code></td>
<td>after virus scanning and global rules, before the recipient split</td>
<td>the message</td>
<td><code>1</code> deletes the message</td>
</tr>
<tr>
<td><code>OnDeliveryFailed(message, recipient, error)</code></td>
<td>once per failed recipient; may fire several times per message</td>
<td>the message, the recipient, the error text</td>
<td>nothing</td>
</tr>
<tr>
<td><code>OnExternalAccountDownload(fetchAccount, message, uid)</code></td>
<td>a message was fetched from an external POP3 or IMAP account</td>
<td>the fetch account, the message or <code>null</code>, its UID on the remote server</td>
<td><code>1</code> deletes it on the remote server now; <code>2</code> after <code>Result.Parameter</code> days; <code>3</code> never</td>
</tr>
<tr>
<td><code>OnBackupCompleted()</code></td>
<td>a backup finished</td>
<td>nothing</td>
<td>nothing</td>
</tr>
<tr>
<td><code>OnBackupFailed(reason)</code></td>
<td>a backup failed</td>
<td>the reason</td>
<td>nothing</td>
</tr>
<tr>
<td><code>OnError(severity, code, source, description)</code></td>
<td>anything is written to the ERROR log</td>
<td>the error, as the ERROR log has it</td>
<td>nothing</td>
</tr>
</tbody>
</table>
<p dir="auto"><code>OnError</code>'s severity is <code>1</code> Critical, <code>2</code> High, <code>3</code> Medium, <code>4</code> Low, and its code is the <code>HM&lt;nnnn&gt;</code> number without the prefix.</p>
<p dir="auto"><strong><code>OnClientValidatePassword</code> setting <code>0</code> is a full authentication bypass</strong> for that account, on every protocol. A handler that bridges to another directory should fail closed: set <code>1</code> when the lookup errors, never <code>0</code>.</p>
<h3><a class="anchor-offset" name="the-objects-a-handler-is-given"></a>The objects a handler is given</h3>
<p dir="auto">The objects an event has are also globals of the same names, <code>HMAILSERVER_CLIENT</code>, <code>HMAILSERVER_MESSAGE</code>, <code>HMAILSERVER_ACCOUNT</code>, <code>HMAILSERVER_FETCHACCOUNT</code>, <code>Result</code> and <code>EventLog</code>, and the parameter and the global are the same object. An object an event does not have is <code>null</code> as 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 a <code>ReferenceError</code>, so a handler reads its parameter.</p>
<p dir="auto"><strong>Do not use <code>Result</code> in the seven events that are notifications.</strong> <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/EventScripts.md" rel="nofollow ugc">EventScripts.md</a> says it is not defined at all in <code>OnClientLogon</code>, <code>OnRecipientUnknown</code>, <code>OnTooManyInvalidCommands</code>, <code>OnDeliveryFailed</code> and <code>OnError</code>; the public documentation adds that the server makes no <code>Result</code> for <code>OnBackupCompleted</code> and <code>OnBackupFailed</code>.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Object</th>
<th>Members</th>
<th>On JavaScript (Linux)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Client</td>
<td><code>Port</code>, <code>IPAddress</code>, <code>Username</code>, <code>HELO</code>, <code>Authenticated</code>, <code>EncryptedConnection</code>, <code>CipherVersion</code>, <code>CipherName</code>, <code>CipherBits</code>, <code>SessionID</code></td>
<td>the same, all read-only</td>
</tr>
<tr>
<td>Message</td>
<td><code>ID</code>, <code>Filename</code>, <code>Subject</code>, <code>From</code>, <code>FromAddress</code>, <code>To</code>, <code>CC</code>, <code>Date</code>, <code>Body</code>, <code>HTMLBody</code>, <code>Charset</code>, <code>Recipients</code>, <code>AddRecipient(name, address)</code>, <code>ClearRecipients()</code>, <code>HeaderValue(name)</code>, <code>HasBodyType(type)</code>, <code>Flag(flag)</code>, <code>Size</code> (KB), <code>State</code>, <code>DeliveryAttempt</code>, <code>UID</code>, <code>InternalDate</code>, <code>Copy(folderId)</code>, <code>RefreshContent()</code>, <code>Save()</code>, <code>Attachments</code>, <code>Headers</code></td>
<td><code>Attachments</code> and <code>Headers</code> are not there; <code>SetHeaderValue(name, value)</code> and <code>SetFlag(flag, on)</code> write; <code>InternalDate</code> is text</td>
</tr>
<tr>
<td>Result</td>
<td><code>Value</code>, <code>Message</code>, <code>Parameter</code></td>
<td><code>Value</code> and <code>Parameter</code> take a whole Number only</td>
</tr>
<tr>
<td>EventLog</td>
<td><code>Write(text)</code></td>
<td>the same</td>
</tr>
<tr>
<td>FetchAccount</td>
<td>the COM <code>FetchAccount</code></td>
<td>readable, not assignable; <code>Password</code>, <code>Save()</code>, <code>DownloadNow()</code> and <code>Delete()</code> are not there</td>
</tr>
<tr>
<td>Account</td>
<td>the COM <code>Account</code>, freshly loaded</td>
<td>the properties that describe the account are readable, not assignable; <code>Password</code>, the collections and every method are not there</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong><code>Save()</code> is what writes a change to disk.</strong> Changes to the subject, the body and the headers are written to the message file by <code>Save()</code>, on both platforms; a handler that does not call it changes nothing on disk. Do not call it in <code>OnSMTPData</code>: 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 in <code>OnAcceptMessage</code>.</p>
<p dir="auto"><strong><code>Result</code> is read after the handler returns.</strong> On JavaScript a string (<code>'1'</code> too), a boolean, <code>null</code>, <code>undefined</code>, <code>NaN</code>, a fraction or an object assigned to <code>Value</code> or <code>Parameter</code> throws a <code>TypeError</code>, and the event goes on with the <code>Result</code> it had. So in <code>OnClientValidatePassword</code>, <code>Result.Value = decisions[user]</code> for a user the table does not have leaves <code>2</code>, the stored password. On Windows, COM turns a boolean, <code>undefined</code>, a fraction and a string of digits into numbers, and <code>undefined</code> into <code>0</code>: the same line in JScript lets that client in.</p>
<p dir="auto"><strong><code>EventLog.Write</code> is the debugger.</strong> It appends a line to <code>hmailserver_events.log</code> in the log directory, and there is no breakpoint.</p>
<h3><a class="anchor-offset" name="a-rule-s-run-function"></a>A rule's Run function</h3>
<p dir="auto">A rule's <em>Run script function</em> action calls the named function as <code>&lt;function&gt;(HMAILSERVER_MESSAGE)</code>, 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 in <code>EventHandlers.js</code>.</p>
<p dir="auto"><strong>Since 6.3.4 the action names the function alone, as a plain identifier</strong>: ASCII letters, digits, <code>_</code> and <code>$</code>, not starting with a digit. <code>StampReceivedDate</code> qualifies; <code>StampReceivedDate(x)</code> does not. On Windows, in VBScript or JScript, any other name is refused as <code>HM5018</code> each 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 a <code>Script Error</code> line, not <code>HM5018</code>.</p>
<h3><a class="anchor-offset" name="a-ported-handler"></a>A ported handler</h3>
<p dir="auto">JScript and JavaScript share the file name, so a JScript file carried from Windows to Linux is the file the JavaScript engine is given. <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/EventScripts.md" rel="nofollow ugc">EventScripts.md</a>'s example starts from VBScript on Windows:</p>
<pre><code class="language-vb">Sub OnAcceptMessage(oClient, oMessage)
   If InStr(oMessage.HeaderValue("X-Spam-Flag"), "YES") &gt; 0 And Not oClient.Authenticated Then
      oMessage.HeaderValue("X-Quarantine") = "yes"
      oMessage.Subject = "[SPAM] " &amp; oMessage.Subject
      oMessage.Save
      EventLog.Write("Tagged spam from " &amp; oClient.IPAddress)
   End If
End Sub
</code></pre>
<p dir="auto">The same handler for a Linux server, in <code>EventHandlers.js</code>:</p>
<pre><code class="language-js">function OnAcceptMessage(client, message) {
   if (message.HeaderValue('X-Spam-Flag').includes('YES') &amp;&amp; !client.Authenticated) {
      message.SetHeaderValue('X-Quarantine', 'yes');
      message.Subject = `[SPAM] ${message.Subject}`;
      message.Save();
      EventLog.Write(`Tagged spam from ${client.IPAddress}`);
   }
}
</code></pre>
<p dir="auto">What changes in a port:</p>
<ul>
<li><strong>Parameterised properties have no assignment.</strong> <code>message.HeaderValue('X') = 'v'</code> becomes <code>message.SetHeaderValue('X', 'v')</code>, and <code>message.Flag(n) = true</code> becomes <code>message.SetFlag(n, true)</code>. The reading forms are unchanged.</li>
<li><strong>Names are case-sensitive and spelled as the IDL spells them.</strong> JScript found <code>message.FileName</code>; JavaScript needs <code>message.Filename</code>.</li>
<li><strong>No <code>CreateObject</code>, no <code>new ActiveXObject</code>.</strong> Both exist on JavaScript only to throw a <code>TypeError</code> that says so.</li>
<li><strong><code>null</code>, not <code>Nothing</code></strong>, for an object an event does not have.</li>
</ul>
<h3><a class="anchor-offset" name="the-limits"></a>The limits</h3>
<p dir="auto"><strong>A fresh engine for every event</strong>, 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.</p>
<p dir="auto"><strong>No file system, no network, no processes on JavaScript.</strong> QuickJS keeps these in <code>quickjs-libc.c</code>, and that file is not in the tree. No module loader is installed, so <code>import()</code> is refused and an <code>import</code> statement is a syntax error; there is no <code>print</code> and no <code>console</code>. What a handler can change is what its objects change: a message's file through <code>Save()</code>, a copy into a folder through <code>Copy()</code>, the event log through <code>EventLog.Write</code>.</p>
<p dir="auto"><strong>On Windows, <code>ScriptAllowedObjects</code> decides what a script may create</strong> with <code>CreateObject</code> or <code>new ActiveXObject</code>. Its shipped value is <code>*</code>, 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.</p>
<p dir="auto"><strong><code>ScriptTimeout</code></strong> is in seconds, with <code>0</code> for no limit. It is kept in the <a href="/topic/28222">settings store</a>: set it with <code>PUT /api/v1/settings/ini/ScriptTimeout</code>, COM <code>Settings.SetIniSetting</code> or the Control Panel, not by editing <code>hMailServer.ini</code>.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Limit</th>
<th>Platform</th>
<th>Value</th>
<th>When a handler reaches it</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ScriptTimeout</code></td>
<td>Windows</td>
<td>60 seconds by default</td>
<td><code>HM5019</code>; the interrupt cannot release a handler blocked inside a COM call</td>
</tr>
<tr>
<td><code>ScriptTimeout</code></td>
<td>Linux</td>
<td>60 seconds by default</td>
<td><code>HM7341</code>; the event goes on with whatever <code>Result</code> the handler had set</td>
</tr>
<tr>
<td>Memory</td>
<td>Linux</td>
<td>64 MB for each run, a constant</td>
<td><code>HM7342</code>; the event goes on</td>
</tr>
<tr>
<td>Stack</td>
<td>Linux</td>
<td>512 KB of the calling thread's stack</td>
<td>a runaway recursion throws an <code>InternalError</code></td>
</tr>
</tbody>
</table>
<p dir="auto">The Linux timeout does <strong>not</strong> stop one call into a built-in that loops in the engine's C code without polling, such as <code>includes</code> or <code>indexOf</code> on an object whose <code>length</code> is huge: do not call an array method on an object whose <code>length</code> the handler has not checked.</p>
<h3><a class="anchor-offset" name="errors-and-where-they-are-written"></a>Errors, and where they are written</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>What</th>
<th>Where</th>
</tr>
</thead>
<tbody>
<tr>
<td>A script that does not compile, or whose top level throws</td>
<td><code>HM5016</code> at start (nothing loaded), <code>HM5710</code> at a reload (the earlier script stays in force)</td>
</tr>
<tr>
<td>A top level that runs past the timeout</td>
<td><code>HM5021</code>, and the load refused</td>
</tr>
<tr>
<td>An exception a handler does not catch</td>
<td>one <code>Script Error</code> line in the ERROR log; the event goes on</td>
</tr>
</tbody>
</table>
<p dir="auto">The load is all or nothing on every engine, so a typo in a reload never switches a working <code>OnClientValidatePassword</code> off.</p>
<h3><a class="anchor-offset" name="the-6.3.4-security-fix-cve-2026-100741"></a>The 6.3.4 security fix: CVE-2026-100741</h3>
<p dir="auto"><strong>Up to 6.3.3 a password could run code inside the server.</strong> On Windows, with event scripting on, the language JScript and an <code>OnClientValidatePassword</code> handler in <code>EventHandlers.js</code>, any client that could reach a sign-in and name an existing, active account could have JScript of its own run inside <code>hMailServer.exe</code>, 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. With <code>ScriptAllowedObjects</code> at its shipped <code>*</code>, that script could create any COM object, <code>WScript.Shell</code> among them.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Path</th>
<th>Who could use it</th>
<th>Open in</th>
</tr>
</thead>
<tbody>
<tr>
<td>The password given to <code>OnClientValidatePassword</code></td>
<td>any client that can reach a sign-in</td>
<td>JScript</td>
</tr>
<tr>
<td>The UID given to <code>OnExternalAccountDownload</code></td>
<td>a remote POP3 server</td>
<td>JScript</td>
</tr>
<tr>
<td>The error text and failed recipient given to <code>OnDeliveryFailed</code></td>
<td>a remote mail server</td>
<td>JScript</td>
</tr>
<tr>
<td>The name in a rule's <em>Run function</em> action</td>
<td>whoever may edit a rule, a domain's own administrator included</td>
<td>JScript and VBScript</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>Which servers are affected.</strong> 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 <em>Run function</em> action wrote its function name into the call unescaped, in either language, so VBScript servers too.</p>
<p dir="auto"><strong>What 6.3.4 changes.</strong> Every JScript value is escaped in one helper, <code>ScriptServer::EscapeForJScriptLiteral</code>: every backslash first, then both quotes, CR, LF, U+2028, U+2029 and NUL; <code>OnBackupFailed</code>'s reason, which neither language escaped and the table above does not list, is escaped in both. A rule's <em>Run function</em> 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.</p>
<p dir="auto"><strong>Until you can upgrade, switch event scripting off.</strong> 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:</p>
<ul>
<li>Take the <code>OnClientValidatePassword</code>, <code>OnExternalAccountDownload</code> and <code>OnDeliveryFailed</code> handlers out of a JScript <code>EventHandlers.js</code> and reload the scripts. Check the reload: one that fails (<code>HM5710</code> in the ERROR log) keeps the earlier script in force, those handlers included.</li>
<li>In either language, let nobody but the server administrator make or edit a rule with a <em>Run function</em> action. <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Roles.md" rel="nofollow ugc">Roles.md</a> 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.</li>
<li>Narrow <code>ScriptAllowedObjects</code> from <code>*</code>, 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.</li>
</ul>
<p dir="auto">Moving a handler to VBScript closes the password, UID and error-text paths and leaves the rule path open.</p>
<p dir="auto">Before you upgrade, check your rules' <em>Run function</em> actions and put the function's bare name in any that does not qualify. After it, an <code>HM5018</code> in the ERROR log (on Linux, a <code>Script Error</code> line naming the function) means one was missed. <a href="/topic/28193">Upgrading to 6.3.4</a> covers the upgrade itself.</p>
<p dir="auto"><strong>Reporting a vulnerability</strong> goes through a <a href="https://gitlab.com/hmailserver/hmailserver/-/issues/new?issue%5Bconfidential%5D=true" rel="nofollow ugc">confidential GitLab issue</a> or the Service Desk address <code>contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com</code>, and not through a public issue or this forum.</p>
<h3><a class="anchor-offset" name="what-it-does-not-do"></a>What it does not do</h3>
<ul>
<li><code>OnClientValidatePassword</code> is not consulted for SCRAM, which has no password to give it.</li>
</ul>
<h3><a class="anchor-offset" name="where-to-read-more"></a>Where to read more</h3>
<ul>
<li><a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/EventScripts.md" rel="nofollow ugc">EventScripts.md</a>: the member-by-member contract of every object, and the engine.</li>
<li><a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/The-COM-API-and-Scripting" rel="nofollow ugc">The COM API and Scripting</a>, sections 23.6 to 23.10.</li>
<li><a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Rules-and-Sieve" rel="nofollow ugc">Rules and Sieve</a> for the <em>Run script function</em> action, and <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Security-Hardening" rel="nofollow ugc">Security Hardening</a>, section 17.11.</li>
<li>The public documentation's <a href="https://www.progressiverobot.com/hmailserver-documentation/#28-event-scripts-the-complete-reference" rel="nofollow ugc">event scripts chapter</a>.</li>
<li>On this forum: <a href="/topic/28189">hMailServer on Linux: what you get and what you do not</a>, <a href="/topic/28218">the 6.3.4 security announcement</a>, <a href="/topic/28262">the COM API on Windows</a> and <a href="/topic/28268">rules and the Run function action</a>.</li>
</ul>
]]></description><link>https://www.hmailserver.co.uk/topic/28261/event-scripts-every-event-and-its-arguments-the-limits-and-the-6.3.4-security-fix</link><generator>RSS for Node</generator><lastBuildDate>Fri, 06 Nov 2026 09:05:49 GMT</lastBuildDate><atom:link href="https://www.hmailserver.co.uk/topic/28261.rss" rel="self" type="application/rss+xml"/><pubDate>Sun, 27 Sep 2026 19:43:13 GMT</pubDate><ttl>60</ttl></channel></rss>