Releases: anhtuank7c/pamsignal
Release list
0.3.0 — Sudo/su brute-force detection
Minor release. Three user-visible threads land together: a new local privilege-escalation brute-force detector, a packaging refactor that aligns the install layout with FHS and removes ~25 lines of post-install shell munging in debian/rules + pamsignal.spec, and the long-promised retirement of the PAMSIGNAL_* journal field set. Two breaking changes: the daemon binary moves from /usr/bin/pamsignal to /usr/sbin/pamsignal, and journalctl queries that filter by PAMSIGNAL_* need to switch to the ECS-aligned EVENT_* / USER_* / SOURCE_* field names. Both have one-line replacements documented in the relevant entries below.
Breaking
- Daemon path moved to
sbindir(FHS §4.10). Packaged builds now install to/usr/sbin/pamsignalinstead of/usr/bin/pamsignal; dev installs land at/usr/local/sbin/pamsignal. The package manager handles the file relocation atomically on upgrade and the systemd unit'sExecStartis updated in lockstep, soapt upgrade/dnf upgradeis the only action a regular user needs. Anyone scripting against the absolute/usr/bin/pamsignalpath (e.g. an out-of-tree systemd override) needs to update. - Legacy
PAMSIGNAL_*journal fields removed. Through 0.2.x the daemon emitted both ECS-alignedEVENT_*/USER_*/SOURCE_*/HOST_*/SERVICE_*/PROCESS_*fields and a parallelPAMSIGNAL_*dictionary; only the ECS set survives. Savedjournalctlqueries that filter byPAMSIGNAL_EVENT=LOGIN_FAILEDetc. need to switch toEVENT_ACTION=login_failure. The full mapping (PAMSIGNAL_USERNAME→USER_NAME,PAMSIGNAL_SOURCE_IP→SOURCE_IP,PAMSIGNAL_PORT→SOURCE_PORT,PAMSIGNAL_SERVICE→SERVICE_NAME,PAMSIGNAL_HOSTNAME→HOST_HOSTNAME,PAMSIGNAL_PID→PROCESS_PID,PAMSIGNAL_TARGET_USER→USER_TARGET_NAME) is indocs/architecture.mdand therefactor!:commit body. The JSONpamsignal.event_type/pamsignal.attempts/pamsignal.window_secwebhook fields are unchanged — those are vendor-specific by intent and not part of this retirement.
Features
- Sudo / su brute-force detection.
ps_parse_messagenow recognizespam_unix(<svc>:auth): authentication failure;and extractsruser=(actor),user=(elevation target), andrhost=(remote endpoint when present). The brute-force tracker keys by source IP whenrhost=is a valid IP literal (covers SSH→sudo chains, indistinguishable from sshd brute-force) and by actor username for pure-local sudo/su attempts. A newps_notify_local_brute_forceemits the alert with chat-text format[ALERT] auth.brute_force_detected actor=alice target=root attempts=5 window=300s service=sudo host=…and ECS JSON{"user":{"name":"alice","target":{"name":"root"}}, "service":{"name":"sudo"}, ...}(nosource.*). Per-event chat alerts are suppressed for sudo/suLOGIN_FAILEDto avoid one-ping-per-mistyped-password noise; the journal entry is still written for every individual failure sojournalctlhistory is complete, and only the threshold-breach alert fires. The brute-force tracker entry struct gainskey_type(IP vs LOCAL_USER),service, andtarget_usernamefields; the existingipfield is renamed tokey(64-byte buffer fits bothINET6_ADDRSTRLENand a username). New tests: 6 parser cases, 6 tracker cases (including the IP-vs-local-actor non-collision invariant), 1 notify smoke. login(1) failures parse but are not yet keyed for tracking — out of scope for this round.
Packaging
- meson installs the systemd unit to the vendor path natively:
pamsignal.service.inis now a template;configure_filesubstitutes@sbindir@at configure time and installs to the dir reported bypkg-config --variable=systemdsystemunitdir systemd(with a<prefix>/lib/systemd/systemfallback). Bothdebian/rulesandpamsignal.specdrop the post-installmv(out of/etc/systemd/system) andsed(/usr/local/bin→/usr/bin) workarounds — the artifact is correct straight out ofmeson install. - RPM
%precreates the group explicitly before the user:groupadd -r pamsignalruns beforeuseradd -r -g pamsignal …soProvides: group(pamsignal)is honored even on hosts whereUSERGROUPS_ENABis unset anduseraddwould otherwise skip auto-creating the matching group. -
PS_DEFAULT_CONFIG_PATHis now derived fromsysconfdirat configure time: a newinclude/paths.h.intemplate is generated into the build dir viaconfigure_file, substituting@sysconfdir@fromget_option('prefix') / get_option('sysconfdir'). Packaged builds (--sysconfdir=/etc) still embed/etc/pamsignal/pamsignal.conf; a devmeson installwith the default--prefix=/usr/localnow correctly embeds/usr/local/etc/pamsignal/pamsignal.confso the daemon finds its conf without an explicit--configflag. - Daemon binary moved from
bindirtosbindir(FHS §4.10: system administration daemons belong insbin, notbin). Packaged builds now install to/usr/sbin/pamsignal(was/usr/bin/pamsignal); dev installs land at/usr/local/sbin/pamsignal(was/usr/local/bin/pamsignal). The systemd unit'sExecStartis updated automatically by theconfigure_filesubstitution. Thepamsignalcommand name is unchanged —/usr/sbinis in root'sPATHon every supported distro, and ordinary users invoke the daemon only viasystemctl. RPM%filesswitches to%{_sbindir}/pamsignal. Upgrade behavior: on the deb/rpm transition, the package manager removes the old/usr/bin/pamsignalfile and installs the new one at/usr/sbin/pamsignal;systemctl daemon-reloadis auto-fired and the unit's new ExecStart points at the new path. Anyone scripting against the absolute/usr/bin/pamsignalpath needs to update to/usr/sbin/pamsignal.
Documentation
-
pamsignal(8)man page added. Newpamsignal.8.intemplate covers SYNOPSIS, OPTIONS (-f/--foreground,-c/--config PATH), SIGNALS (SIGHUP/SIGTERM/SIGINTsemantics), FILES (/etc/pamsignal/pamsignal.conf,/run/pamsignal/pamsignal.pid, vendor unit path), structured-journal output (ECS field reference + samplejournalctlqueries), EXIT STATUS, SECURITY (system user, memfd-backed curl--config,--proto =https,_EXEallowlist, per-IP cooldown), SEE ALSO, BUGS, AUTHOR. The.THversion is filled byconfigure_filefrommeson.project_version()so it tracks the release. Installed to<prefix>/share/man/man8/; debhelper auto-compresses on deb, brp-compress on rpm. The RPM%filesglob%{_mandir}/man8/pamsignal.8*accepts either compressed or uncompressed. -
docs/deployment.mduninstall recipe completed. Removes the man page (/usr/local/share/man/man8/pamsignal.8) soman pamsignaldoes not keep resolving after a devmeson installis unwound, and explicitly removes/etc/pamsignal/(auto-created by systemd'sConfigurationDirectory=pamsignalon first unit start regardless of--prefix, unused on a dev--prefix=/usr/localinstall but left behind by the prior recipe). Notes that/run/pamsignal/is auto-cleaned byRuntimeDirectory=. -
docs/deployment.mdUninstall section split by install path. The previous single recipe assumed source-build only, ignoring the published deb/rpm path that the README points users at. The section is now three subsections:apt removevsapt purgefor Debian/Ubuntu (with the conffile-preservation rationale),dnf removefor Fedora/RHEL family (with the.rpmsavebehavior on modified configs), and the existing manual recipe explicitly framed as the source-build branch. Both packaged paths note that thepamsignalsystem user is preserved by intent (avoids orphaning files owned by a recyclable UID) and shows afind / -user pamsignalcheck before any manualuserdel. -
docs/deployment.mdInstall section split by install path (mirrors the Uninstall split). Three subsections — apt, dnf, source build — replace the prior source-only recipe. Each packaged subsection ends "continue at Configure" because postinst/%prealready handle user creation and conf permissions; only the source-build subsection still walks throughuseradd/usermodand thechown root:pamsignal /…/pamsignal.conf && chmod 0640step. The "Configure" section now disambiguates the conf path (/etc/pamsignal/pamsignal.conffor packaged or--prefix=/usrsource,/usr/local/etc/…for default-prefix source) so readers don't edit the wrong file.
Security
- Closed the two clang-analyzer taint-source warnings the lint pass had been carrying.
src/config.ctrim()no longer callsisspace()— a locale-independentis_ws()classifier replaces it, eliminating theclang-analyzer-security.ArrayBoundfinding (tainted index reaching__ctype_b_loc()'s table) and making config parsing deterministic acrossLC_CTYPE.src/main.chas_journal_access()no longer doesmalloc(getgroups(0, NULL) * sizeof(gid_t))— it uses a 256-entry stack buffer withgetgroups(256, buf), droppingclang-analyzer-optin.taint.TaintedAllocand removing a heap allocation. A user with >256 supplementary groups (NGROUPS_MAX is 65536 in theory but real users have <32) getsEINVALand the daemon fails closed with the existing "add user to systemd-journal" error path.src/journal_watch.cps_fail_table_init()adds an explicitif (copy_count < 0) copy_count = 0;floor — the runtime invariant already held, but the floor lets the analyzer provefail_table_count ∈ [0, capacity]across reinit cycles, closing a transitiveArrayBoundfinding surfaced bytests/test_journal_watch.c. Newtests/test_config.c::test_config_load_whitespace_all_kindscovers\t/\r/\v/\fto lock inis_ws()'s coverage of the C-localeisspaceset.
0.2.4 — Server Context Configuration
Feature release adding server context configurations.
Features
- Server Context Tags: Added
providerandservice_nameconfiguration fields topamsignal.conf. When configured, these tags are automatically included in alert payloads to help administrators identify the environment generating the alert. - Text Alerts: Context tags are appended to the end of text-based alerts (e.g. Telegram, Slack, WhatsApp).
- JSON Webhooks: Context tags are injected natively into the root of the ECS JSON payload under the
labelsdictionary.
CI Fixes
- Release environment: Fixed
.github/workflows/release-packages.ymlto properly declareenvironment: releasein thebuild-rpmandpublish-repojobs, so they can correctly access the GPG signing secrets.
v0.2.3 — log spoof prevention + brute-force tracker fixes
Security and bugfix release.
Security
Log Spoofing Prevention: pamsignal now enforces a _EXE allowlist on every journal entry it processes. Unprivileged users on the host can no longer inject fake PAM events via logger(1) to trigger false alerts or wedge legitimate IPs into a brute-force lockout. Only entries whose _EXE resolves to sshd / sudo / su / login / systemd-logind under a system path prefix (/usr/, /bin/, /sbin/, /lib/, /lib64/, /opt/) are processed; entries with a missing _EXE are dropped.
This closes a real attack vector: any non-root user who could write to /dev/log (i.e. anyone with a shell) could previously send a synthetic Failed password for root from <victim-ip> line and pamsignal would treat it as authentic.
Fixes
- Brute-force cooldown bug: when the per-source-IP tracking table was full and an old entry got evicted to make room for a new IP, the new IP inherited the evicted IP's
last_brute_alert_usec, which could suppress its first brute-force alert. Now zeroed on the evict-and-reuse path. - State loss on SIGHUP reload: reloading the configuration via
SIGHUPpreviously zeroed the in-memory brute-force tracking table, so an attacker mid-attack could keep one server reload away from a fresh count. The table is now preserved across reloads (and copied/truncated when the configured capacity changes).
Install
The signed apt + dnf repositories at https://anhtuank7c.github.io/pamsignal/ auto-update on this release. Users on Debian/Ubuntu run sudo apt update && sudo apt upgrade pamsignal; Fedora / RHEL 9 / Alma 9 / Rocky 9 users run sudo dnf upgrade pamsignal. Signing key fingerprint unchanged: 2D2C 828F A6F4 D019 E446 8FBB B106 2235 2862 2F69.
See CHANGELOG.md for the full entry.
v0.2.2 — republish on github.io
Republish-only release. The .deb and .rpm binaries are bit-for-bit equivalent to v0.2.1 modulo the version stamp.
Why this release exists
The custom domain was removed from the gh-pages site. Cutting this tag retriggers the publish-repo workflow, which regenerates the apt + dnf repository metadata (dists/stable/InRelease, repomd.xml.asc, per-variant pamsignal.repo) and pushes it to gh-pages on the canonical github.io origin: https://anhtuank7c.github.io/pamsignal/.
If you previously configured the repository against the (now removed) custom domain, switch to the github.io URLs in the README:
Debian / Ubuntu
curl -fsSL https://anhtuank7c.github.io/pamsignal/key.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/pamsignal.gpg
echo "deb [signed-by=/usr/share/keyrings/pamsignal.gpg] https://anhtuank7c.github.io/pamsignal stable main" \
| sudo tee /etc/apt/sources.list.d/pamsignal.list
sudo apt update && sudo apt install pamsignalFedora
sudo dnf config-manager addrepo \
--from-repofile=https://anhtuank7c.github.io/pamsignal/rpm/fedora/pamsignal.repo
sudo dnf install pamsignalRHEL 9 / AlmaLinux 9 / Rocky Linux 9
sudo dnf config-manager --add-repo \
https://anhtuank7c.github.io/pamsignal/rpm/el9/pamsignal.repo
sudo dnf install pamsignalThe signing key fingerprint is unchanged: 2D2C 828F A6F4 D019 E446 8FBB B106 2235 2862 2F69. No need to re-import.
See CHANGELOG.md for the full entry.
pamsignal 0.2.1
pamsignal 0.2.1
Packaging-only release. Binary is identical to v0.2.0. Adds signed apt
and dnf repositories published to GitHub Pages, and a small pamsignal.spec
fix (Provides user/group) so dnf 5 on Fedora 44 installs the rpm cleanly.
Install via apt/dnf:
- Ubuntu/Debian: see https://anhtuank7c.github.io/pamsignal/
- Fedora: dnf config-manager addrepo --from-repofile=...rpm/fedora/pamsignal.repo
- RHEL 9 / Alma 9 / Rocky 9: ...rpm/el9/pamsignal.repo
Signing key fingerprint: 2D2C 828F A6F4 D019 E446 8FBB B106 2235 2862 2F69
pamsignal 0.2.0
pamsignal 0.2.0
Breaking change. Alert payloads move to Elastic Common Schema (ECS) for
both chat text and the JSON webhook. Anyone parsing v0.1.0's pipe-
delimited text or flat-JSON consumers will need to update.
Highlights:
- Chat text: severity-prefixed key=value (e.g.
[ALERT] auth.brute_force_detected src=… attempts=… pid=… ts=…). Fixed-width severity bracket so columns
align; new pid= field lets youkill <pid>straight from the alert. - JSON webhook: full ECS schema — @timestamp, nested event.{action,
category,kind,outcome,severity,module,dataset}, host.hostname,
user.name, service.name, source.{ip,port}, process.{pid,user.id}.
Custom fields under pamsignal.* per ECS guidance. - systemd-journal: ECS-aligned structured fields (EVENT_ACTION, USER_NAME,
SOURCE_IP, …) added alongside the existing PAMSIGNAL_* fields. Both
available through v0.2.x; PAMSIGNAL_* retires in v0.3.0. - ps_notify_brute_force gains a last_pid parameter; six new ECS helper
functions in utils.h covered by 36 utils tests (was 30). - docs/alerts.md rewritten with the new schema, a SIEM compatibility
table, and a Vector example showing the conventional pamsignal →
ingest layer → SIEM production architecture.
The webhook is drop-in for ECS-native SIEMs (Elastic, Wazuh) and one
Vector / Logstash config away from any other modern SIEM. ArcSight /
QRadar still need CEF or LEEF mapping at the ingest layer.
See CHANGELOG.md "0.2.0 — 2026-04-30" for the full notes.
pamsignal 0.1.0
pamsignal 0.1.0
First public release. Comprises three bodies of work:
-
Six-phase OWASP 2025 / data-integrity / memory-safety audit closing
every Critical, High, Medium, Low, and Info finding identified, plus
continuous-fuzzing infrastructure for the PAM message parser. -
Distribution packaging for Debian/Ubuntu (debian/ source layout) and
Fedora/CentOS/AlmaLinux/Rocky Linux (pamsignal.spec). Both create the
pamsignal system user, install the systemd unit at the vendor path,
and protect /etc/pamsignal/pamsignal.conf as root:pamsignal 0640. -
CI workflow (.github/workflows/release-packages.yml) that builds
.deb on Ubuntu and .rpm on Fedora when a release is published, with
apt and dnf caching keyed on build-deps. Outputs are uploaded as
workflow artifacts (90-day retention) and attached to the release.
Audit highlights:
- Alert dispatch: secrets via memfd-backed curl config (not argv);
absolute-path execv with clearenv() and minimal PATH; --proto =https
forced - Config validation: per-field token/URL allowlists; O_NOFOLLOW + fstat
permission/ownership checks - PID file & signals: openat-based pidfile with kill(pid,0) liveness
check; volatile sig_atomic_t flags; SA_RESTART; SIGPIPE ignored;
sigprocmask-blocked SIGHUP reload - Privilege defenses: PR_SET_NO_NEW_PRIVS; RLIMIT_NPROC=64
- Brute-force tracking: per-source-IP cooldown
- Build hardening: _FORTIFY_SOURCE=3, stack-clash, CET, separate-code
- Tests: 78 CMocka tests across four suites; opt-in libFuzzer harness
with ASan + UBSan instrumentation
See CHANGELOG.md for the full notes and per-finding citations
(SEC-01..12, MEM-01..13, DSG-01..06, BLD-01..04, TST-01..03, INF-03..04).