{"id":51015081,"url":"https://github.com/securityronin/journald-forensic","last_synced_at":"2026-06-21T09:02:44.801Z","repository":{"id":365047571,"uuid":"1263698093","full_name":"SecurityRonin/journald-forensic","owner":"SecurityRonin","description":"From-scratch systemd journal (.journal) forensic reader — parse entries without journalctl/systemd, carve from unallocated space, and flag tampering (sequence gaps, timestamp regressions, truncation, online-state)","archived":false,"fork":false,"pushed_at":"2026-06-15T16:33:33.000Z","size":48,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-15T17:23:09.926Z","etag":null,"topics":["anti-forensics","dfir","forensics","incident-response","journald","linux","log-analysis","log-forensics","rust","rust-forensics","security","systemd"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SecurityRonin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-09T07:27:42.000Z","updated_at":"2026-06-15T16:33:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SecurityRonin/journald-forensic","commit_stats":null,"previous_names":["securityronin/journald-forensic"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/SecurityRonin/journald-forensic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fjournald-forensic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fjournald-forensic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fjournald-forensic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fjournald-forensic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SecurityRonin","download_url":"https://codeload.github.com/SecurityRonin/journald-forensic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fjournald-forensic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34603647,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anti-forensics","dfir","forensics","incident-response","journald","linux","log-analysis","log-forensics","rust","rust-forensics","security","systemd"],"created_at":"2026-06-21T09:02:39.556Z","updated_at":"2026-06-21T09:02:44.780Z","avatar_url":"https://github.com/SecurityRonin.png","language":"Rust","funding_links":["https://github.com/sponsors/h4x0r"],"categories":[],"sub_categories":[],"readme":"# journald-forensic\n\n[![journald-core](https://img.shields.io/crates/v/journald-core.svg?label=journald-core)](https://crates.io/crates/journald-core)\n[![Docs.rs](https://img.shields.io/docsrs/journald-core)](https://docs.rs/journald-core)\n[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)\n[![Sponsor](https://img.shields.io/badge/sponsor-h4x0r-ea4aaa?logo=github-sponsors)](https://github.com/sponsors/h4x0r)\n\n**Read a systemd `.journal` file directly — no `journalctl`, no running systemd, no live host — and surface the sequence gaps, timestamp regressions, and truncation that say a log was tampered with.**\n\nA DFIR analyst handed a disk image (or a single carved `.journal`) needs the journal's contents and its integrity story without booting the suspect system. `journalctl` refuses files it didn't write and needs a matching systemd; this reads the on-disk binary format from scratch in pure Rust over any byte slice.\n\n## Timeline a journal in 30 seconds\n\n```bash\ncargo install --git https://github.com/SecurityRonin/journald-forensic journald-cli\n```\n\n```bash\n# Chronological timeline as JSONL — one entry per line, every field preserved\njd4n6 timeline system.journal\n\n# Every field name present across the file (know what you can pivot on)\njd4n6 fields system.journal\n\n# Pull every entry matching a field filter\njd4n6 search system.journal PRIORITY=3\n```\n\n```jsonl\n{\"seqnum\":1042,\"realtime_us\":1718900000000000,\"_PID\":\"1\",\"MESSAGE\":\"Started Session 3 of user root.\",\"PRIORITY\":\"6\",\"_SYSTEMD_UNIT\":\"session-3.scope\"}\n```\n\n## The integrity signals\n\n`journald-integrity` walks the parsed entries and flags what a clean, append-only journal should never show. Each is an **observation** — the examiner draws the conclusion.\n\n| `IntegrityKind` | What it observes |\n|---|---|\n| `SequenceGap` | A break `[A, B]` with `B \u003e A + 1` — `B − A − 1` entries deleted between two surviving records |\n| `TimestampRegression` | A realtime timestamp that moves backwards, where the journal guarantees monotonic non-decrease (clock rollback or splice) |\n| `Truncation` | The file ends before `tail_object_offset` + the last object's size — a journal cut short |\n| `InvalidState` | The header `state` is `ONLINE` — an unclean shutdown or a file still being written |\n\n```rust\nuse journald_integrity::{detect_sequence_gaps, detect_timestamp_regressions};\n\nfor gap in detect_sequence_gaps(\u0026seqnums) {\n    println!(\"{:?}: {}\", gap.kind, gap.detail);\n}\n```\n\n## The crates\n\nA reader stack plus an analyzer, one workspace:\n\n| Crate | Role |\n|---|---|\n| [`journald-core`](https://crates.io/crates/journald-core) | Domain types — `JournalEntry`, `JournalField`, `JournalCursor` — with field accessors (`message`, `priority`, `pid`, `syslog_identifier`, `realtime_as_datetime`) |\n| `journald-binary` | The on-disk reader: `parse_journal_magic`, `parse_header`, `parse_object_header`, and the eight `JournalObjectType`s (Data / Field / Entry / EntryArray / hash tables / Tag) |\n| `journald-carver` | Recovery: `scan_for_journal_magic` and `scan_for_entry_objects` carve journal structures from unallocated space and corrupt files |\n| `journald-integrity` | The auditor: `detect_sequence_gaps` / `detect_timestamp_regressions` / `detect_truncation` / `detect_online_state` → `IntegrityIndicator` |\n| `jd4n6` (`journald-cli`) | The end-user CLI: `timeline`, `fields`, `search` |\n\n## Trust, but verify\n\n`journald-forensic` is built to read untrusted journal files from potentially compromised hosts:\n\n- **No `journalctl`, no systemd, no live host** — it parses the binary format itself over any `\u0026[u8]`, so it works on carved fragments and disk-image extractions that `journalctl` rejects.\n- **Bounds-checked parsing** — every length and offset from the file is range-checked before use; malformed input yields a structured `JournalError`, not a panic.\n- **No network, no telemetry** — analysis is entirely local; see [Privacy Policy](https://securityronin.github.io/journald-forensic/privacy/).\n\n```bash\ncargo test\n```\n\n## Where this fits\n\n`journald-forensic` is the systemd-journal LOG-FORMAT reader for the SecurityRonin forensic family: it navigates a journal stream by cursor (sequence number + boot id) → structured entry fields, the Linux counterpart to [`winevt-forensic`](https://github.com/SecurityRonin/winevt-forensic) on Windows. Findings normalize onto the shared [`forensicnomicon`](https://crates.io/crates/forensicnomicon) reporting vocabulary so they aggregate with the rest of the fleet.\n\n---\n\n[Privacy Policy](https://securityronin.github.io/journald-forensic/privacy/) · [Terms of Service](https://securityronin.github.io/journald-forensic/terms/) · © 2026 Security Ronin Ltd\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecurityronin%2Fjournald-forensic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsecurityronin%2Fjournald-forensic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecurityronin%2Fjournald-forensic/lists"}