DEV Community

Rasika Dangamuwa
Rasika Dangamuwa

Posted on

Why Your security.txt Fails RFC 9116 Compliance: 5 Traps Security Teams Overlook

When ethical hackers or bug bounty researchers discover a critical vulnerability in your web infrastructure, how do they find you? Without an easy vulnerability disclosure channel, reports get dumped into generic support desks, sales queues, or worse, published directly to social media.

That is why the IETF standardized security.txt under RFC 9116. It acts as robots.txt for security researchers, providing a machine-readable file with contact channels, PGP encryption keys, disclosure policies, and acknowledgment pages.

However, a surprising number of production security.txt files fail RFC 9116 compliance. When automated security crawlers or bug bounty platforms evaluate non-compliant files, they flag the file as broken or ignore it entirely.

Here are the five most common RFC 9116 implementation traps and how to fix them.


1. The Missing URI Scheme in Contact: Directives

Under RFC 9116 §2.5.3, the Contact: directive must be a valid URI. A common mistake is writing plain email addresses or non-schemed endpoints:

# INVALID - Parser failure
Contact: security@example.com
Contact: example.com/security/report
Enter fullscreen mode Exit fullscreen mode

Parsers cannot reliably differentiate between plain text strings, phone numbers, or web forms without an explicit URI scheme:

# COMPLIANT - Explicit URI schemes
Contact: mailto:security@example.com
Contact: https://example.com/security/report
Contact: tel:+1-555-0199
Enter fullscreen mode Exit fullscreen mode

Always include mailto:, https://, or tel:.


2. The Mandatory Expires: Trap & Date Parsing Failures

In RFC 9116 §2.5.5, Expires: is strictly mandatory. If your security.txt lacks an Expires: header, parsers treat the file as invalid. Furthermore:

  1. Format must be ISO 8601 / RFC 3339: YYYY-MM-DDTHH:MM:SS.sssZ. Writing Expires: 2027-01-01 or human dates causes parser exceptions.
  2. Expired files are dead files: Once the timestamp passes, automated scanners and bounty registries treat the policy as void.
  3. Overly distant expirations: The RFC strongly recommends not setting expiration dates more than one year into the future to ensure contact info stays maintained.
# COMPLIANT
Expires: 2027-08-28T00:00:00.000Z
Enter fullscreen mode Exit fullscreen mode

If you are setting up or auditing your configuration, you can use Nutilz security.txt Generator to generate compliant ISO timestamps, validate existing directives, and verify syntax against RFC 9116 rules.


3. Location and Dot-File Web Server Blocks

RFC 9116 specifies that security.txt must be located in the /.well-known/ path:
https://example.com/.well-known/security.txt

While placing a fallback or redirect at the root /security.txt is permitted for legacy compatibility, hosting it only at /security.txt breaks RFC discovery.

More critically, many web servers (such as Nginx or Apache) include default rules that block access to hidden files and directories starting with a period (.):

# Dangerous default rule that breaks /.well-known/
location ~ /\. {
    deny all;
}
Enter fullscreen mode Exit fullscreen mode

To resolve this in Nginx, add an explicit exception before any generic dot-file block:

location ^~ /.well-known/ {
    allow all;
    default_type text/plain;
}
Enter fullscreen mode Exit fullscreen mode

4. Direct PGP Key Pasting vs URI Endpoints

The Encryption: directive (RFC 9116 §2.5.4) allows security researchers to encrypt sensitive bug submissions using your team's PGP public key.

A frequent error is pasting the raw OpenPGP ASCII-armored key directly into security.txt:

# INVALID - Breaks line-oriented key-value parsing
Encryption: -----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBF...
-----END PGP PUBLIC KEY BLOCK-----
Enter fullscreen mode Exit fullscreen mode

The Encryption: directive expects a URI pointing to where the public key is hosted (or a fingerprint URI):

# COMPLIANT
Encryption: https://example.com/pgp-key.txt
Encryption: dns:pgpkey.example.com?type=OPENPGPKEY
Enter fullscreen mode Exit fullscreen mode

5. Serving Incorrect MIME Types and Missing Cleartext Signatures

To ensure parsers interpret the file properly:

  • The HTTP Content-Type response header must be text/plain; charset=utf-8.
  • If you sign the file using PGP to prevent tampering, do not use detached signatures. Use an RFC 4880 OpenPGP Cleartext Signature:
gpg --clear-sign -u security@example.com security.txt
Enter fullscreen mode Exit fullscreen mode

This outputs a signed file with -----BEGIN PGP SIGNED MESSAGE----- wrapping your original directives, preserving machine readability while verifying authenticity.


A Complete RFC 9116 Template

Here is a compliant /.well-known/security.txt:

# security.txt - RFC 9116 Compliant
Contact: mailto:security@example.com
Contact: https://example.com/security/report
Expires: 2027-08-28T00:00:00.000Z
Encryption: https://example.com/pgp-key.txt
Canonical: https://example.com/.well-known/security.txt
Policy: https://example.com/security-policy
Acknowledgments: https://example.com/hall-of-fame
Preferred-Languages: en, es
Enter fullscreen mode Exit fullscreen mode

Verification Checklist

Before deploying:

  • [ ] Accessible at https://yourdomain.com/.well-known/security.txt returning HTTP 200.
  • [ ] Served with Content-Type: text/plain; charset=utf-8.
  • [ ] Contact: includes mailto: or https:// URI schemes.
  • [ ] Expires: is present, formatted as ISO 8601, and set to a future date (<= 1 year).
  • [ ] Validate your deployed syntax using Nutilz security.txt Generator & Validator or curl headers directly (curl -i https://yourdomain.com/.well-known/security.txt).

Top comments (0)