# 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.