Files
werkator/src/main/resources/templates/fragments.html
T
mhoennigandClaude a734d91918 Stop embedding the control token in every page (v0.9.10)
Closes TODO 2 of the security audit in docs/prs/2026-07-08-PR#000.

Every rendered page carried the live control token in a meta tag so that
gittally.js could send it, but no GET is authenticated — so `curl … |
grep gittally-control-token` handed the token to anyone, and read access
was effectively write access.

Reading stays fully public, which is a requirement rather than an
oversight: build states, logs and artifacts must be linkable from Gitea,
chats or tickets without a login. Only the distribution of the token
changed. The meta tag is gone; gittally.js keeps the token in
localStorage and asks for it once per browser, so knowing it requires
shell access to `.git/gittally/control-token` on the host. A token the
server rejects is dropped and asked for once more, so a rotated secret is
not a dead end. As a request header it stays inherently CSRF-safe.

The five branches of that flow (first use, reuse, stale token, cancelled
prompt, wrong token twice) were exercised against the real source with a
throwaway node harness; the UI test now asserts the token does not appear
in the rendered page. `docs/deployment.md` gained a "Control Token"
section on the public-read/token-protected-write split.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 08:00:09 +02:00

47 lines
2.3 KiB
HTML

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head th:fragment="head(title)">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${#strings.isEmpty(repoName)} ? ${title} + ' — GitTally' : ${title} + ' — ' + ${repoName} + ' — GitTally'">GitTally</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/gittally.css">
<meta name="gittally-gitea-repo-url" th:content="${giteaRepoUrl}">
</head>
<body>
<h1 th:fragment="header(title)">
<a class="title-home" href="/" aria-label="Open latest builds"><img src="/favicon.svg" alt=""></a>
<span class="title-text" th:text="${title}">Latest Builds</span>
<span class="repo-name" th:unless="${#strings.isEmpty(repoName)}" th:text="${repoName}">owner/repo</span>
</h1>
<div th:fragment="nav(view)" class="view-row">
<nav class="view-toggle">
<span th:if="${view == 'latest'}">Latest</span>
<a th:unless="${view == 'latest'}" href="/">Latest</a>
<span th:if="${view == 'branches'}">Branches</span>
<a th:unless="${view == 'branches'}" href="/branches">Branches</a>
<span th:if="${view == 'history'}">History</span>
<a th:unless="${view == 'history'}" href="/history">History</a>
<span th:if="${view == 'current'}">Current</span>
<span th:if="${view == 'system'}">System</span>
<a th:unless="${view == 'system'}" href="/system">System</a>
</nav>
<span class="view-row-actions">
<span id="live-indicator" class="status status-unknown" title="live-update state">static</span>
<button id="reload-button" class="reload-button" type="button" title="Reload view" aria-label="Reload view"></button>
</span>
</div>
<footer th:fragment="footer" class="site-footer">
<strong><a href="/releases" title="Release notes"><em th:text="'GitTally v' + ${version}">GitTally</em></a></strong>
— © <a href="https://michael.hoennig.de" target="_blank" rel="noopener noreferrer">Michael Hönnig</a>, 2026
<th:block th:unless="${#strings.isEmpty(impressumUrl)}">
<a th:href="${impressumUrl}" target="_blank" rel="noopener noreferrer">Impressum (Legal Disclosure)</a>
</th:block>
</footer>
</body>
</html>