teufels / tt3-wellknown
[teufels] well-known - Serve the well-known resources a TYPO3 site should provide, maintained in the site configuration
Package info
bitbucket.org/teufels/tt3-wellknown
Type:typo3-cms-extension
pkg:composer/teufels/tt3-wellknown
Requires
- php: ^8.2
- typo3/cms-core: ^13.4 || ^14.3
- typo3/cms-frontend: ^13.4 || ^14.3
Requires (Dev)
None
Suggests
- typo3/cms-reports: Shows the well-known resources status of every site in System > Reports > Status Report
Provides
None
Conflicts
None
Replaces
None
README
[ ṯeufels ] Well-Known
Serve the /.well-known/ resources a TYPO3 site should provide, maintained in the site configuration
Supported resources:
/.well-known/security.txt(RFC 9116)
This version supports TYPO3
Composer support
composer req teufels/tt3-wellknown
Quick Start
Minimum to do after installation:
composer req teufels/tt3-wellknownvendor/bin/typo3 upgrade:run tt3Wellknown_securityTxtDefault(writes the default text into every site)- Backend: Site Management > Sites > your site > tab Well-Known
- Maintain
Contact: remove the#and enter a real contact, e.g.Contact: mailto:security@your-domain.com - Save and check
https://your-domain.com/.well-known/security.txt
As long as
Contactis commented out (default), the URL answers with 404. This is intended, so a placeholder contact never goes live.
Requirements
- TYPO3 13.4 / 14.3, PHP 8.2+
/.well-known/must reach TYPO3 (index.php) if no physical file exists (TYPO3 default.htaccess/ nginx config allow it)
How to use
Administrator
- Install with composer
- Run the upgrade wizard
tt3Wellknown_securityTxtDefault(repeatable, can run on every deployment withvendor/bin/typo3 upgrade:run)- sets the default text where
wellknownSecurityTxtis missing or empty - existing values are never overwritten
- sets the default text where
- Additionally the Sites module pre-fills the field with the default if a site has no value yet
- With
lochmueller/staticfilecache: verify withcurl -sIthat the response is not cached
Editor / Integrator
- Site Management > Sites > site > tab Well-Known > field
security.txt - Each site (domain) has its own text, stored in
config/sites/<site>/config.yamlaswellknownSecurityTxt - Default text:
# Contact: mailto:security@example.com Expires: {{expires(11M)}} Preferred-Languages: {{languages}} Canonical: {{canonical}}
Documentation
Behaviour
| Request | Result |
|---|---|
GET/HEAD /.well-known/security.txt | 200, text/plain; charset=utf-8, X-Content-Type-Options: nosniff, Cache-Control: public, max-age=3600 |
GET/HEAD /security.txt | 301 to /.well-known/security.txt (only if deliverable) |
| other methods, empty field, text > 8 KB, invalid content | passed on, normal TYPO3 404 |
If the site base contains a path (e.g. https://example.com/de/), the site is found via its host.
Validation (invalid = 404 + log warning)
The file is only delivered if, after placeholder replacement:
- every placeholder is valid and well-formed (unknown, invalid, empty-resulting or unclosed placeholders invalidate the whole file)
- exactly one non-commented
Contact:line with a value exists (field name is case-insensitive) - exactly one non-commented
Expires:line with an ISO 8601 / RFC 3339 date in the future exists (expired files are never served)
The reason is logged as a warning (PSR-3, with the site identifier), e.g. in var/log/. An empty field is not logged.
Placeholders
| Placeholder | Result |
|---|---|
{{expires}} | start of the current month (00:00 UTC) + 11 months, e.g. 2027-09-01T00:00:00.000Z |
{{expires(nX)}} | months (M) and years (Y) count from the start of the current month, days (D) from the start of the current day, e.g. {{expires(6M)}} |
{{languages}} | ISO language codes of the site, default language first, e.g. de, en |
{{canonical}} | https://<host>[:port]/.well-known/security.txt from the request |
Expires more than 12 months ahead is delivered but logged as a warning (RFC 9116 recommends less than one year).
The Expires value is computed on every request but only changes once a month (M/Y) or once a day (D), so the remaining validity of the default {{expires(11M)}} is always between 10 and 11 months and the file never goes stale. Fixed dates (e.g. Expires: 2027-09-15T00:00:00Z) are also supported; they expire for real and result in a 404 after that date.
PGP-signed text
Text starting with -----BEGIN PGP SIGNED MESSAGE----- is delivered unchanged (no placeholders, no normalization), because any change would invalidate the signature. Contact and Expires are still validated.
Status report (System > Reports)
If EXT:reports is installed, System > Reports > Status Report shows one entry per site:
| Severity | Meaning |
|---|---|
| OK | security.txt is deliverable, shows the URL and the current Expires |
| Warning | not delivered (404), with the reason (e.g. no valid Contact, invalid placeholder, expired Expires) |
| Notice | no text maintained for the site |
The report uses the same validation as the delivery, so a green entry means the URL answers with 200.
Extending
Implement Teufels\Tt3Wellknown\Handler\WellKnownHandlerInterface (auto-tagged) to serve further /.well-known/* resources.
Changelog
1.0.x
- 1.0.0 initial stable release
0.2.x
- 0.2.0 status report (System > Reports > Status Report) per site: OK / invalid with reason / not configured (only if
EXT:reportsis installed) - 0.2.0
Expiresis snapped to the start of the month (M/Y) or day (D) so it does not change on every request - 0.2.0 shared validation for delivery and report (
SecurityTxtEvaluator)0.1.x: initial (beta)
- serves
/.well-known/security.txtper site (GET/HEAD), redirect/security.txt=>/.well-known/security.txt(301) - new tab Well-Known in Sites module (
wellknownSecurityTxt) - placeholders
{{expires}},{{expires(nD|nM|nY)}},{{languages}},{{canonical}} - strict validation: invalid placeholder, missing/commented
Contact, missing/duplicate/expiredExpires=> 404 + log - PGP-signed text delivered unchanged
- upgrade wizard
tt3Wellknown_securityTxtDefault