The start-menu entry ran `hound` with Terminal=true — the CLI, which
printed help and exited. No GUI binary had ever been built or packaged.
Four separate faults were stacked behind that report:
1. build-deb.sh now builds and ships hound-gui, and writes a .desktop
entry only when that binary exists. A launcher for software that is
not there is worse than no launcher.
2. Tray icons were loaded from a relative "icons/" path, which resolves
only from the build tree. Installed to /usr/bin the setup hook failed
and Tauri panicked before a window appeared. They are include_bytes!
now — four ~1 KB PNGs that can no longer be missing.
3. The front-end never ran at all. app.js opened with a bare module
specifier ("@tauri-apps/api/core") and there is no bundler, so the
webview could not resolve it and the script silently failed to parse.
The window rendered its static HTML forever, which looks exactly like
a daemon that never answered. withGlobalTauri + window.__TAURI__.
4. build-deb.sh ran the Tauri build as `>/dev/null 2>&1 || true`, so a
config error scrolled past unseen and the package shipped the
PREVIOUS binary. Two fixes appeared to do nothing. That step is no
longer silenced or tolerant of failure, and the build fails outright
on a bare import in gui/dist/*.js.
Guards, because each of these failed quietly: index.html flips to an
interface-error message if app.js never sets a boot flag within 5s. An
antivirus showing "Protected - your system looks healthy" while its own
front-end is dead is the worst failure mode there is.
Then the window came up and could not reach the daemon: the socket was
0700 root:root. Widening it needed more than a chmod, because
quarantine.restore writes files back out as root — handing that to a
desktop group would hand out root. So the daemon now checks SO_PEERCRED
per method (crates/houndd/src/peer.rs):
- group `hound`: status, settings.get, events, quarantine.list,
rootkit.scan, persistence.scan
- scan/supply.sweep: only paths the caller could read itself, decided
by forking a child, dropping to the peer's uid, gid and
supplementary groups, and asking access(2) — which honours ACLs and
mount options, unlike anything reconstructed from mode bits
- everything that writes: root, or the uid the daemon runs as
Unclassified methods fall into Admin, so a new mutating method fails
closed rather than becoming public by omission. The end-to-end socket
test caught that "root only" broke every developer run; the owner
clause collapses to "root" under the packaged root daemon and is
verified to do so.
CAP_SETUID/CAP_SETGID join the gate capability set for the readability
check. There was a test asserting CAP_SETUID must never be retained —
it is updated with the reasoning rather than deleted. The daemon
already holds CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH, so becoming
another user widens nothing that matters. The unit gains Group=hound so
the socket can be chgrp'd without CAP_CHOWN; it stays uid 0.
Also: the footer claimed "engine: ClamAV via Unix socket". It reports
what the daemon actually loaded, which has been yara-x since the engine
was replaced. Every error path in the front-end goes through explain(),
so a privilege refusal reads as "run it from a terminal: sudo hound …"
rather than "daemon error -32000".
358 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ClamAV is a temporary dependency. Everything ClamAV-specific moves
behind a 4-method trait in crates/houndd/src/engine.rs:
trait ScanEngine { name; probe; scan; update }
struct ClamAvEngine // today
const ENGINE // one-line flip when the native engine lands
- parse_clamscan() is now a pure fn with unit tests (OK/FOUND/INCOMPLETE,
dedup, malformed lines)
- main.rs drops all clamscan/clamd/freshclam knowledge; Status reports
engine='clamav' via the trait
- README documents the seam and the wire contract that stays stable
GUI (gui/, Tauri 2, standalone workspace, vanilla JS premium-dark shell):
- system-tray sentinel on the 4-state dog-head ladder (green/amber/red/gray)
with tooltips; clicks open the window / scan / update
- window: protection hero, path + recursive scan, progress bar, results
table, signature update log; state mirrored to the tray
- compiles clean (cargo build, 0 warnings) on Mint 22.3 + webkit2gtk-4.1
All 11 workspace tests pass, incl. the E2E EICAR scan over the real
Unix-socket daemon (clamscan finds the planted EICAR).