git-forensic 0.1.0

Forensic anomaly auditor for Git object stores — backdated commits, rewritten history, unsigned-in-signed-history, and unreachable objects as graded report::Finding, built on git-core
Documentation

git-forensic

git-forensic git-core Docs.rs License: Apache-2.0 CI Sponsor

Point it at a .git, get back severity-graded Git anomalies — backdated commits, rewritten history, unsigned commits in a signed history, and resurrectable dropped objects as forensicnomicon::report::Findings.

[dependencies]
git-forensic = "0.1"   # pulls in git-core
use git_forensic::{audit_repo, source};
use git_core::GitRepo;
use std::path::Path;

let repo = GitRepo::open(Path::new("/path/to/.git"))?;
let head = repo.head()?;

for anomaly in audit_repo(&repo, head)? {
    let finding = anomaly.to_finding(source("repo"));
    println!("[{:?}] {}{}", finding.severity, finding.code, finding.note);
    // e.g. [Some(Medium)] GIT-COMMIT-TIME-INVERSION — committer timestamp … precedes author …
}
# Ok::<(), git_core::GitError>(())

audit_repo walks commits from a starting hash and grades what it finds. Damaged or unreadable objects surface as typed errors, never a panic.

The anomaly codes

Each anomaly is an observation ("consistent with …"); the examiner draws the conclusions. Codes are a stable, published contract.

Code Severity What it observes
GIT-COMMIT-TIME-INVERSION Medium A commit whose committer timestamp precedes its author timestamp — consistent with timestamp backdating (benign cause: cross-machine clock skew)
GIT-HISTORY-REWRITE Medium A reflog entry whose operation rewrote history (reset / rebase / amend / filter-branch / forced update) — the prior tip remains resurrectable
GIT-UNSIGNED-IN-SIGNED-HISTORY Medium An unsigned commit within a predominantly-signed history — consistent with a commit injected outside the prevailing signing policy (benign cause: a forgotten signature)
GIT-UNREACHABLE-OBJECT Medium / Low An object reachable from no ref — residue of deleted or rewritten history, resurrectable until gc (a dropped commit grades Medium; a loose blob/tree grades Low)

GIT-COMMIT-TIME-INVERSION comes from audit_commit / audit_repo; GIT-HISTORY-REWRITE from audit_reflog / audit_reflog_entries (classify_rewrite is the pure decision core); GIT-UNSIGNED-IN-SIGNED-HISTORY from audit_signatures / audit_signatures_repo; GIT-UNREACHABLE-OBJECT from audit_unreachable. Each anomaly emits a graded report::Finding via to_finding(source).

The attribution timeline

Beyond the graded anomalies, the attribution module flattens a set of commits into the who-did-what-when backbone an examiner narrates on:

  • attribution_timeline(commits) → a time-ordered stream of author/committer identity events, timezone offset retained (it can corroborate or contradict a claimed location).
  • distinct_identities(commits) → the distinct (name, email) identities, in first-seen order.
  • attribution_repo(repo, from) → the same, walked from a repository tip.

The two-crate split

This crate is the analyzer; the reader is git-core (loose + packfile objects, refs, commits, trees, reflogs, and GitRepo navigation over a .git directory). The split mirrors ntfs-core/ntfs-forensic. Together they feed issen for cross-artifact correlation.

Trust, but verify

Built for untrusted object stores from potentially compromised systems: #![forbid(unsafe_code)]; panic-free on crafted input (the workspace denies clippy::unwrap_used / expect_used in production code); git-core is fuzzed with four cargo-fuzz targets and exercised against real .git directories with packfile delta resolution cross-checked against git itself.


Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd