No description
Find a file
Hound 020a1fa8bd rules: a rule must earn the right to move somebody's file
Quarantine deletes a file from where its owner put it. Until now every
detection did that, so every false positive was destructive rather than
merely wrong — which on this machine cost an 8.5 MB compiler cache and a
4.3 MB session transcript, the latter's history permanently.

Each rule now declares what Hound may do:

    action = "quarantine"   move it to the vault
    action = "alert"        report it, leave it alone

**The default is alert**, and so is an unrecognised value, and so is a
detection name the engine does not know. One misspelt "quarantne" must
not turn an advisory rule into a destructive one across every machine
that updates.

Quarantine has to be earned by an ANCHOR, not by the author's
confidence:

  EICAR-Test-Signature    quarantine  exact 68-byte payload, size-bounded
  Linux.Coinminer.XMRig   quarantine  ELF magic
  Linux.Rootkit.Preload   quarantine  ELF magic
  Linux.Webshell.PHP-Eval ALERT       content-only — PHP has no file
                                      magic, so it can still match a
                                      security write-up, a log or an AI
                                      transcript quoting a webshell

A test asserts that property directly: any rule declaring quarantine
must contain a file-type check or an exact size bound. A future rule
cannot quietly claim the destructive action without one.

Both the execution gate and the inotify fallback consult it, kept in
step deliberately — a fallback more destructive than the primary path is
a trap for whoever ends up running unprivileged.

Verified live on the gated filesystem: a webshell written to disk is
reported and left in place; an ELF miner written beside it is
quarantined. Event text changed to match — "threat detected in X —
reported, not moved" rather than implying something happened.

One process note. The first attempt at this edit silently did nothing:
the replacement did not match because of indentation, the tooling
reported success, and the webshell was still moved. Second time I made
the edit assert its anchor before applying. That is the third silent
no-op edit in this session and the pattern is now obvious enough to
stop assuming an edit landed.

303 tests pass. Gate off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 08:48:27 -05:00
assets/icons packaging: .deb, AppImage, rpm spec, PKGBUILD, hardened unit, app icon 2026-08-20 23:37:17 -05:00
crates rules: a rule must earn the right to move somebody's file 2026-08-21 08:48:27 -05:00
dist rules: a rule must earn the right to move somebody's file 2026-08-21 08:48:27 -05:00
gui packaging: .deb, AppImage, rpm spec, PKGBUILD, hardened unit, app icon 2026-08-20 23:37:17 -05:00
packaging gate: the mark was invisible to every process but our own 2026-08-21 08:04:19 -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.