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