From 97f8c47b033e3c0a29ebb342c56b75e3ddd57ebf Mon Sep 17 00:00:00 2001 From: Lewis Gibson <12851394+lewisgibson@users.noreply.github.com> Date: Tue, 15 Sep 2026 14:30:00 +0100 Subject: [PATCH] docs(email): explain removal of forged results --- content/momentum/4/4-lua-summary-table.md | 3 +- content/momentum/4/inbound-smtp.md | 4 +- content/momentum/4/lua/index.md | 3 +- ...-validate-authentication-results-remove.md | 40 +++++++++++++++++++ content/momentum/4/using-dkim-validation.md | 36 +++++++++++++++-- 5 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 content/momentum/4/lua/ref-msys-validate-authentication-results-remove.md diff --git a/content/momentum/4/4-lua-summary-table.md b/content/momentum/4/4-lua-summary-table.md index b0803e9ae..f99a67f75 100644 --- a/content/momentum/4/4-lua-summary-table.md +++ b/content/momentum/4/4-lua-summary-table.md @@ -1,5 +1,5 @@ --- -lastUpdated: "08/11/2026" +lastUpdated: "09/15/2026" title: "Lua Functions Summary" description: "This section contains tables of Lua functions Click the function name for details Table 64 1 Lua functions all Function Description Params Package Version Phases ac esmtp capability add Add a capability to the EHLO response name msys extended ac 4 0 connect ehlo ac esmtp capability remove Removes a..." --- @@ -172,6 +172,7 @@ This section contains tables of Lua functions. Click the function name for detai | [msys.tls_params.set](/momentum/4/lua/ref-msys-tls-params-set) - Set a tls parameter string on a per connection basis | tls_params, param, val | msys.tls_params | 4.4 | any | | [msys.type](/momentum/4/lua/ref-msys-type) – Return the type name of the supplied parameter | parameter | msys | 4.0 | any | | [msys.unlock](/momentum/4/lua/ref-msys-unlock) – Releases a lock obtained via msys.lock | mutexname | msys | 4.0 | any | +| [msys.validate.authentication_results.remove](/momentum/4/lua/ref-msys-validate-authentication-results-remove) - Remove incoming authentication results claiming a local identity | msg, [authservid] | msys.validate.authentication_results | 5.4 | data, data_spool, data_spool_each_rcpt | | [msys.validate.dk.get_responsible_domain](/momentum/4/lua/ref-msys-validate-dk-get-responsible-domain) – This function requires module "dk_validate". "msg" is a mail message. "ctx" is the validation context. It returns the responsible domain for the current message | msg, ctx | msys.validate.dk | 4.0 | data, data_spool, data_spool_each_rcpt | | [msys.validate.dk.sign](/momentum/4/lua/ref-msys-validate-dk-sign) – Sign a message using a Domain Key | msg, ctx, options | msys.validate.dk | 4.0 | core_data_validation | | [msys.validate.openarc.sign](/momentum/4/lua/ref-msys-validate-openarc-sign) – Sign a message using OpenARC | msg, options, [ar] | msys.validate.openarc | 5.0 | core_post_final_validation | diff --git a/content/momentum/4/inbound-smtp.md b/content/momentum/4/inbound-smtp.md index bfba64692..a9f9c48c2 100644 --- a/content/momentum/4/inbound-smtp.md +++ b/content/momentum/4/inbound-smtp.md @@ -1,5 +1,5 @@ --- -lastUpdated: "03/26/2020" +lastUpdated: "09/15/2026" title: "ESMTP_Listener Authentication" description: "Momentum can be configured to perform authentication on SMTP connections It provides out of the box support for authenticating remote users against a flat file using CRAM MD 5 DIGEST MD 5 and LOGIN authentication extensions to SMTP Additional authentication back ends can be plugged into Momentum in the form..." --- @@ -18,6 +18,8 @@ In addition, the SMTP session will be marked as relayable, setting the connectio These results of the authentication attempt will be used to generate an `Authentication-Results:` header in the mail before it is delivered from Momentum. +A sender can supply its own `Authentication-Results` fields. Before publishing local results, an inbound Lua policy should remove fields that claim the local authentication service identity. See [Removing incoming authentication results](/momentum/4/using-dkim-validation#removing-incoming-authentication-results) for policy ordering and trusted relay handling. + ### Note Hosts are not subject to any SMTP authentication that you may have implemented when specified by the [`relay_hosts`](/momentum/4/config/ref-relay-hosts) option or when [`open_relay`](/momentum/4/config/ref-open-relay) is set to "true". diff --git a/content/momentum/4/lua/index.md b/content/momentum/4/lua/index.md index 3bdeeb67f..98b028d6a 100644 --- a/content/momentum/4/lua/index.md +++ b/content/momentum/4/lua/index.md @@ -1,5 +1,5 @@ --- -lastUpdated: "08/11/2026" +lastUpdated: "09/15/2026" title: "Category File" type: "custom" name: "Lua Functions Reference" @@ -189,6 +189,7 @@ description: "This section details all Lua functions Functions are ordered alpha | [msys.timer.at](/momentum/4/lua/ref-msys-timer-at) | execute closure at a given time | | [msys.timer.every](/momentum/4/lua/ref-msys-timer-every) | execute closure every interval | | [msys.tls_params.set](/momentum/4/lua/ref-msys-tls-params-set) | Set a tls parameter string on a per connection basis | +| [msys.validate.authentication_results.remove](/momentum/4/lua/ref-msys-validate-authentication-results-remove) | Remove incoming authentication results claiming a local identity | | [msys.validate.dk.get_responsible_domain](/momentum/4/lua/ref-msys-validate-dk-get-responsible-domain) | Return the domain responsible for the current message | | [msys.validate.dk.sign](/momentum/4/lua/ref-msys-validate-dk-sign) | Sign a message using a Domain Key | | [msys.validate.openarc.sign](/momentum/4/lua/ref-msys-validate-openarc-sign) | Sign a message using OpenARC | diff --git a/content/momentum/4/lua/ref-msys-validate-authentication-results-remove.md b/content/momentum/4/lua/ref-msys-validate-authentication-results-remove.md new file mode 100644 index 000000000..39d096d58 --- /dev/null +++ b/content/momentum/4/lua/ref-msys-validate-authentication-results-remove.md @@ -0,0 +1,40 @@ +--- +lastUpdated: "09/15/2026" +title: "msys.validate.authentication_results.remove" +description: "Remove incoming Authentication-Results fields that claim to come from the local authentication service before publishing local results." +--- + + + +## Name + +msys.validate.authentication_results.remove - Remove incoming authentication results claiming a local identity + +## Synopsis + +`msys.validate.authentication_results.remove(msg, authservid)` + +`msg: userdata, ec_message type` + +`authservid: string, optional` + +## Description + +This function removes every `Authentication-Results` field whose leading authentication service identifier matches `authservid`. If omitted, the identifier is `external_hostname`, falling back to [hostname](/momentum/4/config/ref-hostname). A supplied identifier must be a nonempty string and should be the same identifier your policy uses when publishing its results. + +Matching ignores case, recognizes equivalent internationalized domain names, and handles quoted identifiers, comments, folded whitespace, and RFC2047-encoded values. Fields naming other services keep their original position and encoding. `ARC-Authentication-Results` fields are retained. Retaining a field does not establish trust in its contents. + +Call this function in the inbound `validate_data` hook after DKIM and ARC verification and before adding local authentication result headers. An incoming signature may cover the fields being removed, so verification must finish first. Repeated calls also remove any matching local results added since the previous call. + +```lua +local authentication_results = require("msys.validate.authentication_results") +authentication_results.remove(msg) +``` + +The function returns no values. Loading the module does not register a policy hook. See [Removing incoming authentication results](/momentum/4/using-dkim-validation#removing-incoming-authentication-results) for trusted relay handling and the cloud policy setting. + +Available starting in Momentum 5.4. + +## See Also + +[DKIM Validation](/momentum/4/using-dkim-validation), [msys.validate.opendkim.verify](/momentum/4/lua/ref-msys-validate-opendkim-verify), [msg:header](/momentum/4/lua/ref-header) diff --git a/content/momentum/4/using-dkim-validation.md b/content/momentum/4/using-dkim-validation.md index f761a162b..9025fc183 100644 --- a/content/momentum/4/using-dkim-validation.md +++ b/content/momentum/4/using-dkim-validation.md @@ -1,9 +1,37 @@ --- -lastUpdated: "03/26/2020" +lastUpdated: "09/15/2026" title: "DKIM Validation" -description: "When DKIM is enabled as decribed in Section 23 1 DKIM Signing DKIM signature verification is performed on all inbound messages received via SMTP Unlike DKIM signing verification of DKIM messages is driven only through Lua policy Links to the appropriate Lua functions are listed at Section 71 50 2..." +description: "When DKIM is enabled as described in Section 23 1 DKIM Signing DKIM signature verification is performed on all inbound messages received via SMTP Unlike DKIM signing verification of DKIM messages is driven only through Lua policy Links to the appropriate Lua functions are listed at Section 71 50 2..." --- -When DKIM is enabled as decribed in [“DKIM Signing”](/momentum/4/using-dkim#using_dkim.signing), DKIM signature verification is performed on all inbound messages received via SMTP. Unlike DKIM signing, verification of DKIM messages is driven only through Lua policy. Links to the appropriate Lua functions are listed at [“Lua Functions”](/momentum/4/modules/opendkim#modules.opendkim.lua.functions). +When DKIM is enabled as described in [“DKIM Signing”](/momentum/4/using-dkim#using_dkim.signing), DKIM signature verification is performed on all inbound messages received via SMTP. Unlike DKIM signing, verification of DKIM messages is driven only through Lua policy. Links to the appropriate Lua functions are listed at [“Lua Functions”](/momentum/4/modules/opendkim#modules.opendkim.lua.functions). -When a message is received, an attempt is made to locate a DKIM-Signature header. If found, the header is parsed for format and content. If the header is valid, the signature value is extracted from the header and the appropriate DNS operations are performed to find the public key for the signer. The message is then canonicalized as indicated by the signature header. Canonicalization includes all headers listed in the signature header, the body of the message, and the signature header itself. The canonicalized message is digested for verification as indicated by the signature header using the retrieved public key and the signature value. The results "dkim=pass" or "dkim=*`reason for failure`* " are included in an Authentication-Results header prepended to the message. If the message does not contain a DKIM-Signature header, either no Authentication-Results header will be prepended to the message or DKIM results will not appear in an Authentication-Results header prepended because of the actions of a different validation action. \ No newline at end of file +When a message is received, an attempt is made to locate a DKIM-Signature header. If found, the header is parsed for format and content. If the header is valid, the signature value is extracted from the header and the appropriate DNS operations are performed to find the public key for the signer. The message is then canonicalized as indicated by the signature header. Canonicalization includes all headers listed in the signature header, the body of the message, and the signature header itself. The canonicalized message is digested for verification as indicated by the signature header using the retrieved public key and the signature value. The results "dkim=pass" or "dkim=*`reason for failure`* " are included in an Authentication-Results header prepended to the message. If the message does not contain a DKIM-Signature header, either no Authentication-Results header will be prepended to the message or DKIM results will not appear in an Authentication-Results header prepended because of the actions of a different validation action. + +## Removing incoming authentication results + +An incoming message can contain `Authentication-Results: mx.example.net; spf=pass` even when `mx.example.net` has never seen it. A policy that publishes results as `mx.example.net` must remove that incoming claim before adding its own results. Headers naming other authentication services do not establish trust merely because they are retained. + +Load the [msys.validate.authentication_results.remove](/momentum/4/lua/ref-msys-validate-authentication-results-remove) helper in your Lua policy (available starting in Momentum 5.4): + +```lua +local authentication_results = require("msys.validate.authentication_results") +``` + +In the inbound `validate_data` hook, finish DKIM and ARC verification against the original message, then call the helper once, before adding any local authentication result headers: + +```lua +authentication_results.remove(msg) +``` + +Verification comes first because an incoming signature may cover an `Authentication-Results` field. Cleanup removes every field whose leading authentication service identifier matches `external_hostname`, falling back to [hostname](/momentum/4/config/ref-hostname) when it is unset. Matching ignores case, recognizes equivalent internationalized domain names, and accepts quoted identifiers, comments, and folded whitespace. Encoded values are checked too, so a claim cannot become local only after the webhook decodes it. Fields naming other services keep their original order and encoding. `ARC-Authentication-Results` fields are retained. If your policy publishes results under a different identifier, pass that exact identifier as the second argument. + +On a trusted internal relay, skip this cleanup only when the preceding trusted hop has already removed forged claims. Decide that from trusted connection information, never from a header supplied by the sender. Using the same identifier on multiple internal hops otherwise causes a later hop to remove the preceding hop's results. Cleanup is opt-in for custom on-premises policies; loading this helper does not register a hook. + +The cloud inbound relay webhook policy performs cleanup by default for messages it validates. Its SPF and DKIM results both use `external_hostname`, with the same `hostname` fallback. A deployment that accepts mail only from trusted relays that already perform cleanup can preserve their results by setting this in `sparkpost_site_config.lua`: + +```lua +msys.sparkpost.config.relay_webhook.remove_local_authentication_results = false +``` + +The default is `true`. This option controls removal of incoming local results; SPF and DKIM validation still run. Outbound messages are outside this policy's cleanup scope.