packaging: .deb, AppImage, rpm spec, PKGBUILD, hardened unit, app icon
Phase 2. "Distro-agnostic" was a claim with no packaging behind it. .deb and AppImage are both built and tested here; the rpm spec and PKGBUILD are written but not yet built, since neither rpmbuild nor makepkg exists on this machine. The AppImage is deliberately not a packaged daemon. An AppImage is unprivileged by design — no install, no root, no systemd — so the execution gate cannot exist in one, and pretending otherwise would be worse than saying so. What it is instead is the try-before-you-install build: on-demand scanning, quarantine under the user's own data dir, rootkit and supply-chain checks, all with nothing left behind. Asking it for the gate prints why it cannot and how to get it. Verified with an isolated HOME: status, scan, EICAR caught, gate refusal. Two packaging bugs caught by looking rather than assuming: * mktemp -d creates 0700 and dpkg applies the staging root's mode to "/". Installing that package would have chmodded the root directory to 0700 and broken the machine. * AppRun wrote its log before creating the directory, and built a socket path that can exceed sun_path (108 bytes) when XDG_RUNTIME_DIR is long. Both fixed; the socket falls back rather than failing with an error nobody can act on. The systemd unit is hardened as far as this daemon can be. Notably ProtectSystem=strict is WRONG here and is left off on purpose: it remounts everything read-only, and quarantine has to remove a threat from wherever it landed. ProtectHome is off for the same reason. The CapabilityBoundingSet mirrors what caps.rs drops to, so the machine is protected even if the binary is replaced by one that does not reduce itself. App icon: the mark in white on a periwinkle tile, per Joe. Small sizes are not the same artwork scaled down — the mark is line-weight, so at 16px a 62% inset leaves about a pixel and a half of stroke and the head turns to mush. Each size is authored with its own inset and corner radius, and the ground goes flat below 32px because a gradient across 16 pixels is just noise. The tray ladder is untouched: those glyphs stay transparent and re-tint per state so they can sit on any panel colour. Package installs are NOT enabled by default beyond the daemon itself — exec_gate stays off until the operator turns it on, in every packaging format. 99 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BIN
assets/icons/app-128.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/icons/app-16.png
Normal file
|
After Width: | Height: | Size: 675 B |
BIN
assets/icons/app-22.png
Normal file
|
After Width: | Height: | Size: 916 B |
BIN
assets/icons/app-24.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
assets/icons/app-256.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
assets/icons/app-32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/icons/app-48.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/icons/app-512.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/icons/app-64.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/icons/app-contact-sheet.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
26
assets/icons/hound-app.svg
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Hound app icon: the dog mark in white on a periwinkle tile.
|
||||
|
||||
Distinct from the tray ladder (state-*.png), whose glyphs stay
|
||||
transparent and re-tint per security state so they can sit on any
|
||||
panel colour. This one is the launcher, window and About icon.
|
||||
|
||||
Small sizes are NOT this artwork scaled down. The mark is line-weight,
|
||||
so at 16px a 62% inset leaves roughly a pixel and a half of stroke and
|
||||
the head becomes mush. Each size in the family is authored with its own
|
||||
inset and corner radius — the mark grows as the canvas shrinks.
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
||||
<title>Hound Antivirus</title>
|
||||
<defs>
|
||||
<linearGradient id="ground" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#A9A7E8"/>
|
||||
<stop offset="1" stop-color="#8A88D8"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="512" height="512" rx="114.7" ry="114.7" fill="url(#ground)"/>
|
||||
<g transform="translate(97.28,97.28) scale(11.33714)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<path d="M3.80069371,23.1130888 C4.43848107,23.5650959 5.14832497,23.919844 5.9089202,24.1573863 L4.47060635,26.3139954 C4.11309659,26.8500464 3.3887224,26.9947828 2.85267132,26.637273 C2.35490959,26.3052997 2.19455701,25.6569993 2.46043143,25.1368862 L2.52939365,25.019338 L3.80069371,23.1130888 Z M14.5833333,1.16666667 C16.7811895,1.16666667 18.6893658,2.52489596 19.4646473,4.47968116 L19.5335,4.66666667 L24.3833333,4.66666667 C25.0448446,4.66666667 25.5894608,5.16717524 25.6591362,5.81016498 L25.6666667,5.94999885 L25.6666667,11.375 C25.6666667,14.0328704 23.5754085,16.2023012 20.9485691,16.3276156 L20.7083333,16.3333333 L18.7961667,16.3321667 L18.8104203,16.3635263 C19.2754933,17.3735749 19.6443042,18.3167095 19.9164818,19.1943797 L20.0445161,19.627766 C20.7657518,22.2003082 19.7286621,25.1941618 17.5638169,26.637392 C17.0277001,26.9948032 16.3033525,26.8499337 15.9459413,26.3138169 C15.5885301,25.7777001 15.7333996,25.0533525 16.2695164,24.6959413 C17.5764793,23.8246327 18.2515149,21.8759509 17.7978097,20.2576506 C17.4455369,19.0011449 16.8372737,17.5384042 15.9713012,15.8761384 C15.8759396,15.6930882 15.8261459,15.4897339 15.8261459,15.2833333 C15.8261459,14.6231337 16.3245332,14.0799436 16.9690039,14.0080605 L17.1092123,14 L20.7083333,14 C22.0976747,14 23.2349191,12.9206448 23.3272774,11.5547236 L23.3333333,11.375 L23.3333333,6.99883333 L17.6136472,7.00006824 L17.4704091,5.99857195 C17.2758266,4.63808519 16.1483073,3.59829995 14.780321,3.50658494 L14.5833333,3.5 L12.7831667,3.5 L12.5783017,3.50389564 C8.27042644,3.63569563 4.8164366,7.09906173 4.67140846,11.3859455 L4.66666667,11.6666667 L4.66631609,16.8880679 L4.67097109,17.092305 C4.76225687,18.9495018 6.29888585,20.4166667 8.16666667,20.4166667 C10.0996633,20.4166667 11.6666667,18.8496633 11.6666667,16.9166667 C11.6666667,16.1268183 11.523797,15.416107 11.2062777,14.4675176 L10.7570788,13.2056109 C10.1975498,11.6462738 9.90930052,10.544355 9.77521466,9.11410583 C9.71507223,8.47258663 10.1863712,7.90377749 10.8278904,7.84363507 C11.4694096,7.78349264 12.0382188,8.25479164 12.0983612,8.89631084 C12.1996371,9.97658728 12.4067419,10.8376191 12.8110905,12.0132787 L13.3678001,13.5759215 L13.4189446,13.7268763 C13.8098537,14.8947176 14,15.8406089 14,16.9166667 C14,20.1383277 11.3883277,22.75 8.16666667,22.75 C5.13975753,22.75 2.63542176,20.439154 2.35770657,17.4371488 L2.3393951,17.1781792 L2.33333333,16.9166667 L2.33333333,11.6666667 C2.33333333,6.09263102 6.68344803,1.51408374 12.2162292,1.1851268 L12.4308333,1.17483333 L12.4315796,1.16666667 L14.5833333,1.16666667 Z M16.1504743,6.81070338 L16.4543586,8.53411695 C16.5662457,9.1686603 16.1425491,9.77376175 15.5080058,9.88564887 C14.8734624,9.99753598 14.268361,9.57383938 14.1564739,8.93929603 L13.8525895,7.21588246 C13.7407024,6.58133911 14.164399,5.97623765 14.7989424,5.86435054 C15.4334857,5.75246343 16.0385872,6.17616002 16.1504743,6.81070338 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
BIN
dist/Hound-0.1.0-x86_64.AppImage
vendored
Executable file
BIN
dist/hound_0.1.0_amd64.deb
vendored
Normal file
BIN
gui/dist/favicon-32.png
vendored
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
gui/src-tauri/icons/icon-128.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
gui/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
46
packaging/aur/PKGBUILD
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Maintainer: Hound <support@houndav.com>
|
||||
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"
|
||||
}
|
||||
136
packaging/build-appimage.sh
Executable file
|
|
@ -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"
|
||||
186
packaging/build-deb.sh
Executable file
|
|
@ -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" <<CONTROL
|
||||
Package: hound
|
||||
Version: ${VERSION}
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: ${ARCH}
|
||||
Maintainer: Hound <support@houndav.com>
|
||||
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/^/ /'
|
||||
70
packaging/rpm/hound.spec
Normal file
|
|
@ -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 <support@houndav.com> - 0.1.0-1
|
||||
- yara-x engine in process; fanotify execution gate
|
||||
70
packaging/systemd/houndd.service
Normal file
|
|
@ -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
|
||||