Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion docs/case-study-linux-auth-bruteforce.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
85 changes: 85 additions & 0 deletions docs/incident-style-case.md
Original file line number Diff line number Diff line change
@@ -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
```
2 changes: 2 additions & 0 deletions docs/reviewer-path.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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)
Expand Down
Loading