Armed the gate on / on the live server. Aborted after about twenty seconds. The box was never at risk — Caddy stayed sub-millisecond and load never rose — but the gate blocked reads of an AI agent's session transcript, reporting it as Linux.Coinminer.XMRig. It was not wrong about the bytes. That transcript contains "stratum+tcp://", "donate-level" and "xmrig" because the miner rule was being written in that session. The rule matched a document ABOUT malware. Three bugs, none of which the tmpfs stage could have shown: 1. The miner rule had no file-type condition, so any text mentioning mining tripped it: threat-intelligence reports, security blog posts, support tickets, an antivirus's own logs. It now requires ELF magic, as the rootkit rule always did. Two regression tests: a transcript discussing the rule is clean, and an ELF carrying the same strings still matches — the fix must not cost the detection it exists for. 2. The gate requested FAN_OPEN_PERM, so it held every OPEN, not every execve. A matching file could not be read by anything. That is a different product from the one advertised, and on a multi-tenant box it is a denial of service against the operator rather than a defence. Read events are no longer requested at all. FAN_OPEN_EXEC_PERM and FAN_CLOSE_WRITE cover the threat: execution is refused before it happens, and anything malicious written to disk is quarantined when the write completes. An interpreted script is caught as it lands rather than as it is read — the same protection, one step earlier. `serve` also guards deny-on-exec explicitly, so re-requesting read events later cannot silently restore the old behaviour. 3. Hound did not exclude its own state. /var/lib/hound and /run/hound are now always excluded; the vault holds live malware by definition. Henry asked whether the single watchdog rescue was queue pressure or scan time. It was scan time: the gate inherited the on-demand 100 MB limit and tried to read and match a multi-megabyte transcript inline while holding a process. A gate's budget is a deadline, not a size, so it now caps at 32 MB — anything larger is allowed through unread rather than turned into a rescue, which is a process released unscanned and worse than never having looked. Dropping read events made everything faster, because most opens on a running machine are reads: latency +1.38 -> +0.79 ms per exec throughput 2,680 -> 4,178 execs/sec (58% of ungated, was 36%) events 1,179 in five seconds on an idle tmpfs -> 1 Re-verified on the tmpfs: an ELF miner is quarantined before it can even be made executable, a document naming every one of its strings is readable, and a clean binary runs. 297 tests pass. The gate stays off; stage 3 gets attempted again with these fixes and fresh numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| assets/icons | ||
| crates | ||
| dist | ||
| gui | ||
| packaging | ||
| .editorconfig | ||
| .env.example | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
Hound Antivirus
A premium, freemium antivirus for Linux. One engine, three faces:
a Rust daemon (houndd), a CLI (hound), and a Tauri GUI with a
system-tray sentinel that changes color with your security state.
Built for the distros people actually run: Ubuntu, Debian, Linux Mint, and anything else that ships ClamAV.
Repository layout
antivirus/
├── Cargo.toml # Rust workspace
├── crates/
│ ├── hound-api/ # shared wire types + socket client (daemon/CLI/GUI all use it)
│ ├── houndd/ # the daemon: Unix-socket API over a pluggable engine
│ └── hound/ # CLI client
├── assets/icons/ # dog-head brand mark + 4-state tray ladder
└── gui/ # Tauri 2 desktop app (system tray + scan UI)
Architecture
houndd (Rust daemon — the engine)
┌──────────────────────────────────┐
│ ScanEngine trait │
│ ├─ L1 ClamAV signatures (now) │
│ ├─ L2 Curated threat packs(Pro)│
│ ├─ L3 Behavioral monitor (Pro) │
│ └─ L4 Supply-chain checks(Pro) │
└──────────────┬───────────────────┘
Unix socket (JSON-RPC, line-delimited)
┌───────────┼───────────┐
hound CLI GUI (Tauri) future modules
The daemon is the only process that touches a scanning engine. CLI and GUI are thin clients — so future suite tools (firewall, updater, …) plug into the same socket.
Swapping the engine (the ClamAV seam)
ClamAV is a temporary dependency. Everything ClamAV-specific — version
probe, signature freshness, the clamscan subprocess + output parsing,
freshclam — lives in one file behind a four-method trait:
crates/houndd/src/engine.rs
trait ScanEngine { name; probe; scan; update }
struct ClamAvEngine // today
const ENGINE: ClamAvEngine // ← flip this line when the native
// engine lands; nothing else in the
// daemon, CLI, GUI, or wire API moves
The wire stays engine-agnostic: Status.engine names the implementation
("clamav" today) and Status.db carries what any signature store has —
a file name and a timestamp. When our own Rust engine ships, it's a new
ScanEngine implementation, a one-const flip, and the tray/CLI/GUI simply
start reporting the new engine name.
Icon system
The brand mark is a solid dog head (assets/icons/hound.svg), a single
flat fill. It ships in two treatments:
- Brand ladder
hound-{16,22,24,32,48,256}.png— native periwinkle#9896E0, for the window icon, About box, and marketing. - Tray-state ladder
state-<name>-{16,22,24,32,48}.png— the same path re-tinted per security state, for the system-tray sentinel:
| State | Fill | Meaning |
|---|---|---|
protected |
#22C55E green |
up-to-date / protected / clean |
scanning |
#F59E0B amber |
scan in progress / signatures need update |
threat |
#EF4444 red |
infection found |
paused |
#6B7280 gray |
real-time monitor off |
Green is the good state; amber is work in progress, never a failure.
Quickstart (development)
Prereqs: Rust (see rust-toolchain.toml), Node 20+, ClamAV, and the
Tauri system libs (libwebkit2gtk-4.1-dev, libgtk-3-dev, libayatana-appindicator3-dev).
# 1. Signatures (needs the clamav freshclam DB)
sudo freshclam
# 2. Daemon (terminal 1)
cargo run -p houndd
# 3. Scan (terminal 2)
cargo run -p hound -- status
cargo run -p hound -- scan ~/Downloads
# 4. GUI
cd gui && npm install && npm run tauri dev
Verifying the engine with the EICAR test file
EICAR is the industry-standard 68-byte test signature — every AV that works will flag it. Generate it and scan it:
printf 'X5O!P%%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > /tmp/eicar.com
cargo run -p hound -- scan /tmp/eicar.com
# expect: exit code 1, "Eicar-Test-Signature FOUND"
Updating signatures
hound update wraps freshclam (trying sudo freshclam first, since
plain-user runs can't write /var/lib/clamav and /var/log/clamav). The
GUI's "Update Signatures" button drives the same RPC and shows the log.
cargo run -p hound -- update # or: hound update --json
The GUI (gui/)
A Tauri 2 desktop app — a thin view over the same houndd socket the
CLI uses (via the shared hound-api client), so the window and the
command line never disagree about your security state.
gui/
├── dist/ # the front-end (vanilla HTML/CSS/JS, premium dark shell)
└── src-tauri/ # Tauri 2 shell + system-tray sentinel
The tray sentinel swaps the 4-state icons (green/amber/red/gray) as your state changes; the window shows a live protection hero, a scan progress bar, a results table, and the signature-update log.
Build it:
cd gui
npm install
npm run tauri dev # dev with hot reload
npm run tauri build # → .deb in src-tauri/target/release/bundle/
Git conventions
-
Branch
mainis deployable; small, focused commits. -
No hardcoded secrets. For pushes, the bot token lives in a repo-local credential file (never tracked):
git config credential.helper 'store --file=.git/.git-credentials' chmod 600 .git/.git-credentials echo 'https://<user>:<token>@git.joelovestech.com' >> .git/.git-credentials -
Commits: imperative subject, ≤ 72 chars. Example:
houndd: add line-delimited JSON-RPC socket API
License
TBD — core daemon likely proprietary (freemium), shared CLI possibly OSS.
Decision pending; workspace.package.license = MIT is a placeholder.