Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt
8.8 KiB
REPORT — Agent sudoers completeness audit + redeploy (restore multi-drive)
Date: 2026-06-29 · Class: Risky/supervised (host-root allowlist edit + live redeploy) ·
Binary: unchanged (no version bump) · Baseline: felhom-agent main @ c3020ee → this change
55888e5.
What & why
The 2026-06-28 root→non-root agent cutover left configs/felhom-agent.sudoers incomplete:
several commands the agent shells via sudo -n were never granted. The headline casualty —
lxc-info — broke guestInitPID → GuestSeesMount → bound_under_parent, so every external drive was
reported absent, and the controller drive-gate stopped each drive's apps (the "attach one, the other
drops" flapping; audit felhom.eu/documentation/audits/SPIKE-multidrive-mutual-exclusion-2026-06-29.md).
This pass audited every privileged command against the allowlist and closed all read-only/
fixed-vector gaps in one go; arbitrary-execution gaps were surfaced, not added.
Audit table — every privileged command vector vs the allowlist
runner prepends sudo -n <cmd> <args>; the allowlist matches path + arg vector.
| Command vector (shape) | Call site | Alias | Status |
|---|---|---|---|
lxc-info -n [0-9]* -p -H |
intermediary.go:256 guestInitPID |
FELHOM_INTERMEDIARY | GAP → CLOSED |
mount --make-private /mnt/felhom-drives |
intermediary.go:110 EnsureSharedParent |
FELHOM_INTERMEDIARY | GAP → CLOSED |
systemctl restart dnsmasq |
lanresolver restartDnsmasq |
FELHOM_DNSMASQ | GAP → CLOSED |
pct set [0-9]* -onboot 1 |
backhalf.go:162 (F3 autostart) | FELHOM_PROVISION | GAP → CLOSED |
pct reboot [0-9]* |
guestbind.go:100 RebootGuest (disks.go:448) |
FELHOM_GUESTHOOK | GAP → CLOSED |
mount --make-shared /mnt/felhom-drives |
intermediary.go:113 | FELHOM_INTERMEDIARY | OK |
mount --bind /mnt/felhom-drives /mnt/felhom-drives |
intermediary.go:107 | FELHOM_INTERMEDIARY | OK |
mount --bind /mnt/*/felhom-data /mnt/felhom-drives/* |
intermediary.go:204 | FELHOM_INTERMEDIARY | OK |
umount /mnt/felhom-drives/* |
intermediary.go:199,340 | FELHOM_INTERMEDIARY | OK |
mkdir -p /mnt/felhom-drives[/*] , /mnt/*/felhom-data |
intermediary/backhalf | FELHOM_INTERMEDIARY | OK |
chown 100000:100000 /mnt/*/felhom-data |
intermediary.go:181 | FELHOM_INTERMEDIARY | OK |
install -m 0755/0644 -- /tmp/felhom-shared-parent.{sh,service} … |
intermediary.go:136,144 | FELHOM_INTERMEDIARY | OK |
systemctl enable felhom-shared-parent.service |
intermediary.go:150 | FELHOM_INTERMEDIARY | OK |
systemctl daemon-reload |
intermediary.go:147 | FELHOM_MOUNT | OK |
pct set [0-9]* -mp8 /mnt/felhom-drives* |
backhalf.go:184 | FELHOM_INTERMEDIARY | OK |
pct set [0-9]* -mp[0-9]* /var/lib/felhom-agent/guests/* |
backhalf.go:152 | FELHOM_PROVISION | OK |
chown -R 100000:100000 /var/lib/felhom-agent/guests/* |
backhalf.go:145 | FELHOM_PROVISION | OK |
install -m 0755 -- /tmp/felhom-guest-hook.sh /var/lib/vz/snippets/… |
guesthook install.go:45 | FELHOM_GUESTHOOK | OK |
pct set [0-9]* --hookscript local:snippets/felhom-guest-hook.sh |
guesthook install.go:55 | FELHOM_GUESTHOOK | OK |
pct set [0-9]* --delete mp[0-9]* |
decommission/eject | FELHOM_GUESTHOOK | OK |
smartctl -a -j /dev/<disk> |
privileged.go:150 SMART (observe.go:90) |
FELHOM_DISK | OK |
lvs --reportformat json … |
storage | FELHOM_DISK | OK |
blkid -p -o export /dev/* , lsblk -J -o … /dev/* |
format | FELHOM_FORMAT | OK |
mkfs.ext4 -F /dev/* , mkfs.xfs -f /dev/* |
format | FELHOM_FORMAT | OK |
apt-get install -y -q dnsmasq |
lanresolver:107 | FELHOM_DNSMASQ | OK |
install -m <mode> /tmp/felhom-resolver-*.conf /etc/dnsmasq.d/felhom-*.conf |
lanresolver:266 | FELHOM_DNSMASQ | OK |
systemctl enable --now dnsmasq , reload dnsmasq |
lanresolver:117 | FELHOM_DNSMASQ | OK |
rm -f /etc/dnsmasq.d/felhom-*.conf |
lanresolver:178 | FELHOM_DNSMASQ | OK |
pct exec [0-9]* -- ip -4 -o addr show dev eth0 |
lanresolver:187 | FELHOM_DNSMASQ | OK |
pct exec [0-9]* -- docker exec felhom-controller cat …/controller.yaml |
lanresolver:213 | FELHOM_DNSMASQ | OK |
sensors -j |
privileged.go:169 Sensors |
— | GAP — DEFERRED (no caller + lm-sensors not installed; path unverifiable) |
mount UUID=<uuid> <target> (+mkdir -p) |
privileged.go:139 MountUSBByUUID |
— | no daemon caller (legacy) — not added |
pct create <vmid> … --features nesting=1,keyctl=1 … |
privileged.go:124 CreateGoldenLXC |
— | SURFACED (maintenance/broad; no daemon caller) |
pct exec [0-9]* -- {cat,docker,bash -c "<interp>",systemctl} … |
controllerswap.go GuestExec | — | SECURITY FORK — SURFACED, NOT added |
Note:
guest_attached=falsein the/disksreport is not a sudoers gap —GuestConfigis a Proxmox API call (query.go:51), and in the intermediary modelGuestAttached(legacy per-mp bind detection) is expected-false; the gate keys onBoundUnderParent, the fixed signal.
Sudoers diff (additive)
- FELHOM_INTERMEDIARY:
+ mount --make-private /mnt/felhom-drives,+ lxc-info -n [0-9]* -p -H - FELHOM_DNSMASQ:
+ systemctl restart dnsmasq - FELHOM_PROVISION:
+ pct set [0-9]* -onboot 1 - FELHOM_GUESTHOOK:
+ pct reboot [0-9]*
visudo -cf clean (staged file + whole /etc/sudoers after install).
§5.5 — surfaced for operator decision (NOT executed)
GuestExec general pct exec <vmid> -- … (controller-swap self-update / Phase-2 managed
updates). The five vectors it runs:
cat <controllerImageFile>(read/etc/felhom-controller-image)docker image inspect <image>bash -c "printf '%s\n' '<image>' > <controllerImageFile>"← interpolated write (arbitrary)systemctl restart <bootstrapUnit>docker inspect -f <fmt> <controllerContainer>
Granting pct exec [0-9]* -- * = arbitrary root execution in any guest; not added. Consequence:
controller-swap / managed auto-update is currently BROKEN under the non-root agent (manual
bootstrap-mechanism deploys still work). Operator decision needed: narrow per-vector grants (hard —
#3 interpolates the image string and uses bash -c), or run the swap through a different trust path
(e.g. a guest-side agent-authenticated endpoint). Not blocking day-to-day; surfaced for follow-up.
Also surfaced: pct create (golden build — maintenance, broad args, no daemon caller) and
mount UUID=… (MountUSBByUUID, unreferenced/legacy — current USB mounts use systemd .mount units
via FELHOM_MOUNT).
Live validation (felhom-pve, guest 9201) — ALL PASS
- Deploy: staged →
visudo -cfOK →install -m 0440 -o root -g root /etc/sudoers.d/felhom-agent→visudo -cf /etc/sudoersOK. No agent restart (sudo reads the drop-in per call;guestInitPIDrunslxc-infofresh each call). sudo -nprobes (as felhom-agent):lxc-info -n 9201 -p -H→ returns PID2137736(was "a password is required");systemctl restart dnsmasq,pct reboot 9201,pct set 9201 -onboot 1→ ALLOWED;mount --make-private /mnt/felhom-drives→ matched (probed viasudo -l, not executed — would churn the live parent).- Agent report flipped:
/disksnow reportsbound_under_parent=truefor bothfelhom-usbandfelhom-flash(was false/false). - Gate quiesced: controller logged
[gate] drive RETURNED …/felhom-usb — re-attached + restarted gate-stopped appsand the same forfelhom-flash; nextcloud auto-restarted and heldUp (healthy); zerodrive ABSENT/ disconnect / stop events across the following ~2 min. - Real UI (claude-in-chrome,
/settings): both drives show Aktív concurrently — felhom-usb (915.8 GB, HD710 PRO, "1 alkalmazás használja") + felhom-flash (117.1 GB, Flash Drive). The multi-drive mutual-exclusion symptom is gone.
Notes left for follow-up
- Live host-side "doubling" (each submount under both the parent and root, because the live
/mnt/felhom-drivesis in root's peer groupshared:1) is left as-is — re-runningmake-privatenow would churn the peer group and orphan the working guest slave (the very failureEnsureSharedParentguards against). The new grant lets the next clean host boot establish the parent correctly viafelhom-shared-parent.sh; verify the parent has its own peer group after that boot. Latent/harmless today (the guest sees both drives). - 03-host-agent.md carries only a high-level root-boundary narrative (§51), no line-level allowlist table — nothing to sync. The sudoers file + CHANGELOG + this REPORT are the record.
- Durable countermeasure (separate task): an agent capability self-probe at startup (does each
critical
sudo -nvector resolve?) → report denials to the hub. Would have caught all of these at cutover.