docs(audit): findings triage addendum — F1 corrected root cause + F2 verdict + F3 fixed
F1: original "shim sets SMTP_FROM w/o SMTP_HOST" hypothesis was WRONG (shim injects nothing when off) — real cause is defined-empty SMTP vars + upstream _enable_smtp default true → crash; fixed via _ENABLE_SMTP gate (catalog), live-validated. F2 verdict: REAL finding — roleForMountPath resolves an enrolled user-data drive that isn't a PVE storage to system (fail-safe over-refusal) while /disks calls it user-data; agent-side fix deferred. F3: fixed in controller v0.101.0 (git subprocess deadline). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -217,3 +217,77 @@ No production code, versions, or configuration were changed. The single change i
|
||||
The demo node was destroyed and restored (authorized) and left **running and healthy** at R1 state,
|
||||
with notifications restored. Evidence bundle retained on DooPlex, never committed; no secrets in
|
||||
this report.
|
||||
|
||||
---
|
||||
|
||||
# Findings triage addendum (2026-07-06)
|
||||
|
||||
Follow-up implementation task on F1–F3. F1/F3 fixed + shipped; F2 diagnosed to a verdict (agent
|
||||
untouched — a fix is a separate follow-up). Shipped: catalog `d86e256`, controller **v0.101.0**
|
||||
(`fc28033` sync deadline, `c997d79` agent-refusal surfacing).
|
||||
|
||||
## F1 — CORRECTED root cause (the original hypothesis above was wrong)
|
||||
|
||||
The report's F1 hypothesis — *"the shim sets `SMTP_FROM` without `SMTP_HOST`"* — is **incorrect**:
|
||||
with app-email off (the default) the shim injects **nothing**. Validated against live source +
|
||||
the pinned image. Real cause: the template **always** defines `SMTP_HOST=${SMTP_HOST:-}` and
|
||||
`SMTP_FROM=${SMTP_FROM:-}`; vaultwarden treats a **defined-but-empty** env var as *set*, and with
|
||||
its `_enable_smtp` defaulting **true** its validation (`smtp_host.is_some() == smtp_from.is_empty()`
|
||||
→ `true == true`) errors out and the process exits. Every fresh email-off deploy shipped a dead
|
||||
password manager.
|
||||
|
||||
**Empirical proof** on `vaultwarden/server:1.33.2-alpine` (evidence:
|
||||
DooPlex `evidence/PROBE-F1/`): P1 `SMTP_HOST=`/`SMTP_FROM=` (defined-empty) → **exit 12**, exact
|
||||
campaign error; P2 `+_ENABLE_SMTP=false` → **boots**; P3 `_ENABLE_SMTP=true`+host+from → **boots**.
|
||||
|
||||
**Fix** (catalog): gate the SMTP group with `_ENABLE_SMTP` — compose default `false` (validation
|
||||
skipped, clean boot), flipped `"true"` by the app-email injection via `smtp_mapping.extra`.
|
||||
**Live-validated via the real UI pipeline** (v0.101.0 on 9201): fresh email-off deploy → healthy,
|
||||
`_ENABLE_SMTP=false`, restarts=0; app-email ON → healthy with `SMTP_HOST=felhom-controller` +
|
||||
`SMTP_FROM=vaultwarden@felhom.eu`; OFF → healthy. The other 5 smtp-mapped templates are
|
||||
campaign-boot-proven tolerant (no edits).
|
||||
|
||||
## F2 — VERDICT: **real finding** (agent role-resolution inconsistency; fail-safe direction)
|
||||
|
||||
Campaign-time agent journal (persistent journald survived the reboots), the authoritative evidence:
|
||||
```
|
||||
Jul 06 08:36:03 felhom-agent WARN "local-api: protected — decommission refused by role"
|
||||
vmid=9201 where=/mnt/teszt_enroll role=system
|
||||
```
|
||||
`where` + `role` give the verdict directly (no reproduction needed — the drive was never mutated).
|
||||
|
||||
**Mechanism** (`felhom-agent internal/localapi/disks.go:1020` `roleForMountPath`): it loops the
|
||||
agent's `storage.Observe()` **storage targets** for one whose `MountPath == where`, and **fail-safe
|
||||
returns `RoleSystem` when none matches** (`:1031`). `/mnt/teszt_enroll` is a real user-data USB
|
||||
drive but is **not a PVE storage** (`pvesm status` lists only felhom-usb / felhom-flash — both
|
||||
`content backup` — plus the PBS stores; no `teszt_enroll`). So no target matches → `system` →
|
||||
decommission refused.
|
||||
|
||||
**Why it's a real finding, not a harness artifact:** the agent's own `/disks` inventory
|
||||
**already classifies teszt_enroll as `role: user-data`** (durable_id `uuid:f2236136…`), yet the
|
||||
decommission/eject gate independently resolves it to `system`. Two role-resolution paths disagree:
|
||||
the disk-inventory path knows it's user-data; the gate (keyed on PVE storage targets only) doesn't
|
||||
see a bind-mounted user-data drive that isn't its own PVE `dir` storage. Any such drive hits this,
|
||||
so a legitimate customer "decommission anyway" can be blocked.
|
||||
|
||||
**Severity: MEDIUM, fail-safe direction.** The default is *over-refusal* (refuse rather than wrongly
|
||||
unmount) — it never permits a wrong decommission, only blocks a right one. Not a security hole.
|
||||
|
||||
**Suggested fix (deferred — agent follow-up task, NOT done here):** have `roleForMountPath` fall
|
||||
back to the durable-id / DiskInfo role (or `RoleForRawDevice`) when no storage target matches the
|
||||
mount, instead of blanket-defaulting to `system` — so an enrolled user-data drive is decommissionable
|
||||
regardless of whether it is a standalone PVE storage. Pair with a red-proof on a bind-mount-only
|
||||
user-data drive.
|
||||
|
||||
## F3 — FIXED (controller v0.101.0)
|
||||
|
||||
Real half confirmed: `internal/sync/sync.go runGitInDir` ran `cmd.Run()` with no context, so a hung
|
||||
remote parked the sync goroutine, held `syncing=true`, and refused every later sync until a
|
||||
controller restart. Fixed with a per-command `exec.CommandContext` deadline (`gitCmdTimeout = 120s`);
|
||||
the deadline error names the timeout + masked args. Debounce unchanged. Tests + red-proof shipped;
|
||||
live sync smoke `POST /api/sync` → 2xx post-deploy.
|
||||
|
||||
## Harness note (unchanged — for the record)
|
||||
The P9 finale FAIL in the main report was a harness volid-format bug (epoch vs ISO-8601), fixed in
|
||||
the DooPlex workspace copy; the DR restore mechanism itself is sound (9201 recovered from R1). The
|
||||
other harness mis-scores (H2–H8) stand as written.
|
||||
|
||||
Reference in New Issue
Block a user