Skip to content

Serve a security contact at /.well-known: PGP key + security.txt - #108

Merged
stiebitzhofer merged 3 commits into
mainfrom
well-known-pgp
Sep 13, 2026
Merged

stiebitzhofer merged 3 commits into
mainfrom
well-known-pgp

Conversation

@stiebitzhofer

@stiebitzhofer stiebitzhofer commented Sep 13, 2026

Copy link
Copy Markdown
Member

Summary

Gives the repo a discoverable security contact, and makes it correct at the transport level.

  • Tracked source at .well-known/pgp-security.asc, mirroring the served path. It was in site/, which is the gitignored build output that build() clears on every run — untracked, and deleted before it could ever deploy.
  • The build copies the whole .well-known/ directory, so a new file there needs no builder code. security.txt rode in on exactly that.
  • .well-known/security.txt (RFC 9116): Contact, Expires, Encryption pointing at the key above, Preferred-Languages: en, de, Canonical. This is the file scanners actually look for; the key alone was only findable by guessing its name.
  • Worker: /.well-known/* gets Cache-Control: public, max-age=86400 — cacheable, and deliberately not immutable like an entry, because a key can be rotated or revoked and the contact expires. .asc gets Content-Type: application/pgp-keys, .txt gets text/plain; charset=utf-8 as RFC 9116 §3 requires; Pages would otherwise serve them as generic byte streams.

Published key is the public block only: S1Seven Security <security@s1seven.com>, RSA-4096, fingerprint 25FF A4E5 240A E655 FEA3 9082 D57E 1BBD 422F DAD7, expires 2028-09-12.

The expiry ratchet

An expired security.txt is worse than none — tooling reads it as an unmonitored contact — and a static file rots silently. So renewal is a check, not a calendar reminder: validate fails when Expires is missing, unparseable, past, or more than a year out, or when Encryption names a key the repo does not ship.

It is deliberately unnumbered. Checks 1–6 are the entry rules and check 7 is the CI two-yes gate; renumbering would touch the skills, pr-checks.yml and the plan for a check that guards the repo's own contact rather than the dictionary. It stays green on fixture trees, which carry no .well-known at all.

Renew it in place — .well-known/ is not under published/, so no immutability rule applies.

Closes #107
Closes #109

Test plan

  • npm run validate — OK, including security.txt — RFC 9116 freshness
  • npm test — 80/80, coverage 97.05% line
  • Worker tests: both content types, the 1-day ceiling, that immutable is absent, that other .well-known resources keep the origin's type, and that .asc/.txt elsewhere are not special-cased
  • Build test: copied byte-for-byte, the published key bytes are a public block containing no private key, and security.txt lands with its Canonical line intact
  • Check tests: valid file passes; expired, over-a-year, unparseable, missing-field and dangling-Encryption cases each fail with the right message; absent file is green
  • After deploy: curl both live paths and confirm the headers — the artifact upload includes dotfiles and there is no Jekyll step, but worth verifying rather than assuming

Tracked source at .well-known/, mirroring the served path; the build copies the
whole directory, so a security.txt later needs no code change. It was sitting in
site/, which is the gitignored build output that build() clears on every run —
it would have been deleted before it ever deployed.

The Worker serves /.well-known/* with max-age=86400 and no immutable flag: a key
can be rotated or revoked, so a day is the ceiling, unlike an entry. .asc gets
Content-Type: application/pgp-keys, which Pages would otherwise serve as a
generic byte stream.

Public key block only — S1Seven Security <security@s1seven.com>, RSA-4096,
25FF A4E5 240A E655 FEA3 9082 D57E 1BBD 422F DAD7, expires 2028-09-12. A test
asserts the published bytes carry no private key.

Closes #107
RFC 9116. Contact, Expires, Encryption pointing at the key added in #107,
Preferred-Languages and Canonical. The worker serves .txt under .well-known/
as text/plain; charset=utf-8, which RFC 9116 requires, alongside the .asc rule.

An expired security.txt is worse than none: tooling reads it as an unmonitored
contact, and a static file rots without anyone noticing. So this is the ratchet
rather than a calendar reminder — a validate check refuses the next PR when
Expires is missing, unparseable, past, or more than a year out, and when
Encryption names a key the repo does not ship. It is deliberately unnumbered:
checks 1-6 are the entry rules and 7 is the CI two-yes gate; this one guards the
repo's own contact, and it stays green on fixture trees, which carry no
.well-known at all.

Closes #109
@stiebitzhofer stiebitzhofer changed the title Serve the security contact's PGP key at /.well-known/pgp-security.asc Serve a security contact at /.well-known: PGP key + security.txt Sep 13, 2026
@stiebitzhofer
stiebitzhofer merged commit 1919085 into main Sep 13, 2026
1 check passed
@stiebitzhofer
stiebitzhofer deleted the well-known-pgp branch September 13, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add /.well-known/security.txt (RFC 9116), with an expiry check so it cannot rot Serve the security contact's PGP key at /.well-known/pgp-security.asc

1 participant