v0.346.1: SECURITY — traefik v3.7.16, the file manager 1.5.8-stable, cloudflared 2026.10.4 (felhom.eu R-1304, R-1305); test TestEnsureTraefik_ANewImageRecreatesTraefik (red first)
gates / gates (push) Successful in 50s

Built from the v0.346.0 release commit b1b9be3 + this one commit. MinAgent 0.158.1 (unchanged).
This commit is contained in:
2026-10-27 15:48:38 +01:00
parent b1b9be3a8e
commit 5ba1b86293
4 changed files with 79 additions and 4 deletions
+16
View File
@@ -1,3 +1,19 @@
## v0.346.1 (2026-10-27) — SECURITY: traefik v3.7.16, the file manager 1.5.8-stable, cloudflared 2026.10.4 — built from v0.346.0 (`b1b9be3`) + one commit (felhom.eu R-1304, R-1305)
**MinAgent: 0.158.1** (unchanged). Box programs only; no other change from v0.346.0.
- `internal/infra`: `TraefikImage` `traefik:v3.7.13` → `traefik:v3.7.16` (12 notices between; none reaches our config —
docker + file providers, no FastProxy, no NTLM backend, no Kubernetes; the v3.7.14–16 migration notes touch only
Kubernetes provider names, OTel histograms and a CONNECT request now answered 501). `FileBrowserImage`
`gtstef/filebrowser:1.5.6-stable` → `1.5.8-stable` (GHSA-qx86-4v5r-26g5 TOTP re-enrolment, GHSA-3846-gh75-gp3m upload
share, and the 1.5.8 fixes; 1.5.7-stable is skipped — it broke existing TOTP sign-ins). `CloudflaredImage`
`2026.10.3` → `2026.10.4` (VULN-176088: the metrics server's debug endpoints; ours is not reachable from outside — low).
- How a box takes them: the reconcile at start, as for 0.345.1 — traefik's compose changes → `up -d --force-recreate`
(new test `TestEnsureTraefik_ANewImageRecreatesTraefik`, red-proved: it fails when a compose-only change is skipped);
cloudflared `TestEnsureCloudflared_ANewImageRecreatesTheTunnel`; the file manager
`TestReconcileFileBrowserImage_MovesOnlyTheImage` (mounts unchanged; it rejoins the app network only after the
password check, R-1281).
## v0.346.0 (2026-10-27) — D53: the hub learns which apps are held; an unreadable app.yaml keeps its folder (R-1220); the ceremony keeps the off-site key (R-1218, R-1219); safe .fab export/import (R-1187); holds of uninstalled apps are not reported (R-1298); Go 1.27.3 (R-972); the line-test hold waits for the seeder to slow down (R-1250) — RELEASED on the operator's yes (attended release block)
**MinAgent: 0.158.1** (unchanged). Operator ruling D53 (2026-10-26, felhom.eu `09` §3 decision 265): operator-only
+19
View File
@@ -0,0 +1,19 @@
# REPORT — controller v0.346.1: traefik v3.7.16, the file manager 1.5.8-stable, cloudflared 2026.10.4 (security), 2026-10-27 (attended)
> A sibling of `REPORT.md`, not an overwrite: the release session of the same day (v0.346.0) owns `REPORT.md`.
## In plain words
- **What:** three box programs had public security notices: traefik (12 notices, none reaching our config), the file
manager (a TOTP re-enrolment and an upload-share flaw; 1.5.7 is skipped, it broke TOTP sign-ins) and cloudflared
(debug endpoints on its metrics server, not reachable from outside). Controller **0.346.1** = the released 0.346.0
(`b1b9be3`) + only the three new pins.
- **How a box takes them:** the reconcile at start recreates each container on its new image (traefik pinned by the new
test `TestEnsureTraefik_ANewImageRecreatesTraefik`, red-proved).
## Gates
`controller_gates.py` (full) green; `go test ./...` green (41 packages); `go build`, `go vet` green.
Evidence and the box proofs: felhom.eu `documentation/audits/security-app-updates-2026-10-27/part5/`. Register:
felhom.eu R-1304 (traefik), R-1305 (file manager).
+8 -4
View File
@@ -22,11 +22,15 @@ import (
// Pinned image tags — NEVER ":latest" (a floating tag breaks reproducible golden bakes and lets the
// deployed version drift). Verified to resolve on Docker Hub before baking. cloudflared follows Cloudflare's security
// notices (felhom.eu `01` „Tunnel placement"): 2026.10.3 since the notice of 2026-10-20 — before 2026.10.2 the image
// trusts eight eMudhra (emSign) intermediates with weak keys.
// trusts eight eMudhra (emSign) intermediates with weak keys; 2026.10.4 since 2026-10-27 (VULN-176088, the metrics
// server's debug endpoints). traefik v3.7.16 and the file manager 1.5.8-stable since 2026-10-27 for their security
// notices (felhom.eu R-1304, R-1305; 1.5.7-stable is skipped — it broke existing TOTP sign-ins). Each new image reaches
// a running box by the reconcile at start: TestEnsureTraefik_ANewImageRecreatesTraefik,
// TestEnsureCloudflared_ANewImageRecreatesTheTunnel, TestReconcileFileBrowserImage_MovesOnlyTheImage.
const (
TraefikImage = "traefik:v3.7.13"
CloudflaredImage = "cloudflare/cloudflared:2026.10.3"
FileBrowserImage = "gtstef/filebrowser:1.5.6-stable"
TraefikImage = "traefik:v3.7.16"
CloudflaredImage = "cloudflare/cloudflared:2026.10.4"
FileBrowserImage = "gtstef/filebrowser:1.5.8-stable"
// FileBrowserImportMount is the in-container mount point NAME for the canonical drop-zone
// (R-75): the bind lands at /srv/<this>. ASCII and space-free on purpose — it appears in a
// container path, in the generated compose, and (percent-encoded) in the deep-link URL.
@@ -162,6 +162,42 @@ func TestEnsureTraefik_ReconcilesARunningTraefik(t *testing.T) {
}
}
// A controller update that changes only traefik's IMAGE recreates the running traefik on the new image (the v3.7.16
// security bump of 2026-10-27 rides this, felhom.eu R-1304: traefik.yml is unchanged, only the compose's `image:` line
// moves). The control: the same render again is a no-op.
func TestEnsureTraefik_ANewImageRecreatesTraefik(t *testing.T) {
state := stubDocker(t)
touch(t, filepath.Join(state, "running-traefik"), "")
m, calls := tunnelTestManager(t, "owner@example.com")
dir := t.TempDir()
cur, err := infra.RenderTraefik(infra.TraefikData{ACMEEmail: "owner@example.com", Tunnel: true})
if err != nil {
t.Fatal(err)
}
old := strings.Replace(cur["docker-compose.yml"].Content, "image: "+infra.TraefikImage, "image: traefik:v3.7.13", 1)
if old == cur["docker-compose.yml"].Content {
t.Fatal("the render has no `image:` line to age — the test would prove nothing")
}
for name, f := range cur {
touch(t, filepath.Join(dir, name), f.Content)
}
touch(t, filepath.Join(dir, "docker-compose.yml"), old)
if err := m.ensureTraefik(dir, true); err != nil {
t.Fatalf("ensureTraefik: %v", err)
}
cmp, _ := os.ReadFile(filepath.Join(dir, "docker-compose.yml"))
if !strings.Contains(string(cmp), "image: "+infra.TraefikImage) {
t.Fatalf("the compose must carry %s:\n%s", infra.TraefikImage, cmp)
}
if len(*calls) != 1 || (*calls)[0].args != "up -d --force-recreate" {
t.Fatalf("want ONE `up -d --force-recreate` (traefik on the new image), got %+v", *calls)
}
if err := m.ensureTraefik(dir, true); err != nil || len(*calls) != 1 {
t.Fatalf("an unchanged image must not recreate traefik again; err %v calls %+v", err, *calls)
}
}
// A rewrite that would DROP the certificate resolver the running file has is refused (no e-mail in the config).
func TestEnsureTraefik_RefusesToDropTheCertResolver(t *testing.T) {
state := stubDocker(t)