# PR#164: A link between two HTML artifacts of a private repository opens for the operator > **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. ## Problem On vm4006 the scenario report of `hs.hsadmin.ng` opens, but each link on it answers 404 "no repository named 'hs.hsadmin.ng'". The same URL typed into the address bar answers 200. All relative links between HTML artifacts of a private repository fail the same way, e.g. the sub-pages of the Gradle test report. Since PR#55 every artifact carries `Content-Security-Policy: sandbox allow-scripts`, so the artifact document has an opaque origin. A link clicked in it is a cross-site navigation for the browser. `werkator.js` writes the control-token cookie with `SameSite=Strict`, so the browser does not send it on that navigation. Without the cookie the private repository is hidden, and its routes answer 404 (PR#43). ## Non-Goals - No change to the sandbox: artifacts keep `sandbox allow-scripts`, never with `allow-same-origin`. - No artifact cookie or signed artifact URLs. - No change to what the token permits: acting still needs the header `X-werkator-Token`. ## Specification ### Feature: an operator follows links inside the reports of a private repository #### Scenario#164.01: A link from one report page to another opens! … so that the operator can read a whole test report of a private repository. - **Given** an operator who has entered the control token in this browser - **And** a test report of a private repository whose index links to a sub-page - **When** the operator clicks that link on the index - **Then** the sub-page opens ##### Verified by - [ControlTokenCookieUnitTest: the cookie is written and cleared with SameSite=Lax](../../src/test/kotlin/de/javagil/werkator/server/ControlTokenCookieUnitTest.kt) - The check in Chromium under "Solution". #### Scenario#164.02: The token entered before the update stays enough! … so that no operator has to enter the token again. - **Given** an operator who entered the control token before version 1.43.4 - **And** who has opened a Werkator page since the update - **When** the operator clicks a link on a report page of a private repository - **Then** the linked report page opens ##### Verified by - [ControlTokenCookieUnitTest: a stored token is mirrored into the cookie on every load](../../src/test/kotlin/de/javagil/werkator/server/ControlTokenCookieUnitTest.kt) - The check in Chromium under "Solution". #### Scenario#164.03: A script in a report cannot read the token! … so that a build cannot steal the operator's token through its report. - **Given** an operator who has entered the control token in this browser - **And** a report page whose script tries to read the token - **When** the operator opens that page - **Then** the script gets no token ##### Verified by - [ArtifactFileControllerRestTest: an html artifact is sandboxed onto its own origin, away from the control token](../../src/test/kotlin/de/javagil/werkator/server/ArtifactFileControllerRestTest.kt) - The check in Chromium under "Solution". ## Solution - `werkator.js` writes and clears the cookie with `SameSite=Lax`. Lax sends it on a top-level GET navigation, also from an opaque origin, and on no cross-site subresource request or POST. - Why Lax is safe here: - The cookie gives visibility only. Restart, cancel and delete are `POST` and `DELETE` and read the token from the header `X-werkator-Token` alone; no `GET` route changes state. - A foreign page can now open a private page in the operator's browser by a link. It cannot read the answer, because the page is cross-origin to it. - A script in a sandboxed artifact has an opaque origin: it cannot read `document.cookie` or `localStorage` of Werkator, and its `fetch` or `iframe` requests to Werkator are cross-site subresource requests, which do not carry a Lax cookie. - Residual risk, new with Lax: a foreign page can open a private page in the operator's browser and time it, or count its frames, to tell a 200 from a 404. That way it can guess whether a private repository of a given name exists; with Strict it could not. The name is content (RFC 0003 D8), so this weakens D8 for an operator who visits such a page. The alternatives, a path-scoped artifact cookie or signed artifact URLs, cost a server-side token per artifact. - Checked in Chromium, on a local instance with a private repository and two linked HTML artifacts: - With the cookie written `SameSite=Strict`, the index opens from the address bar and its link answers 404. - With the token stored and the home page loaded once, the cookie is rewritten, and the link opens the sub-page. - A script in the artifact gets a `SecurityError` when it reads `document.cookie`. - The existing mirror on load rewrites every stored token, so a Strict cookie becomes Lax on the next page view. - Werkbaum: a node under `#security`. - Version 1.43.4, with an entry in the release notes. ## Open Questions None.