diff --git a/assets/icons/app-128.png b/assets/icons/app-128.png new file mode 100644 index 0000000..979ca21 Binary files /dev/null and b/assets/icons/app-128.png differ diff --git a/assets/icons/app-16.png b/assets/icons/app-16.png new file mode 100644 index 0000000..67714c4 Binary files /dev/null and b/assets/icons/app-16.png differ diff --git a/assets/icons/app-22.png b/assets/icons/app-22.png new file mode 100644 index 0000000..9637b74 Binary files /dev/null and b/assets/icons/app-22.png differ diff --git a/assets/icons/app-24.png b/assets/icons/app-24.png new file mode 100644 index 0000000..4922fac Binary files /dev/null and b/assets/icons/app-24.png differ diff --git a/assets/icons/app-256.png b/assets/icons/app-256.png new file mode 100644 index 0000000..f206816 Binary files /dev/null and b/assets/icons/app-256.png differ diff --git a/assets/icons/app-32.png b/assets/icons/app-32.png new file mode 100644 index 0000000..836da8e Binary files /dev/null and b/assets/icons/app-32.png differ diff --git a/assets/icons/app-48.png b/assets/icons/app-48.png new file mode 100644 index 0000000..e2ac74c Binary files /dev/null and b/assets/icons/app-48.png differ diff --git a/assets/icons/app-512.png b/assets/icons/app-512.png new file mode 100644 index 0000000..3f99aa1 Binary files /dev/null and b/assets/icons/app-512.png differ diff --git a/assets/icons/app-64.png b/assets/icons/app-64.png new file mode 100644 index 0000000..339c2e9 Binary files /dev/null and b/assets/icons/app-64.png differ diff --git a/assets/icons/app-contact-sheet.png b/assets/icons/app-contact-sheet.png new file mode 100644 index 0000000..9f7495d Binary files /dev/null and b/assets/icons/app-contact-sheet.png differ diff --git a/assets/icons/hound-app.svg b/assets/icons/hound-app.svg new file mode 100644 index 0000000..6d33b00 --- /dev/null +++ b/assets/icons/hound-app.svg @@ -0,0 +1,26 @@ + + + + Hound Antivirus + + + + + + + + + + + diff --git a/dist/Hound-0.1.0-x86_64.AppImage b/dist/Hound-0.1.0-x86_64.AppImage new file mode 100755 index 0000000..8b334fa Binary files /dev/null and b/dist/Hound-0.1.0-x86_64.AppImage differ diff --git a/dist/hound_0.1.0_amd64.deb b/dist/hound_0.1.0_amd64.deb new file mode 100644 index 0000000..01cf8fd Binary files /dev/null and b/dist/hound_0.1.0_amd64.deb differ diff --git a/gui/dist/favicon-32.png b/gui/dist/favicon-32.png index 4b182b9..836da8e 100644 Binary files a/gui/dist/favicon-32.png and b/gui/dist/favicon-32.png differ diff --git a/gui/src-tauri/icons/icon-128.png b/gui/src-tauri/icons/icon-128.png new file mode 100644 index 0000000..979ca21 Binary files /dev/null and b/gui/src-tauri/icons/icon-128.png differ diff --git a/gui/src-tauri/icons/icon.png b/gui/src-tauri/icons/icon.png new file mode 100644 index 0000000..f206816 Binary files /dev/null and b/gui/src-tauri/icons/icon.png differ diff --git a/packaging/aur/PKGBUILD b/packaging/aur/PKGBUILD new file mode 100644 index 0000000..366a23a --- /dev/null +++ b/packaging/aur/PKGBUILD @@ -0,0 +1,46 @@ +# Maintainer: Hound +pkgname=hound +pkgver=0.1.0 +pkgrel=1 +pkgdesc="Endpoint and supply-chain protection for Linux" +arch=('x86_64' 'aarch64') +url="https://houndav.com" +license=('Apache-2.0') +depends=('systemd-libs') +optdepends=('clamav: the Windows-malware corpus, for the file-server carrier case') +makedepends=('rust>=1.91' 'cargo') +backup=('etc/hound/hound.toml') +install=hound.install +source=("$pkgname-$pkgver.tar.gz::https://git.joelovestech.com/Hound/Antivirus/archive/v$pkgver.tar.gz") +sha256sums=('SKIP') + +build() { + cd "$srcdir/Antivirus" + cargo build --release --locked -p houndd -p hound +} + +check() { + cd "$srcdir/Antivirus" + # The goodware gate runs here too: a rule that fires on an Arch system + # binary must fail the build on Arch, not only on Debian. + cargo test --release --locked --workspace +} + +package() { + cd "$srcdir/Antivirus" + install -Dm755 target/release/houndd "$pkgdir/usr/bin/houndd" + install -Dm755 target/release/hound "$pkgdir/usr/bin/hound" + install -Dm644 packaging/systemd/houndd.service \ + "$pkgdir/usr/lib/systemd/system/houndd.service" + install -Dm644 crates/houndd/rules/hound-builtin.yar \ + "$pkgdir/usr/share/hound/rules/hound-builtin.yar" + install -dm755 "$pkgdir/var/lib/hound/rules" + install -dm700 "$pkgdir/var/lib/hound/vault" + for s in 16 22 24 32 48 64 128 256 512; do + install -Dm644 "assets/icons/app-${s}.png" \ + "$pkgdir/usr/share/icons/hicolor/${s}x${s}/apps/hound.png" + done + install -Dm644 assets/icons/hound-app.svg \ + "$pkgdir/usr/share/icons/hicolor/scalable/apps/hound.svg" + install -Dm644 README.md "$pkgdir/usr/share/doc/hound/README.md" +} diff --git a/packaging/build-appimage.sh b/packaging/build-appimage.sh new file mode 100755 index 0000000..11085e6 --- /dev/null +++ b/packaging/build-appimage.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# +# Build the Hound AppImage. +# +# What an AppImage can and cannot be, for this product: +# +# An AppImage is unprivileged by design — no install, no root, no +# systemd. The execution gate needs CAP_SYS_ADMIN and a filesystem-wide +# fanotify mark, so it is simply not available here, and pretending +# otherwise would be worse than saying so. +# +# What IS available is everything that does not need privilege: +# on-demand scanning, the quarantine vault under the user's own data +# directory, rootkit heuristics, supply-chain checks and the CLI. That +# makes this the "try it without installing anything" build, and the +# AppRun below says exactly that when the gate is asked for. +# +# Needs appimagetool on PATH (or at $APPIMAGETOOL). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VERSION="$(grep -m1 '^version' "$ROOT/Cargo.toml" | cut -d'"' -f2)" +OUT="${OUT:-$ROOT/dist}" +TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}" +APPDIR="$(mktemp -d)/Hound.AppDir" +trap 'rm -rf "$(dirname "$APPDIR")"' EXIT + +if [ -z "$TOOL" ]; then + echo "appimagetool not found. Set APPIMAGETOOL=/path/to/appimagetool" >&2 + exit 2 +fi + +echo "building Hound AppImage ${VERSION}" +( cd "$ROOT" && cargo build --release -p houndd -p hound ) + +mkdir -p "$APPDIR" +chmod 0755 "$APPDIR" +install -Dm755 "$ROOT/target/release/hound" "$APPDIR/usr/bin/hound" +install -Dm755 "$ROOT/target/release/houndd" "$APPDIR/usr/bin/houndd" +install -Dm644 "$ROOT/crates/houndd/rules/hound-builtin.yar" \ + "$APPDIR/usr/share/hound/rules/hound-builtin.yar" + +# The launcher icon is the white mark on periwinkle, sized optically. +install -Dm644 "$ROOT/assets/icons/app-256.png" "$APPDIR/hound.png" +for size in 16 22 24 32 48 64 128 256 512; do + src="$ROOT/assets/icons/app-${size}.png" + [ -f "$src" ] && install -Dm644 "$src" \ + "$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps/hound.png" +done +install -Dm644 "$ROOT/assets/icons/hound-app.svg" \ + "$APPDIR/usr/share/icons/hicolor/scalable/apps/hound.svg" + +cat > "$APPDIR/hound.desktop" <<'DESKTOP' +[Desktop Entry] +Type=Application +Name=Hound Antivirus +Comment=Endpoint and supply-chain protection for Linux +Exec=hound +Icon=hound +Categories=System;Security; +Terminal=true +DESKTOP + +cat > "$APPDIR/AppRun" <<'APPRUN' +#!/bin/sh +# +# Portable-mode launcher. +# +# Everything lives under the user's own directories, so the AppImage +# leaves nothing behind on the system and needs no privilege. The one +# thing it cannot do is gate execution — see below. +set -e +HERE="$(dirname "$(readlink -f "$0")")" +export PATH="$HERE/usr/bin:$PATH" + +# Rules ship inside the bundle; point the daemon at them read-only. +export HOUNDD_RULES_DIR="${HOUNDD_RULES_DIR:-$HERE/usr/share/hound/rules}" + +# Keep state in the user's own dirs rather than /var/lib. +export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" +export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" +export HOUNDD_SOCK="${HOUNDD_SOCK:-${XDG_RUNTIME_DIR:-/tmp}/houndd.sock}" + +mkdir -p "$XDG_DATA_HOME/hound" "$XDG_CONFIG_HOME/hound" + +# A Unix socket path cannot exceed sun_path (108 bytes on Linux), and +# XDG_RUNTIME_DIR is not always short. Fall back rather than failing with +# an error most people cannot act on. +if [ "${#HOUNDD_SOCK}" -ge 100 ]; then + HOUNDD_SOCK="/tmp/houndd-$(id -u).sock" + export HOUNDD_SOCK +fi + +# Start a private daemon if one is not already answering. +if ! "$HERE/usr/bin/hound" status >/dev/null 2>&1; then + "$HERE/usr/bin/houndd" >"${XDG_DATA_HOME}/hound/appimage.log" 2>&1 & + # Wait for the socket rather than sleeping a fixed amount. + i=0 + while [ ! -S "$HOUNDD_SOCK" ] && [ $i -lt 50 ]; do + i=$((i + 1)) + sleep 0.1 + done +fi + +case "${1:-}" in + settings) + case "${2:-} ${3:-}" in + "set exec_gate") + cat >&2 <<'MSG' +The execution gate is not available in the AppImage. + +Blocking a program at execve needs CAP_SYS_ADMIN and a filesystem-wide +fanotify mark, which an unprivileged, uninstalled bundle cannot have. +Everything else works here: on-demand scanning, quarantine, rootkit +checks and supply-chain checks. + +For real-time protection, install the package: + + sudo apt install ./hound_*.deb + sudo hound settings set exec_gate true +MSG + exit 2 + ;; + esac + ;; +esac + +exec "$HERE/usr/bin/hound" "$@" +APPRUN +chmod 0755 "$APPDIR/AppRun" + +mkdir -p "$OUT" +ARCH=x86_64 "$TOOL" --no-appstream "$APPDIR" "$OUT/Hound-${VERSION}-x86_64.AppImage" 2>&1 \ + | grep -vE "^(WARNING|Warning)" || true + +echo "built $OUT/Hound-${VERSION}-x86_64.AppImage" diff --git a/packaging/build-deb.sh b/packaging/build-deb.sh new file mode 100755 index 0000000..927b2b1 --- /dev/null +++ b/packaging/build-deb.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# +# Build a .deb for Ubuntu / Debian / Linux Mint. +# +# Deliberately hand-rolled rather than cargo-deb: the package needs a +# postinst that creates the vault with the right mode, a conffile that +# survives upgrades, and a unit that is enabled but whose gate stays off +# until the operator turns it on. That is easier to read as a script than +# as a pile of metadata, and it is the thing most likely to need auditing. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VERSION="$(grep -m1 '^version' "$ROOT/Cargo.toml" | cut -d'"' -f2)" +ARCH="$(dpkg --print-architecture)" +OUT="${OUT:-$ROOT/dist}" +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT +# mktemp -d creates 0700, and dpkg applies the staging root's mode to "/". +# Installing this package would chmod / to 0700 and break the machine. +chmod 0755 "$STAGE" + +echo "building hound ${VERSION} (${ARCH})" +( cd "$ROOT" && cargo build --release -p houndd -p hound ) + +install -Dm755 "$ROOT/target/release/houndd" "$STAGE/usr/bin/houndd" +install -Dm755 "$ROOT/target/release/hound" "$STAGE/usr/bin/hound" +install -Dm644 "$ROOT/packaging/systemd/houndd.service" \ + "$STAGE/lib/systemd/system/houndd.service" +install -Dm644 "$ROOT/crates/houndd/rules/hound-builtin.yar" \ + "$STAGE/usr/share/hound/rules/hound-builtin.yar" +install -Dm644 "$ROOT/README.md" "$STAGE/usr/share/doc/hound/README.md" + +# Launcher icon: the white mark on a periwinkle tile (app-*.png), not the +# bare brand mark. The tray ladder is a different family and ships with +# the GUI, because tray glyphs must stay transparent to sit on any panel. +for size in 16 22 24 32 48 64 128 256 512; do + src="$ROOT/assets/icons/app-${size}.png" + [ -f "$src" ] && install -Dm644 "$src" \ + "$STAGE/usr/share/icons/hicolor/${size}x${size}/apps/hound.png" +done +install -Dm644 "$ROOT/assets/icons/hound-app.svg" \ + "$STAGE/usr/share/icons/hicolor/scalable/apps/hound.svg" + +install -Dm644 /dev/stdin "$STAGE/usr/share/applications/hound.desktop" <<'DESKTOP' +[Desktop Entry] +Type=Application +Name=Hound Antivirus +GenericName=Antivirus +Comment=Endpoint and supply-chain protection for Linux +Exec=hound +Icon=hound +Categories=System;Security;Utility; +Keywords=antivirus;malware;security;scan;supply chain; +Terminal=true +DESKTOP + +mkdir -p "$STAGE/DEBIAN" + +cat > "$STAGE/DEBIAN/control" < +Depends: libc6 (>= 2.34) +Recommends: clamav-daemon +Homepage: https://houndav.com +Description: Hound Antivirus for Linux + Endpoint and supply-chain protection built for the distributions people + actually run. Scanning is yara-x in process; real-time protection uses + fanotify, so a binary can be refused at execve rather than reported + after it has already run. + . + The execution gate is installed switched OFF. It needs CAP_SYS_ADMIN and + covers the whole root filesystem, so turning it on is the operator's + decision: hound settings set exec_gate true +CONTROL + +cat > "$STAGE/DEBIAN/conffiles" <<'CONFFILES' +/etc/hound/hound.toml +CONFFILES + +install -Dm644 /dev/stdin "$STAGE/etc/hound/hound.toml" <<'CONF' +# Hound Antivirus configuration. +# +# Live settings are managed through `hound settings` and stored per user; +# this file holds the machine-wide defaults the daemon starts from. + +# Deny execution until a verdict is returned. Needs CAP_SYS_ADMIN. +# Off by default: it covers the whole root filesystem, and that is the +# operator's call to make rather than the installer's. +exec_gate = false + +# Mounts the gate covers. Empty means the root filesystem. +exec_gate_paths = [] + +# Never held for a verdict. +exclude_paths = ["/proc", "/sys", "/dev", "/run", "/var/lib/docker"] + +# Files larger than this are allowed through unread. +max_file_size_mb = 100 + +# "quarantine" or "alert". +on_detect = "quarantine" +CONF + +cat > "$STAGE/DEBIAN/postinst" <<'POSTINST' +#!/bin/sh +set -e + +case "$1" in + configure) + # The vault holds live malware: root-only, and on a filesystem where + # nothing in it can be executed even by accident. + mkdir -p /var/lib/hound/vault /var/lib/hound/rules /var/log/hound + chmod 0700 /var/lib/hound/vault + chmod 0755 /var/lib/hound /var/lib/hound/rules + chmod 0750 /var/log/hound + + # Seed the built-in rules where the daemon looks for packs, so an + # offline install still detects something. + if [ -f /usr/share/hound/rules/hound-builtin.yar ]; then + cp -n /usr/share/hound/rules/hound-builtin.yar /var/lib/hound/rules/ || true + fi + + if [ -d /run/systemd/system ]; then + systemctl daemon-reload || true + systemctl enable houndd.service || true + systemctl restart houndd.service || true + fi + + echo "" + echo "Hound is installed and scanning on demand." + echo "" + echo " hound status what the daemon sees" + echo " hound scan ~/Downloads scan a directory" + echo "" + echo "Real-time execution blocking is OFF until you turn it on:" + echo "" + echo " sudo hound settings set exec_gate true" + echo "" + ;; +esac +exit 0 +POSTINST + +cat > "$STAGE/DEBIAN/prerm" <<'PRERM' +#!/bin/sh +set -e +case "$1" in + remove|deconfigure) + if [ -d /run/systemd/system ]; then + systemctl stop houndd.service || true + systemctl disable houndd.service || true + fi + ;; +esac +exit 0 +PRERM + +cat > "$STAGE/DEBIAN/postrm" <<'POSTRM' +#!/bin/sh +set -e +case "$1" in + purge) + # The vault is deliberately NOT removed on `remove`, only on `purge`, + # and even then only after saying so: it may be the sole copy of + # evidence somebody still needs. + echo "Removing the Hound quarantine vault at /var/lib/hound/vault" + rm -rf /var/lib/hound /var/log/hound + ;; +esac +if [ -d /run/systemd/system ]; then + systemctl daemon-reload || true +fi +exit 0 +POSTRM + +chmod 0755 "$STAGE/DEBIAN/postinst" "$STAGE/DEBIAN/prerm" "$STAGE/DEBIAN/postrm" + +mkdir -p "$OUT" +DEB="$OUT/hound_${VERSION}_${ARCH}.deb" +fakeroot dpkg-deb --build --root-owner-group "$STAGE" "$DEB" >/dev/null +echo "built $DEB" +dpkg-deb -I "$DEB" | sed 's/^/ /' diff --git a/packaging/rpm/hound.spec b/packaging/rpm/hound.spec new file mode 100644 index 0000000..b3fd141 --- /dev/null +++ b/packaging/rpm/hound.spec @@ -0,0 +1,70 @@ +Name: hound +Version: 0.1.0 +Release: 1%{?dist} +Summary: Hound Antivirus for Linux +License: Apache-2.0 +URL: https://houndav.com +Source0: %{name}-%{version}.tar.gz +BuildRequires: rust >= 1.91, cargo, systemd-rpm-macros +Requires: systemd +Recommends: clamd + +%description +Endpoint and supply-chain protection built for the distributions people +actually run. Scanning is yara-x in process; real-time protection uses +fanotify, so a binary can be refused at execve rather than reported after +it has already run. + +The execution gate is installed switched OFF. It needs CAP_SYS_ADMIN and +covers the whole root filesystem, so enabling it is the operator's call: +hound settings set exec_gate true + +%prep +%autosetup + +%build +cargo build --release -p houndd -p hound + +%install +install -Dm755 target/release/houndd %{buildroot}%{_bindir}/houndd +install -Dm755 target/release/hound %{buildroot}%{_bindir}/hound +install -Dm644 packaging/systemd/houndd.service \ + %{buildroot}%{_unitdir}/houndd.service +install -Dm644 crates/houndd/rules/hound-builtin.yar \ + %{buildroot}%{_datadir}/hound/rules/hound-builtin.yar +install -dm755 %{buildroot}%{_sharedstatedir}/hound/rules +install -dm700 %{buildroot}%{_sharedstatedir}/hound/vault +for s in 16 22 24 32 48 64 128 256 512; do + install -Dm644 assets/icons/app-${s}.png \ + %{buildroot}%{_datadir}/icons/hicolor/${s}x${s}/apps/hound.png +done +install -Dm644 assets/icons/hound-app.svg \ + %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/hound.svg + +%post +# Seed the built-in rules so an offline install still detects something. +cp -n %{_datadir}/hound/rules/hound-builtin.yar \ + %{_sharedstatedir}/hound/rules/ 2>/dev/null || : +%systemd_post houndd.service + +%preun +%systemd_preun houndd.service + +%postun +%systemd_postun_with_restart houndd.service + +%files +%{_bindir}/houndd +%{_bindir}/hound +%{_unitdir}/houndd.service +%{_datadir}/hound/rules/hound-builtin.yar +%{_datadir}/icons/hicolor/*/apps/hound.png +%{_datadir}/icons/hicolor/scalable/apps/hound.svg +%dir %{_sharedstatedir}/hound +%dir %{_sharedstatedir}/hound/rules +# The vault holds live malware: root-only, never world-readable. +%attr(0700,root,root) %dir %{_sharedstatedir}/hound/vault + +%changelog +* Fri Aug 21 2026 Hound - 0.1.0-1 +- yara-x engine in process; fanotify execution gate diff --git a/packaging/systemd/houndd.service b/packaging/systemd/houndd.service new file mode 100644 index 0000000..b2d55a4 --- /dev/null +++ b/packaging/systemd/houndd.service @@ -0,0 +1,70 @@ +[Unit] +Description=Hound Antivirus daemon +Documentation=https://houndav.com/docs +After=network.target +# The gate is only useful while the filesystem it watches is mounted. +RequiresMountsFor=/var/lib/hound + +[Service] +Type=exec +ExecStart=/usr/bin/houndd +Restart=on-failure +RestartSec=2s + +# ── Privilege ──────────────────────────────────────────────────────── +# +# houndd reduces its own capabilities at startup (see crates/houndd/src/ +# caps.rs). This bounding set is the second half of that: it protects the +# machine if the binary is ever replaced by one that does not, and it +# means the daemon never *holds* the rest even momentarily. +# +# It cannot be narrower. fanotify needs SYS_ADMIN; scanning arbitrary +# files needs DAC_READ_SEARCH; quarantining out of a directory owned by +# someone else needs DAC_OVERRIDE; stripping the execute bit off a file +# we do not own needs FOWNER. +CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_FOWNER +AmbientCapabilities=CAP_SYS_ADMIN CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_FOWNER +NoNewPrivileges=yes + +# ── Filesystem ─────────────────────────────────────────────────────── +# +# ProtectSystem=strict would be the obvious choice and is WRONG here: it +# remounts everything read-only, and quarantine has to remove a threat +# from wherever it landed. ProtectHome is off for the same reason — most +# of what we quarantine is under /home. +ProtectSystem=false +ProtectHome=false +ReadWritePaths=/var/lib/hound /var/log/hound +StateDirectory=hound +LogsDirectory=hound +RuntimeDirectory=hound +PrivateTmp=no + +# ── Everything else we can shut off ────────────────────────────────── +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectKernelLogs=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +ProtectProc=invisible +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@clock @cpu-emulation @debug @module @mount @obsolete @raw-io @reboot @swap +UMask=0077 + +# ── Resources ──────────────────────────────────────────────────────── +# A scanner that eats the machine is its own denial of service. +MemoryMax=1G +TasksMax=64 +LimitNOFILE=65536 +OOMScoreAdjust=-500 + +[Install] +WantedBy=multi-user.target