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— alwaysSTSv1.mode—enforce,testing, ornone. 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 (underenforce).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:
- Certificate expired on the
mta-sts.{domain}host. The HTTPS fetch fails the TLS handshake. Renew the cert. - 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. - File not served. The host exists, the cert is valid, but
/.well-known/mta-sts.txtreturns 404. Path issue or wrong static-file root. - 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:
- 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. - A static file on Cloudflare Pages or similar. Free, valid cert, low maintenance. Good for clients without an existing web property to attach to.
- 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: enforceis the only pass.testingandnoneare 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
