Check D6 is worth two points, carries a Low effort rating, and is satisfied by a plain text file of five lines served at a fixed path. It is the cheapest work in the trust pillar, and the only way to fail it after publishing is to let one date pass. This post covers what the standard asks for, how the scanner reads the file, and the one detail that turns a correct file into a failing one twelve months after nobody has touched it.
What RFC 9116 asks for
RFC 9116 defines security.txt as a machine-readable statement of how to report a security issue to a site's operator. The file lives at /.well-known/security.txt, is served over HTTPS as text/plain in UTF-8, and consists of one field per line in the form Name: value. Lines starting with # are comments. A field is either required or optional, and two are required.
Contact is where a report should go. Its value is a URI, so it is mailto:, tel: or https: rather than a bare address, and the field may appear more than once, in order of preference.
Expires is the date and time after which the file should be treated as stale. It is an Internet date-time, the format 2027-10-19T00:00:00Z follows, and it must appear exactly once. The RFC recommends setting it less than a year into the future.
The optional fields are worth knowing because two of them cost nothing. Preferred-Languages lists the languages a report can be written in. Canonical gives the URL the file considers its own, which matters if the file is ever mirrored or signed. Policy links to a disclosure policy, Encryption to a public key, Acknowledgments to a page crediting reporters, and Hiring to security roles. The site at securitytxt.org has a generator that walks through all of them.
The field people forget
Expires is the one that catches people out, and it does so in a specific way. A security.txt is almost always written once, by someone tidying up a launch checklist, with a date a year ahead because a year sounded sensible. Then it is never opened again. The address in Contact still works, the policy it links still exists, and the file has become non-conformant, because a security.txt with an Expires in the past is not a valid security.txt at all.
Nothing on the site breaks when that happens. No monitor fires, no browser complains, and no one on the team has a reason to look at a file that has been fine for a year. It is the purest example of the failure mode the trust pillar is built around, which is a signal that was correct when written and has decayed without producing an error.
How D6 reads the file
The crawler fetches /.well-known/security.txt on the origin it was asked to scan, following redirects, in the same pass as robots.txt, llms.txt and the sitemap. It does not try the legacy top-level /security.txt location; the well-known path is the one the RFC requires, and it is the only one checked.
The check then does three things, and the evidence block on the report records the result of each.
If there is no response or the status is anything other than 200, D6 scores zero, the evidence shows present: false and the status received, and the fix text says exactly what to publish.
If the file is present, the check looks for a line beginning Contact: followed by a non-empty value, and a line beginning Expires: followed by a value it can parse as a date. Both matches are case-insensitive and may appear on any line. The parsed date must be in the future at the moment of the scan.
If both conditions hold, the check awards the full two points and records hasContact, the expires value it read, and valid: true. If either fails, the file is present but invalid, and the check awards one point, records valid: false, and names the problem: no Contact field, no Expires field, or an Expires in the past with the date it found. The fix text on that last case is deliberate about the point above, because the file usually was correct when it was written.
That is the whole check. It does not validate the optional fields, does not require a signature, and does not fetch the Policy or Canonical URLs. Everything beyond Contact and a future Expires is for the human reading the file, not for the scanner.
A complete example
This is a conformant file. Replace the address, the dates and the host and it is yours.
Contact: mailto:security@example.com
Expires: 2027-10-01T00:00:00Z
Preferred-Languages: en
Canonical: https://example.com/.well-known/security.txt
Policy: https://example.com/security-policy
# Please report vulnerabilities in this site to the address above.
# We acknowledge reports within two business days.
Serve it as text/plain; charset=utf-8 at /.well-known/security.txt over HTTPS. On a static host that is a file in a .well-known directory. On most frameworks it is a route, and on some it is a route plus a rewrite, for the reason below.
How we serve ours
Ours is at /.well-known/security.txt, and it is generated rather than stored, for two reasons.
The first is the framework. Next.js will not route a path segment that begins with a dot, so a file at app/.well-known/security.txt is never served. The handler lives at /well-known/security instead, without the dot, and a rewrite in next.config.ts maps the canonical path onto it. Anyone fetching the RFC's path gets the file; nobody sees the internal one.
The second is Expires. The handler computes it at request time as one year ahead, rounded down to midnight UTC, which keeps it just under the RFC's recommended maximum and means it cannot lapse. The response is cached for an hour, so the date moves forward by a day at most once a day, and a scan on any day of the year finds a future date. The route also emits Preferred-Languages, Canonical and a Policy line pointing at our terms, with two comment lines saying what we consider in scope and how quickly we acknowledge a report.
A rolling expiry is not the only correct answer. A static file with a calendar reminder to bump the date every ten months works just as well, provided the reminder is real. What does not work is the file everyone actually has, written once with a date that felt distant at the time.
Where this sits among the fixes
Two points is not many, and D6 is the smallest check in the trust pillar. It is in the rubric because of what it demonstrates: a site that publishes a valid, current security.txt has a named person behind it, a working address, and someone who has thought about what happens when a stranger finds a problem. The three tiers of fixes puts this in the first tier, the hours-not-weeks category, and it is the one item in that tier that most sites have simply never done.
Check your own site
Run the free scan and read the D6 evidence block. It will tell you whether the file is present, which fields it found, and the exact Expires value it parsed. The check definition, alongside the rest of the trust pillar, is on the methodology page.