From 146400bade723522b7bea117efc1c9cc48469455 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Thu, 5 Nov 2026 22:48:23 +0000 Subject: Stop Apache writing the server's token into its access log /audio/ URLs carry the token as a query parameter, because a URL handed to a downloader has to be usable as it stands, and the "combined" log format logs %r -- the whole request line. So every audio fetch wrote the token in plaintext into a file logrotate keeps fourteen copies of: 335 lines across fifteen files, going back months. Both vhosts now use a listen_noquery format, %m %U %H instead of %r, which keeps the method, path and protocol and drops the query. Verified with a canary token against the live server rather than by reading the config: the request logged as "GET /api/health HTTP/1.1" with nothing after it, and a real request from the phone logged cleanly alongside it. The existing logs are redacted in place, gzipped ones included, with their lines and timestamps intact -- zero occurrences left, and none anywhere else under /var/log. The token itself is worth rotating, since it sat in those files for months. That is Peter's call. The real fix is for the audio URL to carry no token, which needs the downloader sending a bearer header for skip-server hosts -- and only those, or the token would go to every podcast CDN it fetches from. That app change has to be installed before the server stops emitting the query token, so it is not in this one. Config only; no code changed. Written up in CLAUDE.md so nobody restores "combined". Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- CLAUDE.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index df5b251..391ac2a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -556,6 +556,24 @@ by default and correct; it is simply waiting for a publisher who marks breaks. ## Things a future session should know +- **Apache does not log query strings for listen.terst.org, deliberately** (2026-11-05). Both + vhost files define a `listen_noquery` LogFormat -- `%m %U %H` where `combined` uses `%r` -- so + the request line reaches the log without its query. The reason is that `/audio/` URLs carry the + server's token as a query parameter, because a URL handed to a downloader has to work as it + stands, and `combined` therefore wrote the token in plaintext into a file logrotate keeps + fourteen copies of. 335 lines across fifteen files; all redacted in place at the time, and the + token is worth rotating on any suspicion since it sat there for months. + + **Do not set these back to `combined`.** If a line's query is ever genuinely needed for + debugging, log it from the Go server, which can redact what it prints. + + The real fix is for the audio URL to stop carrying a token at all, which needs + `EpisodeDownloader` to send `Authorization: Bearer` for skip-server hosts — and only those, or + the token goes to every podcast CDN. The app change has to be installed *before* the server + stops emitting the query token, or downloads 401 in the gap. The transcript URL already made + this move: it never needed one, because the phone was sending the header anyway. + + - **Nothing the listener reads is written for a machine, and the build enforces it** (`PlainLanguageTest`, added 2026-11-05). A red banner on Peter's phone read "Playback failed: ERROR_CODE_IO_BAD_HTTP_STATUS"; his words were "arcane and sketchy" and both halves were fair. -- cgit v1.2.3