Skip to content

Releases: hermian-security/hermian

v0.1.0-beta.12

v0.1.0-beta.12 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Oct 00:55
d81d762

The CLI gets the identity: one header on every screen, grouped help, brand
colours. Retired config settings are commented out on upgrade, and status
warns when the command and the daemon are different versions.

Changed

  • The CLI wears the identity. Every screen (status, alerts, report, test,
    notify-test) opens with the same header: the mark, hermian, the version
    or screen name, and context on the right. --help groups the commands
    (everyday, setup, response, remove) and ends with examples and links.
    Severities use the brand colours. --version output is unchanged.
  • hermian status says so when the shell's hermian and the daemon run
    different versions, which usually means a stale copy earlier in PATH.

Deprecated

  • general.log_level, ssh.off_hours_start and ssh.off_hours_end do
    nothing (off-hours scoring was removed earlier; log_level was never
    used). Configs that set them still load. hermian enable, which runs on
    every upgrade, now comments them out, with a note, keeping a backup. They
    stop being accepted at v0.1.0.

Fixed

  • When the Telegram button poller gets HTTP 409, hermian status now
    names the usual cause on a fleet: another host with actions = true
    sharing the same bot (each press reaches only one of them). It used to
    blame a webhook only. docs/FLEET.md explains the setup: one bot for
    every host's alerts, a bot of its own only where you want buttons.

Install

curl -fsSL https://github.com/hermian-security/hermian/releases/download/v0.1.0-beta.12/install.sh | sudo sh

The installer ships with this tag: it picks the .deb or the tarball,
checks SHA256SUMS (and verifies it with cosign or gh when installed),
and takes --dry-run and --version. Prefer to inspect first?
Download install.sh from these assets, read it, then run it.
Manual steps and what setup changes: README.

What's Changed

  • ci: branded apt.hermian.me page, and rebuild the site on release by @mhdgning131 in #70
  • docs: early testers wall and a beta report form by @mhdgning131 in #71
  • fix(apt): real 404s on apt.hermian.me, copy buttons off the text by @mhdgning131 in #72
  • docs: running Hermian on several servers by @mhdgning131 in #73
  • chore: remove unused code and a stale doc by @mhdgning131 in #74
  • docs: one evidence file, shorter project notes, plain text by @mhdgning131 in #75
  • fix(config): comment out settings that no longer do anything by @mhdgning131 in #76
  • feat(cli): one header across screens, grouped help, brand colours by @mhdgning131 in #77
  • chore(release): 0.1.0-beta.12 by @mhdgning131 in #78

Full Changelog: v0.1.0-beta.11...v0.1.0-beta.12

v0.1.0-beta.9

v0.1.0-beta.9 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Oct 13:27
0b17cc6

An installer fix for hosts moving from the tarball to the .deb.

Fixed

  • Moving from the tarball to the .deb with install.sh left the old
    /usr/local/bin/hermian in place, ahead of the package's /usr/bin/hermian
    in PATH, so hermian status and every other command kept running the old
    version (the daemon itself was upgraded). The installer now removes it,
    after checking it really is a hermian binary, and says so.

Install

curl -fsSL https://github.com/hermian-security/hermian/releases/download/v0.1.0-beta.9/install.sh | sudo sh

The installer ships with this tag: it picks the .deb or the tarball,
checks SHA256SUMS (and verifies it with cosign or gh when installed),
and takes --dry-run and --version. Prefer to inspect first?
Download install.sh from these assets, read it, then run it.
Manual steps and what setup changes: README.

What's Changed

Full Changelog: v0.1.0-beta.8...v0.1.0-beta.9

v0.1.0-beta.8

v0.1.0-beta.8 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Oct 03:39
94957af

The new identity reaches the product: the CLI banner, Telegram alerts and
alert emails now match the site, with a dot severity meter and the real logo
in emails.

Changed

  • hermian enable opens with the new identity: the mark (three strokes and
    the signal dot, the only coloured part) beside the wordmark and tagline.
    --quiet (used by the package) still prints nothing.
  • Telegram alerts are laid out for scanning: ◉◉◉◉ CRITICAL · web-01, the
    headline, the sentence as a quote card, a quiet hermian · D1 · time line
    and the hermian show command. Same content as before; the evidence still
    stays in the CLI.
  • Alert emails use the identity: brand row, severity stripe and meter, the
    process chain drawn like a terminal with the flagged process in red, facts,
    why it matters, numbered next steps and the command to run. Severity
    colours follow the site (CRITICAL red, HIGH purple), there's an inbox
    preview line, and a dark palette for clients that support it. The logo
    travels inside the message as a small inline image (about 0.6 KB), so
    nothing is loaded from a server and there's no read tracking.
  • The severity meter is now dots (◉◉◉◉ CRITICAL to ◉○○○ INFO), echoing
    the logo, in the CLI, Telegram and email alike.
  • The product is written "Hermian" everywhere people read it: CLI headers,
    alert titles, webhooks, the email sender, systemd unit descriptions and the
    docs. The default email subject prefix is now [Hermian]; configs written by
    earlier installs spell out [HERMIAN], so their mail filters keep working.
    The PAM marker in /etc/pam.d/sshd is unchanged.
  • The README is rewritten and shows the new logo, with a dark-mode variant.

Install

curl -fsSL https://github.com/hermian-security/hermian/releases/download/v0.1.0-beta.8/install.sh | sudo sh

The installer ships with this tag: it picks the .deb or the tarball,
checks SHA256SUMS (and verifies it with cosign or gh when installed),
and takes --dry-run and --version. Prefer to inspect first?
Download install.sh from these assets, read it, then run it.
Manual steps and what setup changes: README.

What's Changed

Full Changelog: v0.1.0-beta.7...v0.1.0-beta.8

v0.1.0-beta.11

v0.1.0-beta.11 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Oct 17:27
5d565c4

Quieter by default, a report for beta testers, and the first release on the
signed APT repository apt.hermian.me. Hosts on an earlier version: rerun the
one-line installer once, and apt upgrade takes over from there.

Added

  • hermian report: a summary to send the maintainers during the beta.
    Version, OS, kernel, coverage, how long it's been installed, alert counts
    by severity and by rule over the last 7 days (--days), delivery health
    and overhead, under the identity banner. Nothing private: no host name,
    addresses, users, paths or commands (titles that would name one are
    withheld). --json for the same data as JSON.
  • A signed APT repository, apt.hermian.me, published by the release
    workflow: beta for every prerelease, stable from v0.1.0. On
    Debian/Ubuntu the installer adds it (key from the release, checked like the
    .deb), so apt upgrade keeps Hermian current; --no-apt-repo skips it.
    hermian uninstall and apt purge remove the entry and the key. Until the
    repository's signing key is configured, releases and installs behave as
    before.

Changed

  • Less noise. "Script executed from a shell" (cron or a service running a
    script through sh) is gone: no other rule used it, and it was 82% of the
    test VPS's alert store. hermian alerts lists LOW and above by default
    and says how many INFO alerts from the last day it left out; --all
    includes them, -s still picks a level.
  • A restart by hermian enable (every install and upgrade) is logged as
    INFO, "Hermian is restarting", instead of paging "Hermian is stopping".
    Any other stop still pages HIGH.
  • hermian alerts 182 and hermian alerts show 182 work like
    hermian show 182.
  • The hidden key source alert says the path is included in the setting
    (AuthorizedKeysFile usually lists several).

Install

curl -fsSL https://github.com/hermian-security/hermian/releases/download/v0.1.0-beta.11/install.sh | sudo sh

The installer ships with this tag: it picks the .deb or the tarball,
checks SHA256SUMS (and verifies it with cosign or gh when installed),
and takes --dry-run and --version. Prefer to inspect first?
Download install.sh from these assets, read it, then run it.
Manual steps and what setup changes: README.

What's Changed

Full Changelog: v0.1.0-beta.10...v0.1.0-beta.11

v0.1.0-beta.10

v0.1.0-beta.10 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Oct 15:16
070f0b4

Redesigned alerts, SSH logins that say which key got in, and a new check
for hidden SSH key files.

Added

  • Hidden SSH key sources are caught. Hermian audits sshd's effective
    configuration (sshd -T) at start and every ten minutes, and pages once
    when AuthorizedKeysFile, AuthorizedKeysCommand, the principals
    settings or TrustedUserCAKeys point into /dev, /tmp, /run or a
    hidden directory, including a setting that was there before Hermian was
    installed. A login with a key from such a file pages too, whatever the
    source address. (Found by planting a key in /dev/.cache/rdpii on the
    test VPS: nothing noticed it before.)

Changed

  • Alerts are redesigned in Telegram, email and Discord:
    • Telegram: ◉◉◉○ HIGH · Persistence header (the host moves to the
      fact sheet), the title on its own line, the sentence as a quote, a
      tap-to-copy fact sheet (host, facts, process chain, rule, time), then
      the risk and next steps in a collapsed quote so the push stays short.
      Detail is shed automatically to stay under Telegram's 4096 characters.
    • Email: an ink header band with the lockup, the severity as a pill
      with a drawn four-dot meter, a spec grid (host, detected, facts), numbered
      steps, both commands in one terminal block, and the Hermian Security
      lockup in the footer. The subject is now
      [Hermian] HIGH: <title> (<host>), so the useful part survives inbox
      truncation. Both logos are inline images (about 1.1 KB each); nothing is
      loaded from a server.
    • Discord: brand colours, the mark as avatar, meter and detection in
      the author line, host and a live relative time, risk and steps as fields.
      Alert text can no longer ping anyone (allowed_mentions is empty), even
      if it carries @everyone from an attacker-chosen username.
  • "Why this matters" is now Risk and "Recommended action" is Next
    steps
    , in the CLI and every channel.
  • SSH login alerts say how the login authenticated: the key's type and
    fingerprint, and the authorized_keys file and line it sits on (with its
    comment, often user@machine), or "password". The daemon fingerprints
    the keys in the files sshd actually reads (AuthorizedKeysFile from
    sshd -T) itself; nothing is spawned per login.
  • Fewer root-login pages for dynamic IPs: a key that has logged in before,
    from a new address on a network (/16, or /32 for IPv6) the account already
    logs in from, is now INFO. Password logins, keys never seen before and
    genuinely new networks still page.

Install

curl -fsSL https://github.com/hermian-security/hermian/releases/download/v0.1.0-beta.10/install.sh | sudo sh

The installer ships with this tag: it picks the .deb or the tarball,
checks SHA256SUMS (and verifies it with cosign or gh when installed),
and takes --dry-run and --version. Prefer to inspect first?
Download install.sh from these assets, read it, then run it.
Manual steps and what setup changes: README.

What's Changed

  • fix(harness): only rewrite files in cleanup when the marker is there by @mhdgning131 in #60
  • feat: redesign alerts for Telegram, email and Discord by @mhdgning131 in #61
  • feat(ssh): show which key logged in, and stop paging for dynamic IPs by @mhdgning131 in #62
  • feat(ssh): catch hidden key sources in sshd's config by @mhdgning131 in #63
  • docs(readme): follow GitHub's theme for the logos by @mhdgning131 in #64
  • chore(release): 0.1.0-beta.10 by @mhdgning131 in #65

Full Changelog: v0.1.0-beta.9...v0.1.0-beta.10

v0.1.0-beta.7

v0.1.0-beta.7 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 15 Oct 23:56
989d3d5

The first release with the one-command installer: a dead-man switch, durable
alert queues, Telegram response buttons, a brute-force digest, and hardening
across the engine, collectors, isolation and packaging.

v0.1.0-beta.6 was tagged but never published: its arm64 build failed on a
lint that only fires on that architecture (#53). Everything below ships here.

Added

  • Telegram response buttons (off by default; notifications.telegram.actions
    plus allowed_user_ids):
    • A brute-force digest every 2 hours, only when failed logins targeted
      accounts that exist and can log in (or root), with Block <ip>,
      Block all and Ignore 2h. It warns when root can log in with a
      password (checked with sshd -T), with a Remind me in 7 days button.
    • Suspicious logins (after a burst, from a new source, root from a new
      place) get Ignore, Disconnect session and Disconnect and block.
    • Buttons act immediately, work only for the listed user ids in the
      configured chat, expire, and each works once. Presses are fetched by
      outbound polling, so nothing listens on the host. Each one is answered
      in the chat and recorded as a local alert.
    • Blocks live in their own nftables table with a timeout
      (response.block_duration_hours, default 24); IPv6 is blocked per /64;
      management CIDRs and allowlisted sources are never blocked.
      hermian blocked and hermian unblock <ip> manage them.
  • hermian enable (which upgrades run) adds settings and sections new in
    this version to an existing config.toml, commented out next to where
    they belong, with their explanations. Values are never changed: the file
    must read back as exactly the same settings or it's left alone. A backup
    is kept as config.toml.bak. Configs from early betas, which had no
    Telegram or email section, now show them.
  • The default config documents every option: what each detection group
    covers, message_thread_id, what management_cidrs is for, and an
    allowlist example for your own SSH sources.
  • Telegram alerts open with a ■■■□ HERMIAN - HIGH header (filled cells
    count up with severity), Host : web-01, the bold title, and a sentence
    or two: what happened and where, then a hermian show line. Evidence,
    process chains, the why and the steps stay in the CLI; they were the same
    story three times in a chat window. No emoji, and the whole alert is
    no longer repeated underneath. The brute-force digest lists its sources
    on one line instead of emitting a raw newline escape in the value, and
    the disconnect button reads Disconnect session.
  • Alert retention: stored alerts and collect bundles older than
    general.alert_retention_days (default 90; 0 keeps them) are deleted
    hourly, and alerts.log rotates at 50 MB with three old copies kept.
  • The dead-man switch: the daemon writes a heartbeat to its run directory
    every 30 s, and a new hermian-watchdog systemd timer (installed by
    hermian enable, checked every minute) pages HIGH and restarts the
    daemon when the heartbeat goes stale. A crash or SIGKILL is reported on
    the next start. status shows the heartbeat age.
  • Durable alert queues: alerts waiting for a notifying channel are written
    to state/pending/<channel>/ and reloaded after a restart, so delivery
    is at-least-once (a crash mid-send can duplicate an alert, never lose
    one). Retention prunes them with the rest of the store.
  • Releases ship an install.sh next to the binaries, and the README leads
    with one command: curl -fsSL .../releases/download/vX.Y.Z/install.sh | sudo sh. It's served from the same tag it installs, picks the .deb on
    Debian/Ubuntu or the tarball elsewhere, always checks SHA256SUMS, and
    verifies it with cosign or gh when installed (checksum-only, with a
    warning, otherwise). It takes --version and --dry-run, and the whole
    script runs from one function so a truncated download can't half-run.
    Manual steps and the strict verification flow moved into the README's
    "Verify manually" details.

Security

  • rustls is updated to 0.23.46 for RUSTSEC-2026-0346, -0347, -0348 and
    -0349. It reaches the binary through every Telegram and webhook delivery.

  • A web or database shell started through another program (nginx -> perl -> sh, awk 'BEGIN{system("sh")}') is HIGH like a direct one; only the
    nearest parent used to count. Downloaders behind an interpreter and relay
    tools (nc, socat) anywhere under a service are HIGH too.

  • A web shell flag no longer marks the web server itself, so its other
    requests aren't escalated for the next ten minutes.

  • Auto-isolation ignores CRITICALs whose web server or database doesn't look
    genuine: the binary must be named like the service in a root-owned
    location, or be a trusted interpreter running as a system account. Any
    local user could otherwise run a program called nginx (or rename python
    to it) and cut the host off. The alert still pages.

  • Tool exemptions are harder to borrow:

    • A script named like a tool (/tmp/dnf, python renamed to dnf) isn't
      the package manager; the script's own path has to be trusted.
    • LD_PRELOAD under make, cargo and friends is INFO only when that build
      tool is really installed, not just named so.
    • Running visudo -c or passwd -S from a cron job or service no longer
      excuses a direct sudoers/shadow write a moment later. The tool has to
      come from an operator session or a package/config manager.
    • A ptrace from a process we never saw exec (a forked child calling itself
      gdb) isn't treated as a debugger; the daemon now reads it from /proc.
    • A TTY or sudo under a web server, database or cron isn't an operator
      session (pty.spawn from a web shell no longer downgrades alerts).
  • The real crontab installing a download-and-run job is CRITICAL, and every
    cron line is checked (only the first four were). A package manager writing
    a unit that runs from /tmp is reported too.

  • A second, different change to the same sudoers, cron, unit,
    authorized_keys, ld.so.preload or account file within the dedup window is
    its own alert; only identical content is folded.

  • Account diffs catch more: uid/gid 0 however written (00), a service
    account getting a login shell, gid and home changes, and new members of
    docker, lxd, incus-admin, libvirt, disk, shadow and adm.

  • ld.so.conf.d checks no longer accept /libevil, /usr/../tmp or an
    include outside ld.so.conf.d.

  • DNS/NTP-port connections to a new public address are judged like any
    other once the baseline is done; only the LAN and learned servers are
    skipped.

  • A known port bound again on another address (127.0.0.1:8080 then
    0.0.0.0:8080) is a new listener.

  • ExecStart = x (spaces around =) is parsed.

  • Listener alerts name the owning process again: the /proc/net/tcp
    parser was one field off and read the refcount as the socket inode, so
    the owner was never found.

  • Exited ancestors of a live process are all kept in the tree, not just its
    parent, so a double-forked implant keeps the web server in its chain.

  • D1 flags escalate related connects, listeners and /tmp execs for ten
    minutes (was two, beaten by a sleep 130).

  • A setuid file under /opt (vendor helpers like chrome-sandbox) is HIGH,
    not CRITICAL. The default config shows how to allowlist PHP mail().

  • Isolation cuts connections that were already open. A blanket
    ct state established rule kept every flow from before the alert alive,
    a reverse shell included. Now only management CIDRs keep their sessions,
    and DNS, metadata and notification endpoints get their replies.

  • Trusted install paths are checked for ownership, not just their prefix.
    A tool under /opt/<app> owned by a deploy user (or any file or parent
    directory someone other than root can rewrite) no longer earns a package
    manager's or admin tool's exemption.

Fixed

  • Ref ids follow the wall clock and never go backwards, so a late event or a
    clock step around midnight can't reissue an id and overwrite an alert file.

  • The staging heuristic works on words: ssh and flush aren't shells (an
    rsync cron job touching /tmp was CRITICAL), and a unit is only flagged
    when the program, not an option like --socket /tmp/x, sits in /tmp.
    base64 -d | sh and a cron job running a /tmp binary are flagged.

  • Known SSH sources, destinations, and connector programs are recorded for
    the lifetime of the install. They used to freeze when the baseline window
    closed, so a source first seen after it (a new ISP address, a jump host)
    paged new source on every login, and programs re-alerted as first outbound on every connect. Novelty now means first sighting, so each is
    reported once.

  • Dedup signatures, auth sources and learned baseline sets are capped, so
    attacker-chosen paths or rotating IPv6 sources can't grow memory without
    bound. Far-past event timestamps are clamped like future ones.

  • Self-protection gaps:

    • A missing binary.sha256 or config.sha256 pages HIGH at startup
      instead of being quietly re-created (delete it, swap the binary, and
      nothing used to fire).
    • Config reloads compare against the config in effect, so writing the
      hash file before the config no longer skips both the reload and its
      alert.
    • A clean stop pages HIGH and waits a few seconds for delivery.
    • Dropped telemetry (eBPF ring loss, inotify overflow, audit ENOBUFS) pages
      HIGH; status shows those totals and alerts a channel gave up on.
  • The unit restarts after any exit not requested through systemctl and no
    longer gives up after five quick failures. It also sets UMask=0077,
    ProtectKernelLogs and a private keyring.

  • More persistence surfaces are watched: /etc/pam.d, /etc/environment
    and pam_env.conf, rc.local, udev rules, boot-time kernel modules, at
    jobs, vendor units in /usr/lib/systemd/system, per-user systemd units,
    and .ssh ...

Read more