Artifacts readable from allowed websites (v1.30.0) #96

Merged
mi merged 1 commits from mihoe/artifact-cors into main 2026-10-03 15:28:20 +02:00
Owner

The Problem

werkcrappit's code city page links a CodeCharta viewer with the URL of the cc.json beside it.
Served by Werkator, the viewer at javagil.de could not load it: the browser blocks a script from reading another site's response unless that site allows it with Access-Control-Allow-Origin.
CodeCharta reported only Http failure response … 0 Unknown Error and loaded its sample files instead.

Non-Goals

  • Credentials: no Access-Control-Allow-Credentials, so a private repository's artifacts stay unreadable from other sites.
  • Preflight handling: a plain GET without own headers needs none.
  • Other routes than artifact files: pages and the API keep answering without CORS.

The Scenarios

Scenario#96.01: an artifact file answers an allowed origin

… so that a viewer on another host, such as CodeCharta at javagil.de, loads a code city straight from Werkator.

  • Given server.corsAllowedOrigins in the instance file lists https://javagil.de
  • When the browser fetches an artifact file of a public repository with Origin: https://javagil.de
  • Then the response carries Access-Control-Allow-Origin: https://javagil.de and Vary: Origin
  • And no Access-Control-Allow-Credentials, so a token or cookie never reaches the other origin
Verified by

Scenario#96.02: no header for other origins or without the setting

… so that Werkator shares nothing with a website the instance did not name.

  • Given server.corsAllowedOrigins lists https://javagil.de
  • When the browser fetches an artifact file with Origin: https://evil.example, or without Origin
  • Then the response carries no Access-Control-Allow-Origin
  • And without the setting, the default, no response carries one, as before
Verified by

Scenario#96.03: a repository cannot widen the setting

… so that only the operator of an instance decides which websites may read its artifacts.

  • Given a repository's .werkator.yml, committed or on a branch, sets server.corsAllowedOrigins
  • Then that value is ignored, with the same warning as other server keys outside the instance file
  • And a private repository still answers 404 without the control token, Origin or not
Verified by

The Solution

  • ServerConfig.corsAllowedOrigins, a list of exact origins, empty by default.
    The server section is instance-level: with an instance file, a repository's copy is ignored with a warning, and a branch layer never carries it.
    Without an instance file, the repository's committed file is the instance's, as for every server key.
  • ArtifactFileController answers both artifact routes, by key and the permanent branch URL, with the request's Origin when it is listed, and always with Vary: Origin, so a cache never hands one site's answer to another.
  • The commented init template and docs/configuration.md name the key.

Open Questions

  • mih09 needs server.corsAllowedOrigins: ["https://javagil.de", "https://codecharta.com"] in ~/.werkator.yml; read per request, no restart needed.

🤖 Generated with Claude Code

## The Problem werkcrappit's code city page links a CodeCharta viewer with the URL of the `cc.json` beside it. Served by Werkator, the viewer at javagil.de could not load it: the browser blocks a script from reading another site's response unless that site allows it with `Access-Control-Allow-Origin`. CodeCharta reported only `Http failure response … 0 Unknown Error` and loaded its sample files instead. ## Non-Goals - Credentials: no `Access-Control-Allow-Credentials`, so a private repository's artifacts stay unreadable from other sites. - Preflight handling: a plain `GET` without own headers needs none. - Other routes than artifact files: pages and the API keep answering without CORS. ## The Scenarios #### Scenario#96.01: an artifact file answers an allowed origin … so that a viewer on another host, such as CodeCharta at javagil.de, loads a code city straight from Werkator. - **Given** `server.corsAllowedOrigins` in the instance file lists `https://javagil.de` - **When** the browser fetches an artifact file of a public repository with `Origin: https://javagil.de` - **Then** the response carries `Access-Control-Allow-Origin: https://javagil.de` and `Vary: Origin` - **And** no `Access-Control-Allow-Credentials`, so a token or cookie never reaches the other origin ##### Verified by - [ArtifactFileControllerRestTest](../../src/test/kotlin/de/hoennig/werkator/server/ArtifactFileControllerRestTest.kt) `an artifact file answers an allowed origin, without credentials` #### Scenario#96.02: no header for other origins or without the setting … so that Werkator shares nothing with a website the instance did not name. - **Given** `server.corsAllowedOrigins` lists `https://javagil.de` - **When** the browser fetches an artifact file with `Origin: https://evil.example`, or without `Origin` - **Then** the response carries no `Access-Control-Allow-Origin` - **And** without the setting, the default, no response carries one, as before ##### Verified by - [ArtifactFileControllerRestTest](../../src/test/kotlin/de/hoennig/werkator/server/ArtifactFileControllerRestTest.kt) `another origin, or none, gets no CORS header` - [ConfigLoaderUnitTest](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderUnitTest.kt) `without a setting no origin may read artifacts` #### Scenario#96.03: a repository cannot widen the setting … so that only the operator of an instance decides which websites may read its artifacts. - **Given** a repository's `.werkator.yml`, committed or on a branch, sets `server.corsAllowedOrigins` - **Then** that value is ignored, with the same warning as other `server` keys outside the instance file - **And** a private repository still answers 404 without the control token, `Origin` or not ##### Verified by - [ConfigLoaderUnitTest](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderUnitTest.kt) `the origins allowed to read artifacts are the instance's` - [ArtifactFileControllerRestTest](../../src/test/kotlin/de/hoennig/werkator/server/ArtifactFileControllerRestTest.kt) `a private repository stays hidden to an allowed origin` ## The Solution - `ServerConfig.corsAllowedOrigins`, a list of exact origins, empty by default. The `server` section is instance-level: with an instance file, a repository's copy is ignored with a warning, and a branch layer never carries it. Without an instance file, the repository's committed file is the instance's, as for every `server` key. - `ArtifactFileController` answers both artifact routes, by key and the permanent branch URL, with the request's `Origin` when it is listed, and always with `Vary: Origin`, so a cache never hands one site's answer to another. - The commented `init` template and `docs/configuration.md` name the key. ## Open Questions - mih09 needs `server.corsAllowedOrigins: ["https://javagil.de", "https://codecharta.com"]` in `~/.werkator.yml`; read per request, no restart needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
mi added 1 commit 2026-10-03 15:23:14 +02:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mi merged commit fd44422a9f into main 2026-10-03 15:28:20 +02:00
mi deleted branch mihoe/artifact-cors 2026-10-03 15:28:20 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mi/werkator#96