0.3.3 — Sandbox tightening, threat model, sanitizer CI
Hardening release. Three independent improvements: the systemd unit's exposure score drops from 22 to 13 ("OK" band, near the achievable floor for a daemon with the daemon's network and journal-access constraints) via nine new directives; a canonical docs/threat-model.md documents what pamsignal defends against and what it deliberately doesn't (cross-referenced to source-line mitigations); ASAN + UBSAN run on every push and PR via a new .github/workflows/ci.yml workflow. No source-code behavior change visible to operators — the daemon's API surface (config keys, journal fields, webhook payload) is unchanged. Upgrade is apt upgrade pamsignal / dnf upgrade pamsignal; the systemd unit is replaced atomically and daemon-reload is auto-fired by the maintainer scripts.
Security
- systemd unit hardening: exposure score 22 → 13 (displayed 2.2 → 1.3, "OK" band, near the achievable floor for a daemon that needs network egress + journal access). Nine new directives added to
pamsignal.service.in, each justified against the daemon's actual behavior (no defensive copy-paste):UMask=0077,ProtectClock=yes,ProtectHostname=yes,ProtectProc=invisible,ProcSubset=pid,SystemCallArchitectures=native,RemoveIPC=yes,RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6(allowlists exactly the four families the daemon and its curl child need — drops AF_PACKET, AF_BLUETOOTH, AF_VSOCK, etc.), andDevicePolicy=closed(overrides the implicit RTC-read grant fromPrivateDevices=yes). The CI regression-gate threshold drops from 30 to 20 to lock in the new baseline. Directives deliberately not added because they would break the daemon:PrivateNetwork=yes(curl needs outgoing HTTPS),PrivateUsers=yes(would interact badly withSupplementaryGroups=systemd-journalfor journal-read access),IPAddressDeny=any(Telegram/Slack/Discord/etc. backend IPs change too frequently to allowlist),RootDirectory=/RootImage=(would require portable-service repackaging, out of scope). Documented in the directive comments so future readers know what the trade-offs were.
CI
- AddressSanitizer + UndefinedBehaviorSanitizer on every push. New
.github/workflows/ci.ymlrunsmeson setup -Db_sanitize=address,undefined -Db_lundef=false --buildtype=debugoptimized, builds, and runs the full CMocka suite under both sanitizers on every push tomainand every PR targeting it. ASAN catches use-after-free, heap/stack OOB, double-free, and leaks; UBSAN catches signed-integer overflow, misaligned pointer access, null deref, narrowing conversions, etc. — the dynamic-runtime evidence behind the threat-model claim that memory-safety bugs insrc/are an in-scope defense (attack #8). Initial run found zero issues on the existing test surface. Stack traces in failure reports include symbolized backtraces (print_stacktrace=1);halt_on_error=1+abort_on_error=1ensure the first finding stops the run rather than continuing in an undefined state. Concurrency group set to cancel older runs when newer commits land on the same ref.
Documentation
-
docs/threat-model.mddocuments what pamsignal defends against, what it deliberately does not, and the design rationale behind the split. Sections cover: assets in priority order (alert integrity, alert credentials, thepamsignaluser's privilege envelope, journal entries pamsignal writes), adversary classes with explicit capabilities (external remote, local unprivileged, in-pamsignal-group, compromised daemon, network attacker on alert path), nine in-scope attacks each cross-referenced to the source-line of its mitigation (_EXEallowlist, brute-force tracker semantics, memfd credential isolation,clearenv()+ absolute-pathexecv, TLS-only--proto =https,sanitize_string+json_escape, the systemd hardening directives, compiler hardening + libFuzzer, per-IP cooldown), ten explicit out-of-scope non-goals (root-on-host, in-pamsignal-group, compromised journald/libsystemd/curl, compromised alert provider, durable delivery, multi-host correlation, authenticated alert delivery, admin misconfiguration, input-flood DoS), the trust-boundary table, and the deliberate design limitations.SECURITY.md's scope section now references the threat model rather than duplicating the breakdown; thepamsignal(8)man page'sSEE ALSOpoints readers there before reporting suspected vulnerabilities; the README's documentation index links it. Designed as the canonical reference for "should this contribution land?" — a feature that strengthens an in-scope mitigation is welcome; a feature that pulls work into the daemon from an out-of-scope area gets pointed at this document.