Commit graph

7 commits

Author SHA1 Message Date
dev
f039d807cd site: reflect the free community feed and the daily-freshness moat
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>
2026-08-21 16:22:48 -05:00
dev
2cd68ebc57 site: sharpen the copy, sync live design and pricing into the repo
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>
2026-08-21 16:00:47 -05:00
dev
a3f31288fa rootkit: stop reporting every process on the machine as hidden
A clean laptop reported 988 critical rootkit findings; this server
3786, PID 1 among them. Every one was false, and the cause was our own
systemd hardening.

ProtectProc=invisible hides processes the daemon does not own from its
view of /proc, while kill(pid, 0) keeps answering truthfully because it
is a syscall and not a filesystem lookup. The hidden-process check
compares exactly those two sources, so with that setting every process
on the machine looked concealed. Enumerating processes is this daemon's
job, so it needs the default view.

Removing the setting is not enough on its own — hidepid= on the /proc
mount produces the same blindness and we do not control that. So the
detector now recognises when it cannot see:

  - PID 1 is the control. It always exists and nothing hides init; a
    rootkit that did would break the machine it is living on. If PID 1
    answers kill(1, 0) but is absent from the listing, we are blind and
    say so as info rather than crying rootkit.
  - A plausibility ceiling of 32. Hiding a handful of processes is the
    entire point of a rootkit; hundreds means a broken observer. An
    antivirus that reports a critical rootkit finding on every clean
    machine teaches people to ignore the one time it is real.

Also in this change, from testing on a real desktop:

  - Closing the window hides it to the tray instead of exiting, with a
    one-time notification so it does not read as a crash. Quit lives
    only in the tray menu and confirms first. The settings already had
    close_to_tray and confirm_quit fields wired to nothing; they are
    honoured now rather than hardcoded.
  - The tray menu and Scan Home sent the literal string "~". A shell
    would have expanded it, nothing here did, so the daemon was asked
    to scan a directory of that name. It failed silently until the
    per-peer readability check made it audible.
  - Administrative actions elevate through polkit instead of telling
    people to open a terminal. The app tries unprivileged first and
    only on a privilege refusal runs `pkexec hound admin-rpc`, which
    forwards one request as root. auth_admin_keep, because prompting on
    every settings toggle trains people to authenticate without reading
    the prompt. This grants what `sudo hound` already grants to people
    who could already run sudo — a transport, not a new privilege.
  - `hound settings exec-gate on|off` now exists. The install script,
    the AppImage banner, the rpm spec, the AUR install file and
    llms.txt all told users to run `hound settings set exec_gate true`.
    There was no `set` subcommand and no way to enable the execution
    gate from the CLI at all: the flagship paid feature was unreachable
    and the first thing a new user was told to type returned an error.
    A test now asserts every documented command parses.
  - `settings show` displays the exec gate state, and no longer prints
    its own header twice.
  - The CLI help still described ClamAV, which has not been the engine
    for some time. So did the socket permission error, which now
    explains the `hound` group and the log-out-and-back-in it needs.

368 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:21:06 -05:00
dev
3b3586b60a gui: make the desktop app actually launch, and let it reach the daemon
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>
2026-08-21 10:49:58 -05:00
Hound
13a9333e3f site: white button labels, pricing structure, copy icons, and GEO
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>
2026-08-21 09:43:46 -05:00
Hound
ac28efffb0 site: fix mobile overflow at the cause, and give the header a hamburger
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>
2026-08-21 09:35:29 -05:00
Hound
fe49518383 site: houndav.com is live, and the install commands on it work
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>
2026-08-21 09:32:10 -05:00