No description
Find a file
Hound 432e2b825e packaging: four bugs that only a real install could find
Installed the .deb on the live server. The daemon did not start, and
everything below is what that one command surfaced.

1. MemoryDenyWriteExecute=yes stopped the service dead.

   yara-x compiles rules to WebAssembly and JITs them, so it needs pages
   that go writable then executable. With W^X enforced the daemon aborts
   at startup: "unable to make memory executable". The unit had passed
   systemd-analyze verify, which checks syntax and cannot know this.

   Now off, with the reasoning in the unit rather than in a commit
   nobody will read: a hardening directive that stops the service is
   worse than the exposure it prevents, because the machine ends up with
   no antivirus at all. What compensates is spelled out beside it.

2. Hound detected itself.

   The goodware gate reported /usr/bin/houndd as Linux.Coinminer.XMRig
   and Linux.Rootkit.Preload. Correctly: the built-in pack matches on
   "stratum+tcp://", "xmrig", "RTLD_NEXT" and "ld.so.preload", and the
   pack was embedded verbatim, so the daemon's own binary contained all
   of them.

   Not cosmetic. With the execution gate armed, Hound would have refused
   to execute itself or quarantined its own binary — a scanner that eats
   its own daemon the moment protection is switched on.

   The pack is now XOR-masked at build time (build.rs) and unmasked at
   startup. Not a secret — the rules are open source — the only job is
   keeping the literal bytes out of the executable. Two regression tests:
   the embedded blob carries no plaintext rule strings, and a built
   daemon binary in target/ carries none either.

3. The postinst copied the built-in pack into /var/lib/hound/rules,
   where the daemon compiled it a second time and logged a duplicate
   declaration on every start. That directory is for ADDITIONAL packs;
   the built-ins live in the binary. Removed from deb, rpm and AUR.

4. The daemon and the CLI disagreed about the socket. systemd gives the
   service /run/hound; the CLI looked in $XDG_RUNTIME_DIR and reported
   the daemon unreachable — technically true, entirely unhelpful.
   default_socket_path() now prefers /run/hound when it exists, the unit
   states HOUNDD_SOCK explicitly, and a permission error on the socket
   says "try: sudo hound" instead of "Permission denied".

Also: Recommends: clamav-daemon was wrong and apt duly installed clamd,
which took 970 MB of RSS on the live server. clamd is an optional
arm's-length engine, so it is a Suggests. I stopped and disabled the
copy my install pulled in.

Verified on the server after fixing: service active, status reports the
engine and the gate, EICAR caught, rootkit scan clean, and the goodware
gate passes across 3,955 system binaries including the now-installed
houndd.

The gate remains OFF. Turning it on for the host that serves Caddy is a
separate decision.

295 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 07:43:03 -05:00
assets/icons packaging: .deb, AppImage, rpm spec, PKGBUILD, hardened unit, app icon 2026-08-20 23:37:17 -05:00
crates packaging: four bugs that only a real install could find 2026-08-21 07:43:03 -05:00
dist packaging: four bugs that only a real install could find 2026-08-21 07:43:03 -05:00
gui packaging: .deb, AppImage, rpm spec, PKGBUILD, hardened unit, app icon 2026-08-20 23:37:17 -05:00
packaging packaging: four bugs that only a real install could find 2026-08-21 07:43:03 -05:00
.editorconfig Rust engine + CLI over ClamAV Unix socket 2026-08-20 16:59:14 -05:00
.env.example Rust engine + CLI over ClamAV Unix socket 2026-08-20 16:59:14 -05:00
.gitignore Full feature set: realtime monitor, quarantine vault, rootkit scan, settings, events 2026-08-20 20:33:44 -05:00
Cargo.lock supply: read lockfiles, and only ever call a malicious package malicious 2026-08-21 07:29:47 -05:00
Cargo.toml hound-defs: OSV ingest, the IOC index, and signed packs 2026-08-21 07:06:00 -05:00
README.md Engine seam (ScanEngine trait) + Tauri GUI 2026-08-20 17:55:40 -05:00
rust-toolchain.toml Rust engine + CLI over ClamAV Unix socket 2026-08-20 16:59:14 -05:00

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 main is 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.