Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

I2151

A page served beside the write routes runs only script it shipped, and a rebinding host reads nothing

Where it stands

devtask.issue
Readiness
complete completion is recorded and every required evidence token is present
Status
DONE
Can start
not now
Milestone
Milestone status
ACTIVE
Wave
0
Waiting on
none
Holds back
none
Behind it in all
0
Evidence
3 of 3
A move writes the repository: it stamps one field of this issue's record and appends one event to the ledger, and a commit will carry both.
The same moves from a terminal

majordomus plan start I2151

majordomus plan verify I2151

majordomus plan done I2151

What it is

Objective
Give /swagger the Cockpit's security headers and integrity-checked scripts, refuse a GET whose Host names another server, and escape script-breaking sequences where the site embeds JSON.
Why
/swagger serves no Content-Security-Policy, no frame-ancestors and no subresource integrity for the scripts it loads from unpkg (apps/majordomus-cli/src/http/swagger.rs:52,79; headers confirmed live by the sub-audit), on the same origin as every POST route. The Origin check (apps/majordomus-cli/src/http/router.rs:503-508, 718-745) passes for a same-origin script, so a substituted CDN script could call plan/transition, continuity/*, recover/orphans or executions/start. GET routes do no Host validation (only non-GET is checked, apps/majordomus-cli/src/http/router.rs:504, host_is_literal at :749), and the comment at apps/majordomus-cli/src/http/router.rs:498-502 assumes a cross-origin read is contained, which DNS rebinding defeats (the exploit is unverified). The static site embeds json_encode | safe inside script elements without escaping the closing sequence (site/templates/partials/graph.html:76, site/templates/api.html:94, site/templates/why-section.html:284; exploitability unverified). The Cockpit already sends a CSP (apps/majordomus-cli/src/cockpit/mod.rs:193-204, 296-310). Baseline audit of 2026-10-09 at master 1fb8d6be44, projections gaps 7 and 11, lifecycle authorization row and drift 9.
Now
/swagger trusts a CDN with the write routes' origin; GET answers any Host; three site templates embed JSON unescaped.
Wanted
/swagger is served with its own Content-Security-Policy (the pinned unpkg files and the one inline script by digest, frame-ancestors none, no eval) plus nosniff and no-referrer, and both CDN files carry sha384 integrity; a request from loopback whose Host is neither an address nor localhost is refused 403 forbidden before any surface answers (a rebinding page reads nothing), while a request from another host may name this machine as it knows it; the three site templates replace </ with <\/ before marking JSON safe. Verified in Chromium: /swagger renders 53 operation groups with no console error and no CSP violation.
Done when
unknown: the record declares no `completion`
Risk
unknown: the record declares no `risk`

Acceptance

  • GET /swagger answers with a Content-Security-Policy that includes frame-ancestors, and every script it loads carries an integrity attribute
  • A GET whose Host header names another domain is refused before its handler runs
  • A site data value containing a closing script tag renders as text in each template that embeds JSON
Validation
bin/majordomus plan validatecargo test --test http_host --test http_serveMJ_TEST_JOBS=4 bash test/run.sh <the case this issue adds>
Evidence required
swagger_csp_testrebinding_host_refused_testsite_script_escape_case
Evidence present
site_script_escape_caserebinding_host_refused_testswagger_csp_test

Scope

Touches
apps/majordomus-cli/src/http/swagger.rsapps/majordomus-cli/src/http/router.rsapps/majordomus-cli/src/cockpit/mod.rssite/templates/partials/graph.htmlsite/templates/api.htmlsite/templates/why-section.htmlapps/majordomus-cli/tests/http_host.rsapps/majordomus-cli/tests/http_serve.rstest/casesdocs/MCP.md
Not
unknown: the record declares no `non_scope`
Depends on
none
Priority
p0
Profile
implementation
Parallel safe
false
Owner
unknown: the record declares no `owner`

The work so far

Branches
none
Merged
none
Commits
0
Trunk
master
Sessions
none
Created
2026-10-09
Updated
2026-10-14T22:17:04Z
Started
2026-10-14T22:16:25Z
Verified
2026-10-14T22:16:45Z
Completed
2026-10-14T22:17:04Z

Synchronisation

Adapter
scripts/github-sync
Repository
unknown: the derived plan's header carries no repository
External issue
unknown: a live GitHub state; this executable makes no network call, so only `scripts/github-sync --check` can answer it
State there
unknown: a live GitHub state; this executable makes no network call, so only `scripts/github-sync --check` can answer it

Findings

Nothing is wrong with these records.