> **WARNING:** This document describes only the change applied in this PR. > It may already be outdated once the next PR is merged. > Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation. ## Related Links - [PR#43](2026-09-09-PR%2343-private-repositories-and-quota-severity.md) — made the answers depend on the control-token cookie, which is what these two corrections follow from. ## The Problem **A newly entered control token did not take effect.** Since PR#43 a page and every JSON answer depend on the control-token cookie: a private repository is redacted for a visitor and whole for the operator, and the actions are disabled or not. Those answers were sent without a single caching header — no `Cache-Control`, no `Vary`, not even a validator — so after pasting the token the browser could redisplay the copy it already had, and the token looked as if it had not worked. The same omission is a privacy problem in its own right, and a worse one: a shared cache in front of Werkator — the reverse proxy every deployment runs behind — was free to store a response and hand it to somebody else. That could show a visitor's redacted page to the operator, or the operator's page, with every repository named, to a visitor. **The key threw the token away on a single click.** It sits next to the reload button in the header. A misclick cost the token, which then has to be fetched from the host again. ## Non-Goals - No change to what is shown to whom. This PR is about the answers arriving fresh, not about their content. - No caching strategy for artifact files beyond what they already have; they only gain the `Vary` that states the truth. ## The Scenarios ### Feature: answers that depend on the token #### Scenario#45.01: A response that varies by cookie is never stored So that a token works the moment it is entered, and no cache can mix two visitors up. - **Given** any page or JSON answer of the UI, the builds API, the repositories API or the system API - **When** it is requested - **Then** it carries `Cache-Control: no-store` - **and** it carries `Vary: Cookie` - **Given** an artifact file, which depends on the cookie only in whether it exists at all - **Then** it keeps the caching it has and carries `Vary: Cookie` - **because** a report is large and never changes under its key, and stating the dependency is enough for a correct shared cache - **And when** a control token is entered and the address fetched again - **Then** the answer is the new one, not the one the browser had ##### Verified by - [UiControllerTest](../../src/test/kotlin/de/hoennig/werkator/server/UiControllerTest.kt) ### Feature: the key in the header #### Scenario#45.02: Forgetting the token is asked for, entering it is not - **Given** a browser holding the token - **When** the key is clicked - **Then** it asks before forgetting it, naming what will be hidden again - **Given** a browser without one - **When** the key is clicked and a token entered - **Then** it is stored and the address fetched afresh, so the page comes back in its operator form ## The Solution `CookieDependentResponses` is a one-interceptor configuration naming the controllers whose answer depends on who is asking, and setting `no-store` and `Vary: Cookie` on them. `no-store` rather than a validator: these answers are small, they are polled anyway, and they are meaningless a moment later. The key button asks before forgetting, and after taking a token it uses `location.replace(location.href)` rather than `reload()` — a reload may repeat the request that produced the page, while replacing always fetches the address afresh, now with the cookie that exists since a moment ago. ## Additional Changes - Version 1.5.2 and its release-notes entry, since this is what gets deployed.