Repository navigation
Releases: hermian-security/hermian
Release list
v0.1.0-beta.12
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.--helpgroups the commands
(everyday, setup, response, remove) and ends with examples and links.
Severities use the brand colours.--versionoutput is unchanged. hermian statussays so when the shell'shermianand the daemon run
different versions, which usually means a stale copy earlier inPATH.
Deprecated
general.log_level,ssh.off_hours_startandssh.off_hours_enddo
nothing (off-hours scoring was removed earlier;log_levelwas 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 statusnow
names the usual cause on a fleet: another host withactions = true
sharing the same bot (each press reaches only one of them). It used to
blame a webhook only.docs/FLEET.mdexplains 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
An installer fix for hosts moving from the tarball to the .deb.
Fixed
- Moving from the tarball to the
.debwithinstall.shleft the old
/usr/local/bin/hermianin place, ahead of the package's/usr/bin/hermian
in PATH, sohermian statusand 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
- docs: brand rules and the Hermian Security lockup by @mhdgning131 in #57
- fix(install): remove the old tarball binary when moving to the .deb by @mhdgning131 in #58
- chore(release): 0.1.0-beta.9 by @mhdgning131 in #59
Full Changelog: v0.1.0-beta.8...v0.1.0-beta.9
v0.1.0-beta.8
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 enableopens 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 quiethermian · D1 · timeline
and thehermian showcommand. 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/sshdis 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
- feat: bring the new identity into the product and README by @mhdgning131 in #55
- chore(release): 0.1.0-beta.8 by @mhdgning131 in #56
Full Changelog: v0.1.0-beta.7...v0.1.0-beta.8
v0.1.0-beta.11
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).--jsonfor the same data as JSON.- A signed APT repository,
apt.hermian.me, published by the release
workflow:betafor every prerelease,stablefrom v0.1.0. On
Debian/Ubuntu the installer adds it (key from the release, checked like the
.deb), soapt upgradekeeps Hermian current;--no-apt-reposkips it.
hermian uninstallandapt purgeremove 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 alertslists LOW and above by default
and says how many INFO alerts from the last day it left out;--all
includes them,-sstill 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 182andhermian alerts show 182work like
hermian show 182.- The hidden key source alert says the path is included in the setting
(AuthorizedKeysFileusually 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
- feat: hermian report, a summary beta testers can send by @mhdgning131 in #66
- fix: stop recording routine activity as alerts by @mhdgning131 in #67
- feat(packaging): signed APT repository at apt.hermian.me by @mhdgning131 in #68
- chore(release): 0.1.0-beta.11 by @mhdgning131 in #69
Full Changelog: v0.1.0-beta.10...v0.1.0-beta.11
v0.1.0-beta.10
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
whenAuthorizedKeysFile,AuthorizedKeysCommand, the principals
settings orTrustedUserCAKeyspoint into/dev,/tmp,/runor 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/rdpiion the
test VPS: nothing noticed it before.)
Changed
- Alerts are redesigned in Telegram, email and Discord:
- Telegram:
◉◉◉○ HIGH · Persistenceheader (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_mentionsis empty), even
if it carries@everyonefrom an attacker-chosen username.
- Telegram:
- "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, oftenuser@machine), or "password". The daemon fingerprints
the keys in the files sshd actually reads (AuthorizedKeysFilefrom
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
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
plusallowed_user_ids):- A brute-force digest every 2 hours, only when failed logins targeted
accounts that exist and can log in (or root), withBlock <ip>,
Block allandIgnore 2h. It warns when root can log in with a
password (checked withsshd -T), with aRemind me in 7 daysbutton. - Suspicious logins (after a burst, from a new source, root from a new
place) getIgnore,Disconnect sessionandDisconnect 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 blockedandhermian unblock <ip>manage them.
- A brute-force digest every 2 hours, only when failed logins targeted
hermian enable(which upgrades run) adds settings and sections new in
this version to an existingconfig.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 asconfig.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, whatmanagement_cidrsis for, and an
allowlist example for your own SSH sources. - Telegram alerts open with a
■■■□ HERMIAN - HIGHheader (filled cells
count up with severity),Host : web-01, the bold title, and a sentence
or two: what happened and where, then ahermian showline. 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 readsDisconnect session. - Alert retention: stored alerts and collect bundles older than
general.alert_retention_days(default 90; 0 keeps them) are deleted
hourly, andalerts.logrotates 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 newhermian-watchdogsystemd 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.statusshows the heartbeat age. - Durable alert queues: alerts waiting for a notifying channel are written
tostate/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.shnext 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.debon
Debian/Ubuntu or the tarball elsewhere, always checksSHA256SUMS, and
verifies it withcosignorghwhen installed (checksum-only, with a
warning, otherwise). It takes--versionand--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 callednginx(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 todnf) isn't
the package manager; the script's own path has to be trusted. - LD_PRELOAD under
make,cargoand friends is INFO only when that build
tool is really installed, not just named so. - Running
visudo -corpasswd -Sfrom 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
sudounder a web server, database or cron isn't an operator
session (pty.spawnfrom a web shell no longer downgrades alerts).
- A script named like a tool (
-
The real
crontabinstalling 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/tmpis 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/../tmpor an
includeoutside 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:8080then
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
/tmpexecs for ten
minutes (was two, beaten by asleep 130). -
A setuid file under
/opt(vendor helpers like chrome-sandbox) is HIGH,
not CRITICAL. The default config shows how to allowlist PHPmail(). -
Isolation cuts connections that were already open. A blanket
ct state establishedrule 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:
sshandflusharen't shells (an
rsync cron job touching/tmpwas CRITICAL), and a unit is only flagged
when the program, not an option like--socket /tmp/x, sits in/tmp.
base64 -d | shand a cron job running a/tmpbinary 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)
pagednew sourceon every login, and programs re-alerted asfirst outboundon 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.sha256orconfig.sha256pages 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;statusshows those totals and alerts a channel gave up on.
- A missing
-
The unit restarts after any exit not requested through systemctl and no
longer gives up after five quick failures. It also setsUMask=0077,
ProtectKernelLogsand a private keyring. -
More persistence surfaces are watched:
/etc/pam.d,/etc/environment
andpam_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...