v0.328.0: the catalog allow-list — the box refuses container rights a template asks for and the box does not give (R-971, D2 = A)
gates / gates (push) Successful in 45s

internal/privpolicy checks the RENDERED compose (privileged, cap_add, devices, host namespaces, security_opt,
sysctls, volumes_from, build/include/extends, env_file/config files outside the stack dir, aliasing named volumes,
binds outside the box's data roots, the Docker socket, propagation, ${VAR} in a privileged key) against an
allow-list compiled into the controller (one entry: homepage's read-only socket). Asked before every compose
write: the 15-minute sync, the guarded update/ladder step, the install, both settings changes, both restores
(before the stop). A refusal writes nothing, keeps the app on its last good compose, raises the operator-only
event app_definition_refused once per definition and puts one line on the app page. The sync never copies a
template named like a controller infra stack. Structural guard TestAllowList_IsWiredIntoEveryComposeWriter.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
admin committed 2026-10-17 16:34:12 +02:00
1 parent 71fbef6bbe
commit 6d90374f63
26 files changed
+2408 -9

No files matched your search

+35
View File
@@ -1,3 +1,38 @@
## v0.328.0 — the catalog allow-list: the box refuses container rights a template asks for and the box does not give (R-971, operator decision D2 = A) (2026-10-17)
**MinAgent: 0.158.1** — the floor's, carried. Nothing here calls a new agent route. No golden (`09` §3 decision 250).
Task: „agent release, the catalog allow-list (D2 = A), break-glass rotation (D1 = A), off-site history check" (felhom.eu
`REPORT-agent-allowlist-rotation-2026-10-17.md`). **HUB DEPENDENCY:** the operator event `app_definition_refused` needs
hub ≥ v0.157.0 (`allowedEventTypes` + `operatorOnlyEvents`); an older hub answers 400 and drops the event — the box
still refuses and the app page still says so.
- **`internal/privpolicy`** — the check, on the RENDERED compose: `privileged`, `cap_add`, `devices`,
`device_cgroup_rules`, `network_mode`/`pid`/`ipc`/`uts`/`userns_mode`/`cgroup`, `security_opt` (all but
`no-new-privileges`), `sysctls`, `volumes_from`, `build`, `include`, `extends.file`, an `env_file` / `configs` /
`secrets` file outside the stack dir, a named volume with `driver`/`driver_opts`/`external`/`name`, binds outside the
data roots the box gives (a bind source may use only `${HDD_PATH}`, `${USERDATA_PATH}`, `${IMPORT_PATH}`, as its
leading token; each root must be under `/mnt/` or the system data path), the Docker socket anywhere, mount
propagation, and `interpolated <key>` for a `${VAR}` in any privileged key (a catalog deploy-field default could
otherwise change it later). A refusal never prints an env value. Unreadable compose → refused (fail closed).
- **The allow-list** (`internal/privpolicy/allowlist.json`, go:embed — a change in Gitea cannot add to it): app +
service + EXACT setting. One entry: homepage's `bind /var/run/docker.sock -> /var/run/docker.sock ro`. The survey of
all 123 catalog files (templates + ladder steps) found nothing else in any class.
- **Wired into every compose writer:** the 15-minute sync (`sync.copyTemplates` — a refused app keeps its compose AND
its `.felhom.yml`, and is not reported updated), the guarded update / ladder step (`stacks.advancePinTo`), the install
(`DeployStack`), the two settings changes (`UpdateStackConfig`, `UpdateOptionalConfig`), the unit and off-site
restores (asked BEFORE the app is stopped, and again by the writer `RecreateStackDefinitionFromUnit`). Structural
guard `TestAllowList_IsWiredIntoEveryComposeWriter` (AST, comments dropped): every production function that names the
compose file and writes a file is gated or exempt with its reason; the decision points ask before their first
mutation.
- **A catalog template named like a controller infra stack is never copied** (traefik, cloudflared, filebrowser, samba,
felhom-controller) — the sync used to copy one into the infra stack's directory.
- **The refusal is visible:** the operator-only event `app_definition_refused` (once per refused definition), one line
on the app page (`app_info.definition_refused`, both languages; new parity fixture
`app_settings_definition_refused`, every other fixture byte-identical), and the plain message
`err.stacks.definition_refused` for a refused install / settings change / restore.
- Red-proofs: felhom.eu `audits/agent-allowlist-rotation-2026-10-17/P2/redproof-allowlist.txt` (six mutations, each
convicted by its test).
## v0.327.1 — files.<domain> reaches FileBrowser again: a file route by name, not Docker labels (2026-10-17)
**MinAgent: 0.158.1** — the floor's, carried. No golden. Task: as v0.327.0.