From 7dac3d28e7d37a67953288cd776e79cca0f9d450 Mon Sep 17 00:00:00 2001 From: stacknil Date: Sat, 13 Jun 2026 11:26:04 +0800 Subject: [PATCH 1/2] test(parser): bucket pam faillock account locks --- README.md | 6 +++--- docs/parser-contract.md | 6 +++--- src/parser.cpp | 4 ++++ tests/test_parser.cpp | 21 +++++++++++---------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 09d11b1..c3c2d2f 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,9 @@ LogLens also tracks parser coverage telemetry for unsupported or malformed lines - `top_unknown_patterns` Common unsupported-pattern buckets include `sshd_connection_closed_preauth`, -`sshd_timeout_or_disconnection`, `sshd_negotiation_failure`, and -`pam_unix_session_closed`. These buckets keep non-finding evidence reviewable -without counting it as detector evidence. +`sshd_timeout_or_disconnection`, `sshd_negotiation_failure`, +`pam_faillock_account_locked`, and `pam_unix_session_closed`. These buckets keep +non-finding evidence reviewable without counting it as detector evidence. For the parser behavior contract, supported modes, and fixture map, see [`docs/parser-contract.md`](./docs/parser-contract.md). diff --git a/docs/parser-contract.md b/docs/parser-contract.md index b2f550b..b97dc2d 100644 --- a/docs/parser-contract.md +++ b/docs/parser-contract.md @@ -43,9 +43,9 @@ This is the main trust boundary: unsupported input should remain inspectable, ev Stable unsupported-pattern buckets currently exercised by the fixture corpus include `sshd_connection_closed_preauth`, `sshd_timeout_or_disconnection`, -`sshd_negotiation_failure`, and `pam_unix_session_closed`. They are parser -telemetry and warnings only; detector signal mappings decide which parsed events -can contribute to findings. +`sshd_negotiation_failure`, `pam_faillock_account_locked`, and +`pam_unix_session_closed`. They are parser telemetry and warnings only; detector +signal mappings decide which parsed events can contribute to findings. ## Detection signal boundary diff --git a/src/parser.cpp b/src/parser.cpp index 1889ca7..0eaeb95 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -700,6 +700,10 @@ bool parse_pam_faillock_message(std::string_view message, Event& event) { } std::string classify_unknown_pam_faillock_pattern(std::string_view message) { + if (message.starts_with("Account temporarily locked for user ")) { + return "pam_faillock_account_locked"; + } + if (message.starts_with("User ") && message.find("successfully authenticated") != std::string_view::npos) { return "pam_faillock_authsucc"; } diff --git a/tests/test_parser.cpp b/tests/test_parser.cpp index 4c617b1..c874d3d 100644 --- a/tests/test_parser.cpp +++ b/tests/test_parser.cpp @@ -605,12 +605,12 @@ void test_syslog_auth_family_fixture_file() { expect(result.events[11].username == "erin", "expected pam_unix session-opened username"); expect(result.quality.top_unknown_patterns.size() == 5, "expected five syslog auth-family buckets"); - expect(result.quality.top_unknown_patterns[0].pattern == "pam_faillock_authsucc", + expect(result.quality.top_unknown_patterns[0].pattern == "pam_faillock_account_locked", + "expected pam_faillock account-locked telemetry bucket"); + expect(result.quality.top_unknown_patterns[0].count == 1, "expected one pam_faillock account-locked line"); + expect(result.quality.top_unknown_patterns[1].pattern == "pam_faillock_authsucc", "expected pam_faillock authsucc telemetry bucket"); - expect(result.quality.top_unknown_patterns[0].count == 1, "expected one pam_faillock authsucc line"); - expect(result.quality.top_unknown_patterns[1].pattern == "pam_faillock_other", - "expected pam_faillock other telemetry bucket"); - expect(result.quality.top_unknown_patterns[1].count == 1, "expected one pam_faillock other line"); + expect(result.quality.top_unknown_patterns[1].count == 1, "expected one pam_faillock authsucc line"); expect(result.quality.top_unknown_patterns[2].pattern == "pam_sss_authinfo_unavail", "expected pam_sss authinfo-unavail telemetry bucket"); expect(result.quality.top_unknown_patterns[2].count == 1, "expected one pam_sss authinfo-unavail line"); @@ -671,12 +671,13 @@ void test_journalctl_auth_family_fixture_file() { "expected journalctl pam_unix session-opened auth-family event"); expect(result.quality.top_unknown_patterns.size() == 5, "expected five journalctl auth-family buckets"); - expect(result.quality.top_unknown_patterns[0].pattern == "pam_faillock_authsucc", + expect(result.quality.top_unknown_patterns[0].pattern == "pam_faillock_account_locked", + "expected journalctl pam_faillock account-locked telemetry bucket"); + expect(result.quality.top_unknown_patterns[0].count == 1, + "expected one journalctl pam_faillock account-locked line"); + expect(result.quality.top_unknown_patterns[1].pattern == "pam_faillock_authsucc", "expected journalctl pam_faillock authsucc telemetry bucket"); - expect(result.quality.top_unknown_patterns[0].count == 1, "expected one journalctl pam_faillock authsucc line"); - expect(result.quality.top_unknown_patterns[1].pattern == "pam_faillock_other", - "expected journalctl pam_faillock other telemetry bucket"); - expect(result.quality.top_unknown_patterns[1].count == 1, "expected one journalctl pam_faillock other line"); + expect(result.quality.top_unknown_patterns[1].count == 1, "expected one journalctl pam_faillock authsucc line"); expect(result.quality.top_unknown_patterns[2].pattern == "pam_sss_authinfo_unavail", "expected journalctl pam_sss authinfo-unavail telemetry bucket"); expect(result.quality.top_unknown_patterns[2].count == 1, "expected one journalctl pam_sss authinfo-unavail line"); From 8b62dc730ea5bcf2108c1534855293bcb5526e37 Mon Sep 17 00:00:00 2001 From: stacknil Date: Sat, 13 Jun 2026 11:28:23 +0800 Subject: [PATCH 2/2] test(cli): print assertion failures --- tests/test_cli.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_cli.cpp b/tests/test_cli.cpp index cb2b73f..1fbe21c 100644 --- a/tests/test_cli.cpp +++ b/tests/test_cli.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -8,6 +9,7 @@ namespace { void expect(bool condition, const std::string& message) { if (!condition) { + std::cerr << "test_cli assertion failed: " << message << '\n'; throw std::runtime_error(message); } }