Skip to content

Commit 9dba40e

Browse files
committed
docs(parser): add conformance matrix
1 parent 29f05e2 commit 9dba40e

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

docs/parser-conformance-matrix.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Parser Conformance Matrix
2+
3+
This matrix documents the parser behavior that LogLens currently treats as
4+
reviewable contract surface. It is derived from `src/parser.cpp`,
5+
`src/event.hpp`, `tests/test_parser.cpp`, and the checked-in parser fixture
6+
corpus.
7+
8+
The parser contract is intentionally conservative:
9+
10+
- recognized evidence emits a normalized `Event`
11+
- unsupported evidence emits a parser warning and an unknown-pattern bucket
12+
- unsupported evidence does not become detector input
13+
14+
## Input Format Matrix
15+
16+
| Input format | Header shape | Timestamp behavior | Parser metadata | Primary fixture |
17+
| --- | --- | --- | --- | --- |
18+
| `syslog_legacy` | `Mar 10 09:00:01 example-host program[pid]: message` | Requires `--year` or `timestamp.assume_year`; the supplied year is injected into the parsed timestamp | `input_mode = syslog_legacy`, `assume_year = <year>`, `timezone_present = false` | [`assets/parser_fixture_matrix_syslog.log`](../assets/parser_fixture_matrix_syslog.log) |
19+
| `journalctl_short_full` | `Tue 2026-03-10 09:00:01 UTC example-host program[pid]: message` | Uses the embedded date and supported timezone token; supported tokens are `UTC`, `GMT`, `Z`, `+HHMM`, and `+HH:MM` style offsets | `input_mode = journalctl_short_full`, no assumed year, `timezone_present = true` | [`assets/parser_fixture_matrix_journalctl_short_full.log`](../assets/parser_fixture_matrix_journalctl_short_full.log) |
20+
21+
After the header is parsed, both input formats use the same authentication
22+
message classifier. A supported message should therefore normalize to the same
23+
event type in both formats.
24+
25+
## Supported Event Matrix
26+
27+
| Evidence family | Supported message shape | Input formats | Expected normalized event | Field notes |
28+
| --- | --- | --- | --- | --- |
29+
| SSH failed password | `Failed password for <user> from <ip> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_failed_password` | Extracts `username` and `source_ip`. |
30+
| SSH failed password for invalid or illegal user | `Failed password for invalid user <user> ...` or `Failed password for illegal user <user> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_invalid_user` | `illegal user` is treated as an OpenSSH wording variant of invalid user. |
31+
| SSH failed none for invalid or illegal user | `Failed none for invalid user <user> ...` or `Failed none for illegal user <user> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_invalid_user` | `Failed none` for a regular user remains unsupported. |
32+
| SSH direct invalid or illegal user | `Invalid user <user> from <ip>` or `Illegal user <user> from <ip>` | `syslog_legacy`, `journalctl_short_full` | `ssh_invalid_user` | Extracts `username` and `source_ip`. |
33+
| SSH `input_userauth_request` invalid or illegal user | `input_userauth_request: invalid user <user> [preauth]` or illegal-user variant | `syslog_legacy`, `journalctl_short_full` | `ssh_invalid_user` | Extracts `username`; source IP is absent in this message shape. |
34+
| SSH accepted password | `Accepted password for <user> from <ip> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_accepted_password` | Context event; not failure evidence by default. |
35+
| SSH accepted publickey | `Accepted publickey for <user> from <ip> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_accepted_publickey` | Context event; key material in fixtures is sanitized. |
36+
| SSH accepted keyboard-interactive/pam | `Accepted keyboard-interactive/pam for <user> from <ip> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_accepted_keyboard_interactive` | Context event; not failure evidence by default. |
37+
| SSH failed publickey | `Failed publickey for <user> from <ip> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_failed_publickey` | Invalid or illegal-user wording is accepted but still normalizes as publickey failure. |
38+
| SSH failed keyboard-interactive/pam | `Failed keyboard-interactive/pam for <user> from <ip> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_failed_keyboard_interactive` | Regular-user variant keeps the keyboard-interactive failure type. |
39+
| SSH failed keyboard-interactive/pam for invalid or illegal user | `Failed keyboard-interactive/pam for invalid user <user> ...` or illegal-user variant | `syslog_legacy`, `journalctl_short_full` | `ssh_invalid_user` | Invalid or illegal-user variant collapses to invalid-user evidence. |
40+
| SSH maximum authentication attempts | `maximum authentication attempts exceeded for <user> from <ip> ...` | `syslog_legacy`, `journalctl_short_full` | `ssh_max_auth_tries` | Optional leading `error:` is accepted. |
41+
| SSH maximum authentication attempts for invalid or illegal user | `maximum authentication attempts exceeded for invalid user <user> ...` or illegal-user variant | `syslog_legacy`, `journalctl_short_full` | `ssh_invalid_user` | Optional leading `error:` is accepted. |
42+
| `sshd`-owned PAM auth failure | `PAM: Authentication failure for <user> from <ip>` | `syslog_legacy`, `journalctl_short_full` | `pam_auth_failure` | Optional leading `error:` is accepted. |
43+
| `sshd`-owned PAM auth failure for invalid or illegal user | `PAM: Authentication failure for invalid user <user> from <ip>` or illegal-user variant | `syslog_legacy`, `journalctl_short_full` | `ssh_invalid_user` | Keeps invalid-user evidence separate from lower-confidence PAM failure evidence. |
44+
| `pam_unix(...:auth)` authentication failure | `authentication failure; ... rhost=<ip> ... user=<user>` | `syslog_legacy`, `journalctl_short_full` | `pam_auth_failure` | Missing `user=` or `rhost=` is allowed; fields remain empty. |
45+
| `pam_faillock(...:auth)` consecutive login failures | `Consecutive login failures for user <user> ... from <ip>` | `syslog_legacy`, `journalctl_short_full` | `pam_auth_failure` | Selected `pam_faillock` auth failure variant. |
46+
| `pam_faillock(...:auth)` authentication failure | `Authentication failure for user <user> from <ip>` | `syslog_legacy`, `journalctl_short_full` | `pam_auth_failure` | Selected `pam_faillock` auth failure variant. |
47+
| `pam_sss(...:auth)` authentication failure | `received for user <user>: ... (Authentication failure)` | `syslog_legacy`, `journalctl_short_full` | `pam_auth_failure` | Extracts `username`; source IP is absent in this message shape. |
48+
| `pam_unix(...:session)` session opened | `session opened for user <target> by <actor>(...)` | `syslog_legacy`, `journalctl_short_full` | `session_opened` | Normalized `username` is the actor after `by`. |
49+
| Sudo command audit | `<actor> : ... COMMAND=<command>` | `syslog_legacy`, `journalctl_short_full` | `sudo_command` | Counts as sudo burst evidence by default. |
50+
| Sudo password failure | `<actor> : 1 incorrect password attempt ...` | `syslog_legacy`, `journalctl_short_full` | `sudo_auth_failure` | Audit event; not counted as sudo burst evidence by default. |
51+
| Sudo policy denial | `<actor> : user NOT in sudoers ...` or `<actor> : command not allowed ...` | `syslog_legacy`, `journalctl_short_full` | `sudo_policy_denied` | Audit event; not counted as sudo burst evidence by default. |
52+
| `su` failure audit | `FAILED SU (to <target>) <actor> on <tty>` | `syslog_legacy`, `journalctl_short_full` | `su_auth_failure` | Normalized `username` is the actor. |
53+
| `su` success audit | `Successful su for <target> by <actor>` | `syslog_legacy`, `journalctl_short_full` | `session_opened` | Normalized `username` is the actor. |
54+
55+
## Unsupported Bucket Matrix
56+
57+
Unsupported buckets are warning labels, not normalized events. The expected
58+
normalized event is always `none`.
59+
60+
| Unsupported evidence | Input formats | Expected unsupported line bucket | Expected normalized event |
61+
| --- | --- | --- | --- |
62+
| `sshd` preauth connection closed or reset, including `Connection closed by ... [preauth]`, `Connection closed by authenticating user ... [preauth]`, and `Connection reset by ... [preauth]` | `syslog_legacy`, `journalctl_short_full` | `sshd_connection_closed_preauth` | none |
63+
| `sshd` timeout, disconnection, or disconnect notice, including `Timeout, client not responding`, `Disconnected from ...`, and `Received disconnect ...` | `syslog_legacy`, `journalctl_short_full` | `sshd_timeout_or_disconnection` | none |
64+
| `sshd` negotiation failure such as `Unable to negotiate with ...` | `syslog_legacy`, `journalctl_short_full` | `sshd_negotiation_failure` | none |
65+
| Other well-formed but unsupported `sshd` messages | `syslog_legacy`, `journalctl_short_full` | `sshd_other` | none |
66+
| `pam_unix(...:session)` session closed | `syslog_legacy`, `journalctl_short_full` | `pam_unix_session_closed` | none |
67+
| Other unsupported `pam_unix(...)` messages | `syslog_legacy`, `journalctl_short_full` | `pam_unix_other` | none |
68+
| `pam_faillock(...:auth)` account temporarily locked | `syslog_legacy`, `journalctl_short_full` | `pam_faillock_account_locked` | none |
69+
| `pam_faillock(...:auth)` successful authentication telemetry | `syslog_legacy`, `journalctl_short_full` | `pam_faillock_authsucc` | none |
70+
| Other unsupported `pam_faillock(...)` messages | `syslog_legacy`, `journalctl_short_full` | `pam_faillock_other` | none |
71+
| `pam_sss(...:auth)` user not known to underlying authentication module | `syslog_legacy`, `journalctl_short_full` | `pam_sss_unknown_user` | none |
72+
| `pam_sss(...:auth)` authentication service cannot retrieve authentication info | `syslog_legacy`, `journalctl_short_full` | `pam_sss_authinfo_unavail` | none |
73+
| Other unsupported `pam_sss(...)` messages | `syslog_legacy`, `journalctl_short_full` | `pam_sss_other` | none |
74+
| Well-formed `sudo` line that is not command, incorrect-password, or policy-denial evidence | `syslog_legacy`, `journalctl_short_full` | `sudo_other` | none |
75+
| Well-formed `su` line that is not recognized as success or failure audit evidence | `syslog_legacy`, `journalctl_short_full` | `su_other` | none |
76+
| Well-formed unsupported program tag | `syslog_legacy`, `journalctl_short_full` | `program_<sanitized_program>` | none |
77+
78+
## Header And Structural Warning Matrix
79+
80+
Structural failures do not reach the authentication message classifier. They
81+
still produce parser warnings and unknown-pattern buckets through the same
82+
coverage telemetry path.
83+
84+
| Failure class | Input formats | Expected bucket | Expected normalized event |
85+
| --- | --- | --- | --- |
86+
| Missing syslog assumed year | `syslog_legacy` | `syslog_legacy_mode_requires_assume_year` | none |
87+
| Missing syslog header fields | `syslog_legacy` | `missing_syslog_header_fields` | none |
88+
| Invalid syslog month token | `syslog_legacy` | `invalid_month_token` | none |
89+
| Invalid syslog day token | `syslog_legacy` | `invalid_day_token` | none |
90+
| Invalid time token | `syslog_legacy`, `journalctl_short_full` | `invalid_time_token` | none |
91+
| Invalid calendar date | `syslog_legacy`, `journalctl_short_full` | `invalid_calendar_date` | none |
92+
| Missing journalctl short-full header fields | `journalctl_short_full` | `missing_journalctl_short_full_header_fields` | none |
93+
| Invalid journalctl date token | `journalctl_short_full` | `invalid_journalctl_date_token` | none |
94+
| Invalid journalctl timezone token | `journalctl_short_full` | `invalid_timezone_token` | none |
95+
| Missing program/message delimiter | `syslog_legacy`, `journalctl_short_full` | `missing_program_message_delimiter` | none |
96+
97+
## Fixture Anchors
98+
99+
| Fixture | Current conformance expectation |
100+
| --- | --- |
101+
| [`assets/parser_fixture_matrix_syslog.log`](../assets/parser_fixture_matrix_syslog.log) | 32 analyzed lines, 23 parsed events, 9 parser warnings, unsupported buckets for SSH preauth close/reset, timeout/disconnection, negotiation failure, and `pam_unix` session-close telemetry |
102+
| [`assets/parser_fixture_matrix_journalctl_short_full.log`](../assets/parser_fixture_matrix_journalctl_short_full.log) | Same event and warning shape as the syslog matrix, with journalctl header parsing and timezone metadata |
103+
| [`assets/parser_auth_families_syslog.log`](../assets/parser_auth_families_syslog.log) | Selected `sshd`, `pam_unix`, `pam_faillock`, `pam_sss`, and session-opened auth-family support, plus five unsupported PAM-family telemetry buckets |
104+
| [`assets/parser_auth_families_journalctl_short_full.log`](../assets/parser_auth_families_journalctl_short_full.log) | Same auth-family event and warning shape as the syslog auth-family fixture, with journalctl timestamp parsing |
105+
| [`assets/noisy_auth_sample.log`](../assets/noisy_auth_sample.log) and [`tests/fixtures/parser_matrix/noisy_auth_expected.json`](../tests/fixtures/parser_matrix/noisy_auth_expected.json) | Noisy syslog coverage fixture with malformed lines, blank lines, unsupported auth-family evidence, irrelevant service lines, and locked parser quality counts |
106+
107+
## Review Rule
108+
109+
When this matrix changes, update the relevant parser tests and fixtures in the
110+
same pull request. A parser behavior change should be visible in at least one of
111+
these places:
112+
113+
- normalized event expectation in `tests/test_parser.cpp`
114+
- supported fixture line under `assets/`
115+
- unsupported warning bucket expectation
116+
- report-contract fixture if the visible report shape changes

0 commit comments

Comments
 (0)