IRFlow Timeline 1.0.12 — Diff Tabs, and a Triage Layer That Was Losing Work
Published August 24, 2026
Two of this release's three headline items are corrections, and one of them is embarrassing.
The new feature is Diff Tabs: compare any two imported files and get a timeline of what changed. The corrections are that the tag and bookmark layer had been quietly throwing away annotations in four separate ways, and that every DMG the project has ever published was an unsigned disk image.
Diff Tabs
View → Diff Tabs compares any two imported files — not a Computer History special case.
Pick a baseline and a compare tab, match on auto-detected identity columns or on entire-row content, and you get a result timeline of Added / Removed / Changed rows with field-level before/after, clickable status counts, and schema-delta highlighting.
The distinction worth internalising: Merge is a union, Diff is a comparison. Merge Tabs answers what happened, in order, across these sources. Diff Tabs answers what is different between this collection and that one — the second acquisition against the first, the golden image against the suspect host, yesterday's autoruns against today's.
The tag layer was losing your work
A user reported two symptoms: a new tag landing on rows they never selected, and multi-row tagging that "sometimes doesn't work". Both were real. Neither was one bug.
Tagging appeared to do nothing right after an import. Tag and bookmark writes were being discarded for the entire duration of the deferred index and FTS build — minutes, on a large timeline — while the grid cheerfully showed the tag as applied. The write was guarded against contention with the background build. That guard was never necessary: the build runs on the same database connection, on the main thread, chunked between event-loop turns, and only ever touches the data tables. Analysts tagging in the first minutes after an import lost all of it.
A new tag could land on the whole file. Opened from the Actions menu, Bulk Tag / Bookmark carried no scope. It fell back to "the filtered view", and on an unfiltered tab that is every row. Selecting rows first changed nothing, because only the selection bar's button ever passed a selection. One click after typing a tag name could tag a 30 GB timeline end to end — and a tag on every row filters to nothing useful, so it destroys the triage layer rather than merely adding noise.
Half a selection came back untagged. Applying a tag to multiple rows let each row decide add-versus-remove for itself. On a selection where some rows already carried the tag, a single click tagged the ones that lacked it and untagged the ones that had it.
Select-all plus tag marked exactly one row. Select-all is an inverted selection — the deselected rows are tracked, the members are never materialised — and the row menu fell through to its single-row path while the status bar reported the full count.
There was a fifth, quieter one: a filter round-trip could repaint the tag state cached from before an edit, so a write that did land looked like it had reverted.
What replaced it
Bulk Tag / Bookmark now leads with the scope — Selected rows / Filtered view / Entire tab — defaulting to the selection whenever one exists. Each option shows its true row count, resolved in SQLite against the exact query the write will use. A write that isn't narrowed by a selection or a filter is refused unless you confirm it.
Manage Tags is backed by live row counts. It can rename a tag (merging into the destination on collision), collapse tags that differ only by case or spacing, and actually delete a tag from the rows carrying it. Deleting previously removed only the colour swatch: every row kept the tag, still filterable, still in the report, and no longer removable from the row menu because the palette no longer listed it. Deleting a tag was the fastest way to make it permanent. Analyzer-written tags — IOC:, VT:, Sigma rule tags, Encrypted — now appear alongside manual ones.
Tags and bookmarks survive export. CSV, TSV and XLSX exports carry Tags and Bookmarked columns when the tab has any. Previously the grid showed both as columns and the export dropped them, so a triaged file came out indistinguishable from an untriaged one.
And because triage is a hundreds-of-rows-per-hour job, Cmd+Shift+1 through Cmd+Shift+9 apply palette tags to the current selection without opening a menu at all.
Every DMG we shipped was unsigned
The app inside was signed and notarized correctly. The disk image around it never was.
Notarization ran as an afterSign step, which fires on the .app bundle — before any DMG exists. The build then wrapped that correctly-stapled app in a disk image and did nothing further to the wrapper. So the installed app launched cleanly, and the download tripped Gatekeeper:
codesign -dvv → code object is not signed at all
spctl → rejected (source=no usable signature)That is the "Apple could not verify…" dialog on first open, and the reason the install page has always told you to right-click → Open. It shouldn't have. Every release through 1.0.11 shipped this way.
1.0.12 signs, notarizes and staples the DMG itself, and the build now asserts with spctl that Gatekeeper accepts the image before it is allowed to succeed — so this cannot regress quietly again. Download it, double-click it, no warning.
Hayabusa v2, v3 and v4
Hayabusa v4 merged csv-timeline and json-timeline into a single dfir-timeline subcommand with an explicit -t output type, and rejects the old form before scanning a single event. The scanner now detects the installed binary's version and builds the matching command line, keeping v2 and v3 on the legacy subcommands.
Alongside it, the Lateral Movement Tracker gains Select All / Clear for its multi-source tab list, and the Persistence Analyzer gains Clear next to its existing Select all.
Both were contributed by @Yuds16 in #27 — the project's first external code contribution. Thank you.
Computer History, re-audited again
The 1.0.10 catalog and the 1.0.11 verification both missed a later recorder kind, and both overstated how reliably the 48-hour purge runs.
terminal.value_changed is now parsed — the visible iTerm2 scrollback emitted when Secure Input blocks keystroke capture, carrying SSH targets, rsync/scp command lines and first-seen host-key acceptance. The typed password is still withheld; the command that opened the prompt is not. Records prefixed [truncated to visible range] are the on-screen slice rather than full scrollback, and are now labelled as such.
And the purge caveat: the advertised rolling 48-hour window applies while the recorder is running. A stopped recorder left 90 segment buckets on disk three days after its last write.
Full changelog → · Diff Tabs workflow → · Bookmarks & Tags →