MTA-STS — the Policy File

MTA-STS — the policy file

The DNS record at _mta-sts.{domain} tells senders the policy exists. The policy file at https://mta-sts.{domain}/.well-known/mta-sts.txt tells them what to do. Cathedra fetches this file over HTTPS, parses it, and produces a finding based on the mode: field.

This is the only Cathedra check that does anything outside DNS. Understanding the failure modes matters because half the MTA-STS issues we see in the field are policy-file problems, not DNS problems.

What the policy file contains

A minimal policy file:

version: STSv1
mode: enforce
mx: *.mail.protection.outlook.com
max_age: 86400

The fields:

  • version — always STSv1.
  • mode — enforce, testing, or none. This is the field Cathedra branches on.
  • mx — one or more lines listing the allowed MX hostnames or wildcard patterns. Senders compare the actual MX hostname they’re connecting to against this list; if it does not match, the connection is refused (under enforce).
  • max_age — how long, in seconds, senders may cache the policy. 86,400 (1 day) is conservative; values up to 31,536,000 (1 year) appear in production. Higher caching is better for stability — once the policy is fetched, an attacker can’t trick the sender into re-fetching a modified version mid-attack.

For multiple MX hosts, the mx: field repeats:

mx: aspmx.l.google.com
mx: alt1.aspmx.l.google.com
mx: alt2.aspmx.l.google.com
mx: alt3.aspmx.l.google.com
mx: alt4.aspmx.l.google.com

What Cathedra does

MtaStsCheck.php runs the policy fetch only when the DNS record (Lesson 6.1) returned a v=STSv1 value. The code:

$response = Http::timeout(5)->get("https://mta-sts.{$domain}/.well-known/mta-sts.txt");

Five-second timeout. If the response is successful, the body is parsed for a mode: line. If not, an error finding is emitted.

The three pass-side outcomes

mode: enforce → pass

mode: enforce
Field Value
status pass
severity —
finding “MTA-STS policy file fetched. Mode: enforce.”
recommendation —

This is the only mode that produces a pass. Senders are instructed to refuse to deliver when TLS or MX matching fails. The policy is operational.

mode: testing → warning

mode: testing
Field Value
status warning
severity low
finding “MTA-STS policy file fetched. Mode: testing.”
recommendation “Upgrade MTA-STS mode to “enforce” once you have verified TLS is working for all MX hosts.”

Testing mode means senders should attempt to follow the policy and report failures via TLS-RPT (next lesson), but should not actually refuse delivery when something fails. It is the MTA-STS equivalent of p=none in DMARC — observe, do not enforce.

The severity is low because testing is a legitimate transitional state. If you are mid-deployment, this is what you want to see for a few weeks before flipping to enforce.

mode: none → warning (same severity, same status)

mode: none

Same status, same severity, same recommendation as testing. Cathedra’s code treats anything that is not enforce as warning. none is functionally the same as not having MTA-STS at all from a protection standpoint, but the operator has explicitly published the intent to disable enforcement — usually because they had it on and turned it off for a reason. Treat this finding as a flag to ask why.

The error side — fetch failures

When the DNS record exists but the policy file cannot be fetched, Cathedra emits:

Field Value
status error
severity medium
finding “MTA-STS DNS record exists but policy file at https://mta-sts.{domain}/.well-known/mta-sts.txt returned HTTP {code}.” (when HTTP status is not 2xx)
recommendation “Ensure the policy file is served correctly.”

Or, when the fetch throws (DNS resolution failure, TLS handshake failure, timeout):

Field Value
status error
severity medium
finding “MTA-STS DNS record exists but the policy file at https://mta-sts.{domain} could not be fetched.”
recommendation “Verify the policy file endpoint is publicly accessible.”

This is error / medium because the state is genuinely broken. The DNS record is telling senders to look for a policy. The senders look. The policy is unreachable. Different senders handle this differently — some treat it as a soft fail and deliver anyway; some treat it as no-policy; some retry. The behavior is not specified by RFC 8461 strictly enough to be predictable.

The common causes, in order of frequency we see them:

  1. Certificate expired on the mta-sts.{domain} host. The HTTPS fetch fails the TLS handshake. Renew the cert.
  2. DNS record for mta-sts.{domain} is missing. The TXT record is fine, but no A/CNAME exists for the host that serves the file. Add an A or CNAME pointing to whatever serves the static file.
  3. File not served. The host exists, the cert is valid, but /.well-known/mta-sts.txt returns 404. Path issue or wrong static-file root.
  4. File has wrong content type. Less common but happens. Should be text/plain. Some clients are strict about this.

How clients usually host the policy

Three patterns we see for serving mta-sts.{domain}/.well-known/mta-sts.txt:

  1. A CNAME from mta-sts.{client-domain} to a hosted MTA-STS service. Easiest. Several SaaS vendors offer this; the policy is managed in their dashboard, the file is served on their infrastructure with a valid cert, and the client only manages DNS.
  2. A static file on Cloudflare Pages or similar. Free, valid cert, low maintenance. Good for clients without an existing web property to attach to.
  3. A path on the client’s existing web infrastructure. Most common for clients running their own site — same web server, additional vhost or path.

Pick based on what infrastructure already exists. Do not stand up a new server just for MTA-STS.

Cheat sheet

Condition Cathedra status Severity Finding fragment
mode: enforce pass — “Mode: enforce.”
mode: testing warning low “Mode: testing.”
mode: none warning low “Mode: none.”
HTTP non-2xx response error medium “…returned HTTP {code}.”
Fetch exception (timeout, TLS, DNS) error medium “…could not be fetched.”

What you should remember

  • Policy file lives at https://mta-sts.{domain}/.well-known/mta-sts.txt. Cathedra fetches over HTTPS with a 5-second timeout.
  • mode: enforce is the only pass. testing and none are warnings at low severity.
  • A DNS record without a working policy file is error / medium. This is the worst MTA-STS state — worse than not deploying at all.
  • The common breakage is TLS cert expiry on the mta-sts.{domain} host. Watch cert renewals.
  • The mta-sts.{domain} host is separate from the underscored _mta-sts.{domain} TXT record. Both must work.

Cathedra alignment: MtaStsCheck.php — second stage of the check, the HTTPS fetch of the policy file and the mode field