• v1.0.0-rc.1 fd3e83773d

    Guard v1.0.0-rc.1
    All checks were successful
    release / release (push) Successful in 1m28s
    Pre-release

    adam released this 2026-10-25 19:00:46 +00:00 | -2 commits to main since this release

    Added

    • First version. Guard watches log files (followed through rotation and
      truncation) and the systemd journal (by unit and syslog identifier),
      matches lines with patterns holding one <IP>, counts each address's
      bounty within a window, and bans an address that reaches a watch's
      hits in a Housecarl ban list (Housecarl 0.2): of every port, or of the
      watch's protocol and ports, for a time that grows for repeat
      offenders. IPv6 addresses are counted and banned by the ban list's
      prefix (/64). Never banned: never_ban (addresses, prefixes, host
      names), loopback, link-local, multicast; Housecarl adds its own. Bans
      wait while Housecarl can't be reached, across restarts.

    • Presets: sshd, jarl, housecarl, steward, bind-xfr,
      postfix-sasl, dovecot, nginx-auth, housecarl-drops
      (Housecarl's log of dropped packets, TCP only by default).

    • Commands: guard run, check, config [--example], presets,
      test (a watch's patterns over a log), status and reload (through
      a local socket), bans, ban, unban, version.

    • Reloading: systemctl reload guard, guard reload or SIGHUP reads
      guard.yaml again without losing the bounties. A watch keeps its
      bounties under the same name and takes its new settings at once; its
      source starts again only if it changed; watches gone stop, new ones
      start. A file with a mistake is refused. The Housecarl connection,
      state, socket and log wait for a restart (guard reload says so).

    • A Debian package with a sandboxed systemd unit, as the guard user in
      the adm and systemd-journal groups, published with each release to
      the Gitea Debian registry (stable, or testing for a release
      candidate) along with tarballs for Linux amd64 and arm64.

    • Weights. A pattern may capture the user name tried, with <USER>
      (one word) or a group named user (any form), and a watch's users
      weighs matches by it (users: {root: 5, admin: 3, "*": 1}); a match
      pattern may weigh more on its own ({pattern: …, weight: 3}). The
      weight is how many hits a match counts as; a weight of hits bans on
      the first match. The presets capture the user where their
      program logs it (sshd, Jarl, Housecarl, Steward, Dovecot, nginx).
      guard test shows each match's user and weight.

    • Observe mode: mode: observe (per watch, or defaults.mode) counts as
      usual but bans nobody; Guard logs "would ban (observe mode)" and
      guard status lists the would-be bans. They count as no offence.

    • Escalation, off unless a watch asks: escalate: {after: 3, within: 1d, list: edge, time: 1w} sends an address's third ban within a day
      (counting every watch's bans) to another ban list or action, of every
      port unless it says which, for its own time. escalate: default takes
      defaults.escalate.

    • Actions: a watch can ban with your own commands instead of Housecarl
      (actions: {nft: {ban: …, unban: …}}, ban: {action: nft}), run with
      sh -c and the ban only in GUARD_* environment variables (never
      written into the command). Guard keeps their bans in its state file and
      runs the unban command when each ends, also after a restart; a failed
      ban is tried again while it lasts, a failed unban up to 5 times. A
      Guard whose watches all use actions needs no Housecarl. guard test-action runs an action's ban and unban for a test address; guard bans/ban/unban --action work on its bans through the running Guard;
      guard check checks the commands' syntax. docs/actions.md, with an
      nftables table and script (contrib/nft/, tried on nftables 1.1.3).

    • Restarts lose nothing: Guard keeps where each watch read up to (each
      file's place, the journal's cursor, Housecarl's log) and the watches'
      highest bounties in its state file, every ten seconds and on stopping,
      and goes on from there: a file rotated meanwhile is finished first, a
      truncated one read from its start, one more than 10 MB behind followed
      from its end; a journal cursor the journal no longer has, from now.
      A line older than the watch's window counts no bounty.

    • guard import fail2ban FILTER [--file F] [--mode M] [--set K=V]
      converts a fail2ban filter (its includes, [Init], substitutions,
      mode) into a watch, with notes on what it left out (Python-only
      regular expressions, multi-line patterns, lines that aren't failures).
      Over fail2ban 1.1.0's own samples it finds 73% of what fail2ban finds,
      with no false or wrong address. No fail2ban file is shipped.

    • defaults.max_tracked (100000): the most addresses a watch keeps a
      bounty for. Full, it forgets the lowest bounties first, so a flood of
      one-match addresses never pushes out one close to a ban; logged, and
      counted in guard status.

    • The state file has a format version; one from a newer Guard is refused
      instead of overwritten.

    • The logo (artwork/logo.png, traced to artwork/logo.svg), the
      wordmark and the README's banner (artwork/wordmark.svg,
      artwork/banner.svg, from scripts/wordmark.py), and a ROADMAP.md
      (several Housecarls for 2.0.0).

    Changed

    • The presets are anchored at the start of the line (a syslog head
      allowed, nothing else), so text a client chose earlier in a line can't
      start a match; their sample lines include more planting attempts.
    • housecarl may be left out when no watch bans into a ban list or
      reads Housecarl's log.
    • defaults.ban is written as a watch's ban is: ban: {time: 1h}
      instead of ban: 1h (an error that says so), so ban means the same
      everywhere and can take more defaults later.
    • guard status shows where each watch's bans go and its mode, the
      actions' bans in force, and escalated bans.

    Fixed

    • Numbers in guard.yaml are exact: hits: 0.5 was silently taken for
      the default 5 and hits: 2.7 for 2; a fraction, a quoted number, 0
      or less (and factor below 1, a time of nothing) are now errors, and
      the same holds for every new number.
    • <IP> in a pattern not anchored after the address took
      ::ffff:1.2.3.4 as ::ffff:1; it now takes the whole address.
    • The package's guard user is now in the adm and systemd-journal
      groups itself, as the documentation said, not only through its unit's
      SupplementaryGroups. sudo -u guard guard test and guard check
      could read neither /var/log's files (permission denied) nor the
      journal. Upgrading adds the groups.
    • Shorter, plainer command output. Tables name a journal source by its
      first two units or identifiers (journal postfix/smtpd, postfix/submission/smtpd +1), so presets, check and status
      fit a normal terminal. guard test prints the first 20 matching
      lines (not 200) and the first 20 addresses it would ban (--all for
      every one), single addresses without /32, and counts the matches
      from never_ban addresses. When Housecarl doesn't answer, guard check
      says so once ("Housecarl at URL doesn't answer: connection refused")
      instead of once per ban list with the request's URL, and only asks for
      log:read when Housecarl refused the log. guard status tells "not
      running" from "no permission". guard bans says when there are none;
      guard unban says "1 ban lifted".
    Downloads