Option C pricing on the page: the Free card now names the recent
malicious-package feed it ships with (not just heuristics), and the Pro card
reframes the feed as the value that is actually paid for — the full 235k
corpus, refreshed daily, new threats within hours of disclosure — plus the
incremental delivery. JSON-LD Free offer updated to match. Design untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Joe's note: the visual identity is right, the words were not. This keeps the
design exactly as shipped and rewrites the verbiage to be sharper and more
specific — the hero lede, the stat labels, the "what it catches" framing, the
supply-chain / execution / model-file cards, the agents section, and the
pricing line.
Also reconciles the repo with what is actually live: the current hero-video
design, the new pricing ($59.99/yr Pro, $19/seat/mo Fleet with a 3-seat
minimum), softwareVersion 0.1.12 in the structured data, and the referenced
static assets (hero.mp4, favicon, og-image, privacy/terms, icons). Source
links now point at the public GitHub mirror.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The start-menu entry ran `hound` with Terminal=true — the CLI, which
printed help and exited. No GUI binary had ever been built or packaged.
Four separate faults were stacked behind that report:
1. build-deb.sh now builds and ships hound-gui, and writes a .desktop
entry only when that binary exists. A launcher for software that is
not there is worse than no launcher.
2. Tray icons were loaded from a relative "icons/" path, which resolves
only from the build tree. Installed to /usr/bin the setup hook failed
and Tauri panicked before a window appeared. They are include_bytes!
now — four ~1 KB PNGs that can no longer be missing.
3. The front-end never ran at all. app.js opened with a bare module
specifier ("@tauri-apps/api/core") and there is no bundler, so the
webview could not resolve it and the script silently failed to parse.
The window rendered its static HTML forever, which looks exactly like
a daemon that never answered. withGlobalTauri + window.__TAURI__.
4. build-deb.sh ran the Tauri build as `>/dev/null 2>&1 || true`, so a
config error scrolled past unseen and the package shipped the
PREVIOUS binary. Two fixes appeared to do nothing. That step is no
longer silenced or tolerant of failure, and the build fails outright
on a bare import in gui/dist/*.js.
Guards, because each of these failed quietly: index.html flips to an
interface-error message if app.js never sets a boot flag within 5s. An
antivirus showing "Protected - your system looks healthy" while its own
front-end is dead is the worst failure mode there is.
Then the window came up and could not reach the daemon: the socket was
0700 root:root. Widening it needed more than a chmod, because
quarantine.restore writes files back out as root — handing that to a
desktop group would hand out root. So the daemon now checks SO_PEERCRED
per method (crates/houndd/src/peer.rs):
- group `hound`: status, settings.get, events, quarantine.list,
rootkit.scan, persistence.scan
- scan/supply.sweep: only paths the caller could read itself, decided
by forking a child, dropping to the peer's uid, gid and
supplementary groups, and asking access(2) — which honours ACLs and
mount options, unlike anything reconstructed from mode bits
- everything that writes: root, or the uid the daemon runs as
Unclassified methods fall into Admin, so a new mutating method fails
closed rather than becoming public by omission. The end-to-end socket
test caught that "root only" broke every developer run; the owner
clause collapses to "root" under the packaged root daemon and is
verified to do so.
CAP_SETUID/CAP_SETGID join the gate capability set for the readability
check. There was a test asserting CAP_SETUID must never be retained —
it is updated with the reasoning rather than deleted. The daemon
already holds CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH, so becoming
another user widens nothing that matters. The unit gains Group=hound so
the socket can be chgrp'd without CAP_CHOWN; it stays uid 0.
Also: the footer claimed "engine: ClamAV via Unix socket". It reports
what the daemon actually loaded, which has been yara-x since the engine
was replaced. Every error path in the front-end goes through explain(),
so a privilege refusal reads as "run it from a terminal: sudo hound …"
rather than "daemon error -32000".
358 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four things Joe asked for, one of which needed a decision he did not
make.
WHITE BUTTON LABELS. "White on both themes" could not be done by reusing
--brand: white on the dark theme's #9896E0 is 2.70:1, which fails AA
outright and would have looked washed out rather than crisp. Buttons now
have their own background — #5A58C8 light, #5A58D6 dark, the same
periwinkle the desktop app already uses for its primary button — at
5.52:1 and 5.73:1. The label is white in both, which is what makes the
control read as the same object when the theme changes. Contrast was
computed before choosing the colour rather than after somebody
complained.
PRICING. Cards are equal-height flex columns so the buttons line up
regardless of how many features each lists. The featured tier is marked
by a rule along its top edge and a badge rather than a different fill —
it is the same object, just the one being pointed at. Differentiating
features are bold, each tier carries a one-line footnote answering the
question the price raises ("Annual only", "Over 20 seats? We will quote
you"), and the feature text now says what the thing does rather than
naming it: "a malicious binary is refused before it runs, in 2 ms".
COPY BUTTONS are a clipboard icon that becomes a green tick, with
aria-label toggling to "Copied" so it is not a visual-only signal.
GEO AND SEO — the part with real leverage:
- JSON-LD SoftwareApplication with 11 features and all three offers,
and a FAQPage with nine questions.
- A VISIBLE FAQ backing that schema. Schema describing content that is
not on the page is both invalid and dishonest, so the nine questions
are really there, in <details> elements, and they are the questions
somebody actually asks before installing a root daemon.
- llms.txt — a structured summary written for a model deciding whether
to recommend Hound, including a section of honest limitations.
Linux only, no RPM package yet, gate needs root, small rule pack,
full-download definitions. A recommendation that omits those is
worse than none, and a model that repeats them is a model that got
the answer right.
- robots.txt naming GPTBot, ClaudeBot, PerplexityBot, Google-Extended,
Applebot-Extended, CCBot and others explicitly. Hound exists partly
to be used BY coding agents; an agent reading this site should not
have to guess whether it is welcome. Several of those crawlers treat
silence as refusal, and silence is not the same as consent.
- Canonical, Open Graph, Twitter card, theme-color, sitemap.
Every number in the structured data and in llms.txt is measured or comes
from a published feed. No ratings, no awards, no "trusted by" — a model
that quotes a fabricated number does more damage than one that quotes
nothing, because it is confident.
All four assets verified live: HTML 200, robots 200, sitemap 200,
llms.txt 200, and both JSON-LD blocks parse from the served page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Joe opened the page on a phone and found what I could not: the install
commands pushed the layout sideways, and the header did the same.
ROOT CAUSE, and it was one thing behind both: grid and flex children
default to min-width:auto, so a box holding a long unbreakable string —
a curl command, a terminal block — refuses to shrink below that string
and pushes the whole PAGE sideways instead of scrolling inside itself.
`overflow-x: auto` on the inner element does nothing while its parent
will not shrink. min-width:0 on the children of every grid is the fix,
and it is why the page was fine at 1080px and broken at 390.
The copy blocks also had the button inside the scrolling region, so on a
narrow screen the one control anybody actually uses scrolled out of
reach. The command now scrolls in its own box, the button sits outside
it, and below 720px the command wraps instead of scrolling — the whole
thing visible at once, break-all because a URL has no useful break
points.
HEADER: hiding the nav links on small screens was never a fix, because
the theme switch and the call to action still competed for a 360px bar.
Below 760px the bar now holds the mark and one button, and everything
else moves into a panel: aria-expanded on the control, Escape closes it,
following a link closes it — a panel left open over the thing you just
asked to see is its own bug — and a resize past the breakpoint closes it
so rotating to landscape cannot strand a phone panel over a desktop
layout.
One regression I introduced and caught before it shipped: I had written
`#menu { display: contents }` to make the panel transparent on desktop.
An ID beats a class, so that quietly overrode nav.links's own flex
layout and margin-left:auto and unstuck the desktop navigation from the
right-hand side. The panel styles now live inside the breakpoint and
nowhere else.
I still cannot see this page — the browser here renders with CSS
disabled — so this was reasoned from the cause Joe's report pointed at
rather than from looking. Another pair of eyes on a phone would be worth
having.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 7's first half. A single self-contained page — no build step, no
framework, no dependencies — deployed to /srv/houndav/www and serving.
Downloads published to dl.houndav.com, and both install commands on the
page were run end to end from those public URLs before this commit.
The positioning is the one the product actually earns. It does not lead
with "antivirus", because at $89.95 that reads expensive next to ESET at
$40 and cheap next to JetBrains at $169 — and because the claim would be
wrong. It leads with the fact that turned out to be the whole thesis:
97% of npm's OSV records are malicious packages, not vulnerabilities.
Alongside 235,577 indicators, 2 ms to refuse a binary at execve, and
0.8 ms added to a normal program starting. Every number on that page is
measured, and every one of them appears in a commit message here.
Themes: light is the bare :root, dark is layered twice — once for the
un-stamped "follow the system" state via prefers-color-scheme, once for
an explicit [data-theme="dark"] — so all three of auto/light/dark
resolve as complete sets rather than half a palette. The toggle persists
to localStorage inside try/catch, because a private window throws on
access and a theme switch must not take the page down with it.
The sticky header now sets an opaque background BEFORE the color-mix and
backdrop-filter enhancement, behind @supports. A sticky header that
falls back to transparent puts scrolling content underneath the
navigation, which is worse than having no blur at all.
WHAT I COULD NOT VERIFY, and it matters: the browser available to me
renders with CSS disabled — document.styleSheets.length is 0 with the
<style> element present, every computed style comes back unset and every
font resolves to Times. So I have confirmed the bytes are served
correctly, the CSS parses (balanced braces, no unterminated comments, no
undefined custom properties, no missing semicolons), the HTML tags
balance, the viewport meta is present and there are eight responsive
breakpoints — but I have NOT SEEN THIS PAGE. Somebody with eyes should
look before it is advertised anywhere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>