From c95ac471f1b9e79d8e899ad7651bc5e6e599404a Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 19 Oct 2026 23:21:43 +0000 Subject: [PATCH] Add the HTTP hardening the policy requires before 1.0 (closes #108) Every answer carries the six security headers REPO_POLICIES.md lists. The Content-Security-Policy allows inline content only with a nonce made for that answer; the pages put it on their style and script blocks and drop their four style attributes. The request log and /ip/ take a forwarded address only from a proxy listed in TRUSTED_PROXIES, which stops the start when set but invalid: the last X-Forwarded-For entry that is not a trusted proxy. X-Real-IP and True-Client-IP are no longer read. The JSON routes and the health check send fixed messages in place of database, parser and resolver errors, and log the detail. Model: opus-5-5 --- README.md | 36 +++- internal/config/config.go | 50 ++++- internal/config/config_internal_test.go | 51 +++++ internal/server/handlers.go | 90 ++++++--- internal/server/handlers_internal_test.go | 104 +++++++++- internal/server/middleware.go | 112 ++++++++++- internal/server/middleware_internal_test.go | 205 ++++++++++++++++++++ internal/server/routes.go | 3 +- internal/server/server.go | 14 +- internal/templates/as_detail.html | 2 +- internal/templates/index.html | 15 +- internal/templates/prefix_detail.html | 8 +- internal/templates/prefix_length.html | 2 +- internal/templates/status.html | 9 +- 14 files changed, 641 insertions(+), 60 deletions(-) create mode 100644 internal/server/middleware_internal_test.go diff --git a/README.md b/README.md index 05ca2a5..800ec34 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,9 @@ DEBUG=routewatch ./routewatch picked at random, each with its newest route (HTML) - `GET /prefixlength6/{length}` - The same for IPv6 - `GET /ip/{addr}` - The same JSON answer as `/api/v1/ip/{ip}` -- `GET /ip/` - The same, for the caller's own address, or the one a proxy names - in `True-Client-IP`, `X-Real-IP` or `X-Forwarded-For` +- `GET /ip/` - The same, for the caller's own address: the address the request + came from or, when that is a trusted proxy (`TRUSTED_PROXIES`, see + Configuration), the client address in its `X-Forwarded-For` ### API v1 @@ -84,7 +85,17 @@ and 4 seconds for the database reads behind `/status.json` and `/api/v1/stats`. An AS number, prefix or mask length that does not parse answers 400, as does a mask length out of range for its IP version. An AS the database does not hold, or a prefix with no live route, answers 404. The HTML pages give these errors as -plain text. +plain text. A 500 answer says only `Internal server error`, and a host name that +does not resolve only `could not resolve host name`; the detail goes to the log. + +Every answer carries `Strict-Transport-Security`, `Content-Security-Policy`, +`X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy` and +`Permissions-Policy`. The Content-Security-Policy lets a page load content only +from the server, and does not allow inline content with `'unsafe-inline'`: the +pages keep their CSS and JavaScript in inline `