Skip to content

Releases: anhtuank7c/pamsignal

0.3.0 — Sudo/su brute-force detection

Choose a tag to compare

@anhtuank7c anhtuank7c released this 03 May 11:48

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/pamsignal instead 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's ExecStart is updated in lockstep, so apt upgrade / dnf upgrade is the only action a regular user needs. Anyone scripting against the absolute /usr/bin/pamsignal path (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-aligned EVENT_*/USER_*/SOURCE_*/HOST_*/SERVICE_*/PROCESS_* fields and a parallel PAMSIGNAL_* dictionary; only the ECS set survives. Saved journalctl queries that filter by PAMSIGNAL_EVENT=LOGIN_FAILED etc. need to switch to EVENT_ACTION=login_failure. The full mapping (PAMSIGNAL_USERNAMEUSER_NAME, PAMSIGNAL_SOURCE_IPSOURCE_IP, PAMSIGNAL_PORTSOURCE_PORT, PAMSIGNAL_SERVICESERVICE_NAME, PAMSIGNAL_HOSTNAMEHOST_HOSTNAME, PAMSIGNAL_PIDPROCESS_PID, PAMSIGNAL_TARGET_USERUSER_TARGET_NAME) is in docs/architecture.md and the refactor!: commit body. The JSON pamsignal.event_type / pamsignal.attempts / pamsignal.window_sec webhook fields are unchanged — those are vendor-specific by intent and not part of this retirement.

Features

  • Sudo / su brute-force detection. ps_parse_message now recognizes pam_unix(<svc>:auth): authentication failure; and extracts ruser= (actor), user= (elevation target), and rhost= (remote endpoint when present). The brute-force tracker keys by source IP when rhost= 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 new ps_notify_local_brute_force emits 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"}, ...} (no source.*). Per-event chat alerts are suppressed for sudo/su LOGIN_FAILED to avoid one-ping-per-mistyped-password noise; the journal entry is still written for every individual failure so journalctl history is complete, and only the threshold-breach alert fires. The brute-force tracker entry struct gains key_type (IP vs LOCAL_USER), service, and target_username fields; the existing ip field is renamed to key (64-byte buffer fits both INET6_ADDRSTRLEN and 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.in is now a template; configure_file substitutes @sbindir@ at configure time and installs to the dir reported by pkg-config --variable=systemdsystemunitdir systemd (with a <prefix>/lib/systemd/system fallback). Both debian/rules and pamsignal.spec drop the post-install mv (out of /etc/systemd/system) and sed (/usr/local/bin/usr/bin) workarounds — the artifact is correct straight out of meson install.
  • RPM %pre creates the group explicitly before the user: groupadd -r pamsignal runs before useradd -r -g pamsignal … so Provides: group(pamsignal) is honored even on hosts where USERGROUPS_ENAB is unset and useradd would otherwise skip auto-creating the matching group.
  • PS_DEFAULT_CONFIG_PATH is now derived from sysconfdir at configure time: a new include/paths.h.in template is generated into the build dir via configure_file, substituting @sysconfdir@ from get_option('prefix') / get_option('sysconfdir'). Packaged builds (--sysconfdir=/etc) still embed /etc/pamsignal/pamsignal.conf; a dev meson install with the default --prefix=/usr/local now correctly embeds /usr/local/etc/pamsignal/pamsignal.conf so the daemon finds its conf without an explicit --config flag.
  • Daemon binary moved from bindir to sbindir (FHS §4.10: system administration daemons belong in sbin, not bin). 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's ExecStart is updated automatically by the configure_file substitution. The pamsignal command name is unchanged — /usr/sbin is in root's PATH on every supported distro, and ordinary users invoke the daemon only via systemctl. RPM %files switches to %{_sbindir}/pamsignal. Upgrade behavior: on the deb/rpm transition, the package manager removes the old /usr/bin/pamsignal file and installs the new one at /usr/sbin/pamsignal; systemctl daemon-reload is auto-fired and the unit's new ExecStart points at the new path. Anyone scripting against the absolute /usr/bin/pamsignal path needs to update to /usr/sbin/pamsignal.

Documentation

  • pamsignal(8) man page added. New pamsignal.8.in template covers SYNOPSIS, OPTIONS (-f/--foreground, -c/--config PATH), SIGNALS (SIGHUP/SIGTERM/SIGINT semantics), FILES (/etc/pamsignal/pamsignal.conf, /run/pamsignal/pamsignal.pid, vendor unit path), structured-journal output (ECS field reference + sample journalctl queries), EXIT STATUS, SECURITY (system user, memfd-backed curl --config, --proto =https, _EXE allowlist, per-IP cooldown), SEE ALSO, BUGS, AUTHOR. The .TH version is filled by configure_file from meson.project_version() so it tracks the release. Installed to <prefix>/share/man/man8/; debhelper auto-compresses on deb, brp-compress on rpm. The RPM %files glob %{_mandir}/man8/pamsignal.8* accepts either compressed or uncompressed.
  • docs/deployment.md uninstall recipe completed. Removes the man page (/usr/local/share/man/man8/pamsignal.8) so man pamsignal does not keep resolving after a dev meson install is unwound, and explicitly removes /etc/pamsignal/ (auto-created by systemd's ConfigurationDirectory=pamsignal on first unit start regardless of --prefix, unused on a dev --prefix=/usr/local install but left behind by the prior recipe). Notes that /run/pamsignal/ is auto-cleaned by RuntimeDirectory=.
  • docs/deployment.md Uninstall 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 remove vs apt purge for Debian/Ubuntu (with the conffile-preservation rationale), dnf remove for Fedora/RHEL family (with the .rpmsave behavior on modified configs), and the existing manual recipe explicitly framed as the source-build branch. Both packaged paths note that the pamsignal system user is preserved by intent (avoids orphaning files owned by a recyclable UID) and shows a find / -user pamsignal check before any manual userdel.
  • docs/deployment.md Install 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/%pre already handle user creation and conf permissions; only the source-build subsection still walks through useradd/usermod and the chown root:pamsignal /…/pamsignal.conf && chmod 0640 step. The "Configure" section now disambiguates the conf path (/etc/pamsignal/pamsignal.conf for packaged or --prefix=/usr source, /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.c trim() no longer calls isspace() — a locale-independent is_ws() classifier replaces it, eliminating the clang-analyzer-security.ArrayBound finding (tainted index reaching __ctype_b_loc()'s table) and making config parsing deterministic across LC_CTYPE. src/main.c has_journal_access() no longer does malloc(getgroups(0, NULL) * sizeof(gid_t)) — it uses a 256-entry stack buffer with getgroups(256, buf), dropping clang-analyzer-optin.taint.TaintedAlloc and removing a heap allocation. A user with >256 supplementary groups (NGROUPS_MAX is 65536 in theory but real users have <32) gets EINVAL and the daemon fails closed with the existing "add user to systemd-journal" error path. src/journal_watch.c ps_fail_table_init() adds an explicit if (copy_count < 0) copy_count = 0; floor — the runtime invariant already held, but the floor lets the analyzer prove fail_table_count ∈ [0, capacity] across reinit cycles, closing a transitive ArrayBound finding surfaced by tests/test_journal_watch.c. New tests/test_config.c::test_config_load_whitespace_all_kinds covers \t/\r/\v/\f to lock in is_ws()'s coverage of the C-locale isspace set.

0.2.4 — Server Context Configuration

Choose a tag to compare

@anhtuank7c anhtuank7c released this 03 May 07:33

Feature release adding server context configurations.

Features

  • Server Context Tags: Added provider and service_name configuration fields to pamsignal.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 labels dictionary.

CI Fixes

  • Release environment: Fixed .github/workflows/release-packages.yml to properly declare environment: release in the build-rpm and publish-repo jobs, so they can correctly access the GPG signing secrets.

v0.2.3 — log spoof prevention + brute-force tracker fixes

Choose a tag to compare

@anhtuank7c anhtuank7c released this 02 May 11:58

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 SIGHUP previously 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

Choose a tag to compare

@anhtuank7c anhtuank7c released this 02 May 10:20

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 pamsignal

Fedora

sudo dnf config-manager addrepo \
  --from-repofile=https://anhtuank7c.github.io/pamsignal/rpm/fedora/pamsignal.repo
sudo dnf install pamsignal

RHEL 9 / AlmaLinux 9 / Rocky Linux 9

sudo dnf config-manager --add-repo \
  https://anhtuank7c.github.io/pamsignal/rpm/el9/pamsignal.repo
sudo dnf install pamsignal

The 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

Choose a tag to compare

@anhtuank7c anhtuank7c released this 29 Apr 18:39

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:

Signing key fingerprint: 2D2C 828F A6F4 D019 E446 8FBB B106 2235 2862 2F69

pamsignal 0.2.0

Choose a tag to compare

@anhtuank7c anhtuank7c released this 29 Apr 17:19

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 you kill <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

Choose a tag to compare

@anhtuank7c anhtuank7c released this 29 Apr 16:28

pamsignal 0.1.0

First public release. Comprises three bodies of work:

  1. 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.

  2. 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.

  3. 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).