From 29ce9e454766bf45b90f2c1e99d7c849186c2a98 Mon Sep 17 00:00:00 2001 From: stacknil Date: Tue, 30 Jun 2026 11:18:14 +0800 Subject: [PATCH] docs(case): add one-page evidence trace --- CHANGELOG.md | 2 + README.md | 4 +- docs/case-study-linux-auth-bruteforce.md | 2 +- docs/incident-style-case.md | 85 ++++++++++++++++++++++++ docs/reviewer-path.md | 2 + 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 docs/incident-style-case.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af538b..513e38b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ All notable user-visible changes should be recorded here. ### Docs +- Added a one-page incident-style case that traces raw SSH evidence through + normalized events and finding fields to a bounded conclusion. - Added a rule-by-rule false-positive taxonomy for NAT, bastion, internal scanner, lab replay, scheduled admin task, and shared-account contexts. - Expanded the parser conformance matrix with explicit Ubuntu / Debian diff --git a/README.md b/README.md index 16ec1b3..be48129 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ A compact finding summary is a bounded triage signal, not attribution: LogLens is an MVP / early release. The repository is stable enough for public review, local experimentation, and extension, but the parser and detection coverage are intentionally narrow. -Reviewing the project quickly? Start with [`docs/reviewer-path.md`](./docs/reviewer-path.md), [`docs/reviewer-brief.md`](./docs/reviewer-brief.md), and the [`quality gates map`](./docs/quality-gates.md). For detection reasoning, read the forensic-style [`Linux auth brute-force case study`](./docs/case-study-linux-auth-bruteforce.md), the [`rule catalog`](./docs/rule-catalog.md), and the [`false-positive taxonomy`](./docs/false-positive-taxonomy.md). For local scale expectations, see the [`performance envelope`](./docs/performance-envelope.md). +Reviewing the project quickly? Start with [`docs/reviewer-path.md`](./docs/reviewer-path.md), [`docs/reviewer-brief.md`](./docs/reviewer-brief.md), and the [`quality gates map`](./docs/quality-gates.md). For detection reasoning, follow the [`one-page incident-style case`](./docs/incident-style-case.md), then use the full [`Linux auth brute-force case study`](./docs/case-study-linux-auth-bruteforce.md), [`rule catalog`](./docs/rule-catalog.md), and [`false-positive taxonomy`](./docs/false-positive-taxonomy.md) for depth. For local scale expectations, see the [`performance envelope`](./docs/performance-envelope.md). ## Why This Project Exists @@ -113,7 +113,7 @@ classes: `unknown_timestamp`, `unknown_program`, `known_program_unknown_message`, `malformed_source_ip`, and `unsupported_pam_variant`. -For rule-by-rule semantics and signal boundaries, see [`docs/rule-catalog.md`](./docs/rule-catalog.md). For benign-context hypotheses and the evidence needed to support them, see [`docs/false-positive-taxonomy.md`](./docs/false-positive-taxonomy.md). For a forensic-style evidence walkthrough, see [`docs/case-study-linux-auth-bruteforce.md`](./docs/case-study-linux-auth-bruteforce.md). For the parser behavior contract, supported modes, and fixture map, see [`docs/parser-contract.md`](./docs/parser-contract.md). For the deliberately noisy parser-coverage sample, see [`docs/parser-coverage-notes.md`](./docs/parser-coverage-notes.md). +For a compact raw-log-to-conclusion trace, see [`docs/incident-style-case.md`](./docs/incident-style-case.md). For rule-by-rule semantics and signal boundaries, see [`docs/rule-catalog.md`](./docs/rule-catalog.md). For benign-context hypotheses and the evidence needed to support them, see [`docs/false-positive-taxonomy.md`](./docs/false-positive-taxonomy.md). For a full forensic-style evidence walkthrough, see [`docs/case-study-linux-auth-bruteforce.md`](./docs/case-study-linux-auth-bruteforce.md). For the parser behavior contract, supported modes, and fixture map, see [`docs/parser-contract.md`](./docs/parser-contract.md). For the deliberately noisy parser-coverage sample, see [`docs/parser-coverage-notes.md`](./docs/parser-coverage-notes.md). LogLens does not currently detect: diff --git a/docs/case-study-linux-auth-bruteforce.md b/docs/case-study-linux-auth-bruteforce.md index 356624b..4717257 100644 --- a/docs/case-study-linux-auth-bruteforce.md +++ b/docs/case-study-linux-auth-bruteforce.md @@ -1,6 +1,6 @@ # Linux Auth Brute-Force Case Study -This case study explains how LogLens interprets a sanitized Linux authentication sample as evidence. The emphasis is forensic reasoning: what the records support, what they do not support, and where parser uncertainty remains visible. +This case study explains how LogLens interprets a sanitized Linux authentication sample as evidence. The emphasis is forensic reasoning: what the records support, what they do not support, and where parser uncertainty remains visible. Reviewers who want the shortest end-to-end trace can start with the [`one-page incident-style case`](./incident-style-case.md). ## Scenario diff --git a/docs/incident-style-case.md b/docs/incident-style-case.md new file mode 100644 index 0000000..388227a --- /dev/null +++ b/docs/incident-style-case.md @@ -0,0 +1,85 @@ +# One-Page Incident-Style Case: Concentrated SSH Failures + +This compact evidence note follows one LogLens finding from raw log lines to a bounded conclusion. It uses the sanitized sample and its golden report fixture; it is incident-style in structure, not an incident verdict. + +## Case Scope + +| Field | Value | +| --- | --- | +| Evidence source | Linux `auth.log` / `secure` style syslog | +| Host | `example-host` | +| Review window | 2026-03-10 08:11:22 to 08:18:05 | +| Primary question | Did one source produce enough terminal SSH failures to meet the configured `brute_force` rule? | +| Parser mode | `syslog_legacy`, assumed year 2026 | + +## 1. Raw Log + +The selected evidence is five records from [`assets/sample_auth.log`](../assets/sample_auth.log): + +```text +Mar 10 08:11:22 example-host sshd[1234]: Failed password for invalid user admin from 203.0.113.10 port 51022 ssh2 +Mar 10 08:12:05 example-host sshd[1235]: Failed password for root from 203.0.113.10 port 51030 ssh2 +Mar 10 08:13:10 example-host sshd[1236]: Failed password for test from 203.0.113.10 port 51040 ssh2 +Mar 10 08:14:44 example-host sshd[1237]: Failed password for guest from 203.0.113.10 port 51050 ssh2 +Mar 10 08:18:05 example-host sshd[1238]: Failed password for invalid user deploy from 203.0.113.10 port 51060 ssh2 +``` + +Observed facts: the records name one source address, five attempted usernames, and five failed SSH authentications over 6 minutes 43 seconds. + +## 2. Normalized Events + +LogLens converts the selected lines into typed events before rule evaluation: + +| Evidence ID | Timestamp | Event type | Source IP | Username | Default signal role | +| --- | --- | --- | --- | --- | --- | +| `line:1` | 08:11:22 | `ssh_invalid_user` | `203.0.113.10` | `admin` | attempt evidence; terminal auth failure | +| `line:2` | 08:12:05 | `ssh_failed_password` | `203.0.113.10` | `root` | attempt evidence; terminal auth failure | +| `line:3` | 08:13:10 | `ssh_failed_password` | `203.0.113.10` | `test` | attempt evidence; terminal auth failure | +| `line:4` | 08:14:44 | `ssh_failed_password` | `203.0.113.10` | `guest` | attempt evidence; terminal auth failure | +| `line:5` | 08:18:05 | `ssh_invalid_user` | `203.0.113.10` | `deploy` | attempt evidence; terminal auth failure | + +All five events carry the source-IP grouping key and the default `counts_as_terminal_auth_failure` signal required by `brute_force`. Unsupported raw lines do not receive that signal and cannot enter this count. + +## 3. Finding + +The detector selects the five-event window and emits this explainable finding in `report.json`: + +```json +{ + "rule_id": "brute_force", + "subject_kind": "source_ip", + "subject": "203.0.113.10", + "grouping_key": "source_ip", + "threshold": 5, + "observed_count": 5, + "window_start": "2026-03-10 08:11:22", + "window_end": "2026-03-10 08:18:05", + "evidence_event_ids": ["line:1", "line:2", "line:3", "line:4", "line:5"], + "verdict_boundary": "triage_signal_not_compromise_or_attribution" +} +``` + +Rule result: `observed_count` equals the configured threshold, so the evidence supports a `brute_force` triage finding for `203.0.113.10`. The same five events also support a separate `multi_user_probing` finding; this page follows only the primary brute-force evidence chain. + +## 4. Bounded Conclusion + +**Supported by the selected evidence:** one source address produced five normalized terminal SSH failures inside the configured 10-minute window. The threshold match is deterministic and traceable to five event IDs. + +**Not established by the selected evidence:** that the source was malicious, that any credential succeeded, that `example-host` was compromised, or that an individual or organization can be attributed. The accepted login later in the sample comes from `203.0.113.20`, not the finding subject. Nearby sudo activity is not causally joined to this SSH cluster. + +**Context required before disposition:** determine whether the source represents NAT, a bastion, an authorized internal scanner, a lab replay, a scheduled task with stale credentials, or shared-account use. The rule-specific corroboration questions are listed in [`false-positive-taxonomy.md`](./false-positive-taxonomy.md#brute-force). + +**Parser boundary:** the full 16-line sample contains 14 parsed lines and 2 explicit parser warnings. Neither warning is part of `evidence_event_ids`. The warnings limit claims about evidence completeness, but they do not silently increase the finding count. + +## Evidence Provenance + +- Raw evidence: [`assets/sample_auth.log`](../assets/sample_auth.log) +- Golden finding: [`tests/fixtures/report_contracts/syslog_legacy/report.json`](../tests/fixtures/report_contracts/syslog_legacy/report.json) +- Full forensic explanation: [`case-study-linux-auth-bruteforce.md`](./case-study-linux-auth-bruteforce.md) +- Rule contract: [`rule-catalog.md`](./rule-catalog.md#brute-force) + +Reproduce the report with: + +```bash +./build/loglens --mode syslog --year 2026 ./assets/sample_auth.log ./out-incident-case +``` diff --git a/docs/reviewer-path.md b/docs/reviewer-path.md index c8ad2ea..be4989e 100644 --- a/docs/reviewer-path.md +++ b/docs/reviewer-path.md @@ -10,6 +10,7 @@ This path is for reviewers who want to understand LogLens quickly without readin | What log formats are supported? | [`docs/parser-contract.md`](./parser-contract.md) | Can name `syslog_legacy` and `journalctl_short_full` behavior | | What artifacts does it produce? | [`docs/report-artifacts.md`](./report-artifacts.md) and report-contract fixtures | Can inspect Markdown, JSON, and optional CSV outputs | | How do rules use evidence? | [`docs/rule-catalog.md`](./rule-catalog.md) | Can explain grouping keys, windows, thresholds, and unsupported-evidence boundaries | +| Can I trace one finding end to end? | [`docs/incident-style-case.md`](./incident-style-case.md) | Can follow raw lines through normalization and rule fields to a bounded conclusion | | What benign context can match a rule? | [`docs/false-positive-taxonomy.md`](./false-positive-taxonomy.md) | Can distinguish rule-true evidence from compromise, intent, attribution, or authorization claims | | Can the parser behavior be trusted? | Parser contract, fixture matrix, and [`assets/mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json) | Can see known, unknown, and malformed line handling | | What proves the main claims? | [`docs/quality-gates.md`](./quality-gates.md) | Can map claims to tests, fixtures, docs, and repeatable commands | @@ -46,6 +47,7 @@ Inspect: - [`docs/parser-contract.md`](./parser-contract.md) - [`assets/mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json) - [`docs/quality-gates.md`](./quality-gates.md) +- [`docs/incident-style-case.md`](./incident-style-case.md) - [`docs/rule-catalog.md`](./rule-catalog.md) - [`docs/false-positive-taxonomy.md`](./false-positive-taxonomy.md) - [`docs/case-study-linux-auth-bruteforce.md`](./case-study-linux-auth-bruteforce.md)