Skip to content

feat(frontend): serve RFC 9116 security.txt at literal-root /.well-known/security.txt - #9

Open
JRojowski wants to merge 9 commits into
mainfrom
work/001-security-txt-v2
Open

feat(frontend): serve RFC 9116 security.txt at literal-root /.well-known/security.txt#9
JRojowski wants to merge 9 commits into
mainfrom
work/001-security-txt-v2

Conversation

@JRojowski

Copy link
Copy Markdown
Collaborator

Summary

Adds a GET /.well-known/security.txt endpoint to the frontend (Go) service, compliant with RFC 9116. Serves the two RFC-required fields only — Contact and a dynamically-generated Expires — with the correct Content-Type: text/plain; charset=utf-8.

Required vs recommended (RFC 9116): only Contact (§2.5.3) and Expires (§2.5.5, exactly once, RFC 3339) are REQUIRED; everything else (Encryption, Acknowledgments, Preferred-Languages, Canonical, Policy, Hiring) is recommended/optional. This unit ships the required-only body by decision.

Response

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

Contact: mailto:security@example.com
Expires: <now + ~6 months, RFC 3339>

Key decisions (locked at pipeline checkpoints)

  • Required fields onlyContact + Expires, nothing else.
  • Contact — placeholder mailto:security@example.com (demo app, no real security team).
  • Path — registered at the literal root /.well-known/security.txt, independent of BASE_URL, for strict RFC conformance.
  • Expires — generated dynamically at request time (now + ~6 months, time.RFC3339) so the file never goes stale.
  • Method — unrestricted, matching the existing robots.txt/_healthz handlers.

Testing

  • 8 behaviour tests (src/frontend/security_txt_test.go, the frontend's first test file) via net/http/httptest — status, exact Content-Type, Contact line, Expires (once/future/<1yr), required-only body, literal-root path, method handling. go build ./... + go test ./... green.
  • Live e2e smoke (PASS): rebuilt the frontend image, deployed to a local kind cluster, and curled the real endpoint — 200 + correct headers/body, storefront still 200. Evidence in docs/test/001-security-txt/.

Pipeline artifacts

Spec-driven (research → tests → plan → execute → docs):

  • docs/work/001-security-txt/1-research.md
  • docs/work/001-security-txt/2-tests.md
  • docs/work/001-security-txt/3-plan.md

Note

Supersedes the earlier PR #7 (built on an outdated dev-kit base, using a baseUrl-prefixed path and a 1-year Expires). This PR rebases the unit onto current main and applies the literal-root path + rolling 6-month Expires decisions. PR #7 can be closed.

Scope a GET /.well-known/security.txt endpoint for the frontend (Go)
service per RFC 9116. Establishes that only Contact (§2.5.3) and Expires
(§2.5.5) are REQUIRED; all other fields are recommended/optional.
Task tier: lightweight.
8 Given-When-Then scenarios (T-001..T-008) pinning status, exact
Content-Type, Contact placeholder, dynamic RFC 3339 Expires
(once/future/<1yr), required-only fields, literal-root path, and method
handling. Pure net/http/httptest, no mocks.
Six-phase plan (baseline -> seams -> red tests -> implementation ->
consolidation -> e2e) across 5 coder/e2e dispatches. Stub-first seam so
T-001..T-008 are genuinely red, then a dynamic RFC 9116 body-builder
turns them green; mandatory live e2e smoke closes the unit.
Add a securityTxtHandler stub and register GET /.well-known/security.txt
at the literal root (independent of BASE_URL), matching the
robots.txt/_healthz inline-handler precedent. Stub only: no RFC 9116
body yet (implemented in P3-04) so the P3-03 tests are genuinely red.

Task: P3-02 (seam). Baseline P3-01 verified green.
First frontend test file. Covers status, exact Content-Type, Contact
placeholder, dynamic RFC 3339 Expires (once/future/<1yr), required-only
body, literal-root path, and unrestricted method, all via
net/http/httptest.

Expected-red by design: T-002/T-003/T-004 fail against the P3-02 stub;
P3-04 turns them green. Task: P3-03.
securityTxtHandler now sets Content-Type: text/plain; charset=utf-8 and
writes the two required fields: Contact (mailto:security@example.com) and
a dynamically-generated Expires (now + 6 months, RFC 3339) so the file
never goes stale. Required fields only, method unrestricted.

Turns T-001..T-008 green. Task: P3-04.
Split body/Expires construction into securityTxtBody() so the handler is
a thin HTTP wrapper and the RFC 9116 content is independently readable.
No behaviour change; T-001..T-008 stay green. Task: P3-05.
Rebuilt the frontend from this branch, loaded into the kind boutique
cluster, rolled out, and curled GET /.well-known/security.txt: 200,
text/plain; charset=utf-8, Contact + single future RFC 3339 Expires;
storefront still 200. Verdict PASS. Task: P3-06.
Document the GET /.well-known/security.txt smoke step under the frontend
subsection and update the status banner: the frontend live-smoke path is
now proven against a local kind cluster (kind-boutique), while other
services still need wiring.
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.

1 participant