Two changes to how definitions are distributed, sharing one mechanism.
Incremental updates. The old nightly rebuilt the full per-ecosystem pack
every day, so its hash changed and every client re-downloaded 44 MB of npm
daily. Now the server publishes an immutable baseline plus small daily delta
packs (crates/hound-defs/examples/build-delta.rs); the client — which
already fetches only packs whose sha256 it lacks — pulls the baseline once
and then kilobytes a day. When deltas pile up the builder folds them into a
fresh baseline and drops the old files from the index; the client prunes
whatever the index stops listing, so both the server dir and every client's
defs dir stay bounded. Verified end to end: day-2 fetched only the delta
(baseline untouched), day-3 rebaseline pruned the superseded packs.
Free community tier. Free now gets a recent subset of the public OSV feed
(build-community.rs, ~5,000 newest indicators) so a Free install detects
current threats out of the box — not just the heuristics. Pro is the full
235k corpus, daily/near-real-time freshness, and the curated threat pack.
The client always fetches the community channel and gates the full feed +
threat pack on the licence; a lapse prunes both back to exactly what a fresh
Free install has — community pack + built-in rules — while leaving any custom
.yar the user placed themselves untouched. reload_rules() lets a new threat
pack go live without a daemon restart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the product buyable and the open-source claim true.
Licence system, end to end. license.rs was well-designed dead code; wire
it up: an Ed25519-signed token (same key and verify-before-parse discipline
as definition packs), `hound license install`, houndd loads and verifies at
boot, and the execution gate and full supply-chain feed now gate on
Capability checks. Verification failing always degrades to Free, never to a
locked-out security tool; an expired licence downgrades with the reason
shown. Adds tools/issue-license.py.
Hound Linux threat pack. 34 curated YARA rules — miners, IoT/DDoS bots,
backdoors, rootkits, ransomware, webshells, droppers, reverse shells —
shipped through a new signed rules-pack channel (.rpack) alongside the
definitions feed. Every rule is ELF- or size-anchored and keyed on
family strings, never syscalls; the builder refuses to sign a pack that
matches a system binary (the goodware gate caught two bad rules), and a
regression test proves every rule fires on a sample and stays quiet on a
document about malware.
Action signature verification. The composite action claimed Ed25519
verification "against the same signed manifest the desktop agent uses" but
only compared a same-host sha256. It now fetches latest.json, verifies the
Ed25519 signature over the canonical release statement against the pinned
release key, and installs the checksum from the verified manifest.
Licence resolved to Apache-2.0: Cargo.toml, a real LICENSE file, README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every pack in the published feed was dated 2026.08.21 because that is
when I last ran the ingest by hand. The client-side update machinery
shipped in 0.1.2 works well against a source that never moves, which
means it would have kept every installation confidently green on data
that aged a day for every day that passed — and the staleness warnings
added in the same release would have started telling users the truth
about a problem we caused.
tools/refresh-definitions.sh runs the steps that already existed
(ingest-osv.py, build-pack, index.json) with the properties a published
feed needs:
- Never publishes an empty feed. If every pack fails to build it
exits non-zero and leaves the previous one live. An agent that
installed an empty feed would report a clean machine with no
indicators loaded, which is worse than one keeping yesterday's.
- Packs are written before index.json, and each lands via rename.
The index is what tells an agent a pack exists, so writing it first
would advertise files that are not there yet, and a rename means a
fetch mid-run never sees a half-written pack.
- Old packs stay on disk; the index advertises only the newest per
ecosystem. An agent that has been offline for a while still has a
URL that resolves.
- One ecosystem failing does not cost the others.
The first dry run built a pack called stage-bfsqqa-2026.08.21.pack:
the staging directory was inside the work directory, and the loop
treats every directory in there as an ecosystem. Staging now lives
outside it, and directory names are filtered as well.
The timer runs at 05:20 UTC with Persistent=true, so a builder that was
off does a catch-up rather than silently skipping a day — that being
the exact failure that produces a stale feed nobody notices. The
service runs as the publishing user rather than root: it needs the
signing key and write access to one directory, and a build pipeline
running as root to write a web directory is a bigger target than the
thing it protects.
Verified: the timer is enabled, a real run republished all six packs
and 235,577 indicators, and a client installed from the result.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>