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
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
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:
-
Format must be ISO 8601 / RFC 3339:
YYYY-MM-DDTHH:MM:SS.sssZ. WritingExpires: 2027-01-01or human dates causes parser exceptions. - Expired files are dead files: Once the timestamp passes, automated scanners and bounty registries treat the policy as void.
- 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
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;
}
To resolve this in Nginx, add an explicit exception before any generic dot-file block:
location ^~ /.well-known/ {
allow all;
default_type text/plain;
}
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-----
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
5. Serving Incorrect MIME Types and Missing Cleartext Signatures
To ensure parsers interpret the file properly:
- The HTTP
Content-Typeresponse header must betext/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
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
Verification Checklist
Before deploying:
- [ ] Accessible at
https://yourdomain.com/.well-known/security.txtreturning HTTP 200. - [ ] Served with
Content-Type: text/plain; charset=utf-8. - [ ]
Contact:includesmailto:orhttps://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)