Repository navigation
Releases: jacsonott/Winnow
Release list
Winnow 0.4.1
A short follow-up to 0.4.0. Any page can now move to a window of its own, a Pivot number opens the rows behind it, the Timeline gets the table pages' timeframe filter, and an update can restart Winnow for you. It also fixes a handful of things that went wrong when switching cases.
What's new since 0.4.0
Pages in their own windows. Right-click a page tab (SQL, Timeline, Notes, Watchlist, a plugin tab such as the Pivot, or a pinned dashboard) and pick Open in its own window, or click the ⧉ on its sidebar row. The page sits beside the table it is about instead of taking turns with it. Clicks that open a table or a row from a popped-out page land in the main window, changes made in one window show up in the others, and a pop-out closes with its main window and follows it to another case. With Pages as a dropdown turned on, each row in the list has the same right-click menu.
From a Pivot number to its rows. Right-click a Pivot number, or use the button at the top of its rows list, and Show these rows in the grid opens the table filtered to exactly what the cell counted: its values in the column filter boxes, the Filters area as filter-builder conditions, and its tags as the ribbon's tag filter. The timeframe is turned off on the way, and Winnow says so, because the Pivot counts the whole table.
A timeframe for the Timeline. ⏱ Timeframe in the Timeline header, or R, sets a start and end the same way the table pages do; r turns it off and on, and a chip in the header shows the range. Dragging across the overview strip sets the same range. It is the Timeline's own, so a timeframe set to read one table doesn't take findings off the Timeline. Every Timeline line shows its full ISO timestamp again.
Restart after updating. Installing an update from Settings → Updates now offers Restart Winnow when it’s installed, ticked by default; untick it and Restart now waits until you're ready. Winnow comes back on the same address and the same case with no new window, and every open window reloads on its own. If the new version needs a Python package this machine doesn't have, nothing is stopped and the panel gives the pip command to run. This works when Winnow was started with python server.py, which includes the launchers and file associations.
New case on the two-pane frame. + New case, and the quick look's Save as a case, list the profiles down the side with a one-line summary of each, and the case's name, group and file on the right. Switching profiles keeps what you typed.
Histogram. The strip marks each midnight, or each Monday once its bars are a day or wider, as the Timeline's overview does, and hovering a mark names its day. It charts only the datetime columns the grid shows, so hiding the charted column moves the chart to one you can see.
First/Last picks tags with the same chips as the grid, the Timeline and the Pivot, and can now exclude a tag. Sheets saved with the old tag picker open as the chips that mean the same thing.
Shipped filters. "EVTX — Defender config changes (noisy)" is no longer shipped. An unedited copy is removed from existing workspaces; one you edited or renamed stays.
Fixes
- Switching back and forth between cases no longer shows "This window is out of date" over the case you just opened.
- Clicking another case while one is still opening now opens the one you clicked last, instead of dropping the click.
- A Timeline note typed just before switching cases could be saved into the new case, on whichever row had the same table and row number. It is now saved to its own case.
- A header filter committed with Enter was built twice, and the second build could land after you came back to the table, showing the whole table under a filter box and losing your scroll position.
- In a Pivot "is any of" filter, (blank) now matches empty cells that hold NULL too, as it does in the grid.
- The Pages site's dark mode uses neutral grays instead of a green cast, and the keyboard shortcuts page lists the first-row and last-row jumps once each, with all their keys.
For plugin authors
The plugin API goes from 14 to 15. winnow.openFiltered(sourceId, pairs, opts) takes opts with where, tags and clearTimeframe, so a plugin can hand over a count's whole question, the way the Pivot now does. The tab context's apiVersion is 9. The example plugins ask for API 15. Plugin tabs can be popped out like any other page. The plugin guide was brought up to date with what plugins can do.
Upgrading
Case files from 0.4.0 open as they are, and nothing in requirements.txt changed. Use Settings → Updates or python update.py, or download the archive below. The update from 0.4.0 to 0.4.1 still ends with restarting Winnow yourself, because the running 0.4.0 doesn't know how to restart; from 0.4.1 on, the update can do it.
Winnow 0.4.0
Six weeks after 0.3.0, and the change you notice first is how Winnow reads. The Timeline tells a story instead of printing rows, the histogram is built in, and nearly every dialog now shares one two-pane layout with a live preview. Underneath, a pre-release review closed a long list of defects, and large cases got much faster, measured on generated EvtxECmd exports up to 10 GB.
What's new since 0.3.0
The Timeline reads as a story. Each line says what happened instead of printing the raw row. The day is written once, a Gap column says how long passed since the line above, and a run of repeats can fold into one line (×N). An overview strip shows the lines over time. A details pane lets you tag the row, note it, or open it in its table. "What else happened then" pulls in every untagged row, from every table, within a window around a finding.
The histogram is built in. It is a toolbar toggle now, not a plugin. Colour its bars by tag, by a column's values or by the first groups, and see where the grid is on the strip. It answers about five times faster and keeps answers it has already worked out.
Search keeps up. A search that runs past five seconds moves to the background and waits for Apply. Ctrl+F opens Winnow's search box instead of the browser's. Search all can be scoped to the tables you choose. A table whose search index isn't built yet is scanned on every core at once: on a 10 GB table that took a search from 17–19 s down to 4–5.6 s on four cores. Searches that do have an index are faster too.
More ways in. IIS web server logs are recognised by the names IIS gives them. FortiGate-style key=value logs get a column per key. Any other file can be imported as raw text, one line per row. A .tsv is read as tab-separated. Import and Import a folder are two-pane, with a file picker that filters and sorts.
Merging and saving tables. Merge tables whose columns don't match, with a new merge builder that can filter the table list by folder and handles merges past 500 tables. Save a view, or a selection of rows, as a new table. Export every table to one workbook, or a SQL result to Excel. Copy a column's unique values, or another table's column layout.
Derived columns. New operations: Coalesce across several columns, Row as JSON, one column per named regex capture group, URL decode and Base64 decode. Derived columns can be renamed, land next to the column they came from, preview against the rows on screen, and fill in without leaving the table.
The grid. Excel-style cell selection from the keyboard, and row selection from the gutter, where ranges add and picks stick. Filter by several tags at once (or by none), exclude a tag by right-clicking its chip, press z to undo the last filter change, and press Shift with a tag's key to take the tag back off. Each new tag gets its own colour and the next free key. A table-menu switch hides empty rows, and row striping is a setting saved per skin.
Analysis tabs. First/Last can total a column, count unique values and list each group's distinct values, and it works in the background. Pivot filters on tags and computes every subtotal level in one scan. Dashboards repaint their last results instead of re-running, plugins can offer boards of their own, and the KAPE triage board went from 26 cards to 10. Profiles say what applying one will do to the case. The Watchlist opens on its findings, scans in the background, groups hits by table and shows where each one matched. The Claude assistant example adds a SQL Copilot to the SQL pane.
One look throughout. Settings, the table menu, the Tables manager, the home screen, Import, profiles, the widget editor, Timeline configuration and the last of the older dialogs now share one two-pane frame. A plugins manager replaces the list in Settings. Notes have a live preview beside the editor. There is a new theme, Jenna, and Harvest is softer. Settings lists every keyboard shortcut and can shut Winnow down.
Big cases. Wide tables build only the columns on screen. EvtxECmd imports index EventId and the first timestamp column up front, so dashboards and time sorts stay fast. Grid pages, merged views, First/Last, Pivot, the tag rail and the histogram all do less work per request.
Windows. Open With says "Winnow", not "Python". A double-click launch with no console starts cleanly, a stalled console no longer freezes the app, and Winnow warns when it runs as administrator, which blocks dropping files onto it.
A manual. The user guide lives in docs/wiki, is published as the GitHub wiki, and from this release is also a site at https://winnow.jacson.tech, with a page and a recording for each feature.
Hardening in this release
A pre-release review fixed findings across the app. The ones worth knowing about:
- A link in case notes could run script when hovered, because the Markdown renderer didn't escape quotes in URLs. Notes travel with case files, so update before exchanging cases.
- Loading or comparing a session matched tables by their file alone, so tables imported from one SQLite database or workbook could swap tags or lose them. Sessions now match table to table.
- Imports no longer lose rows on odd input: an XLSX sheet that declares the wrong size, SQLite text that isn't UTF-8, JSON with lone surrogate escapes, a field over 128 KB, or a damaged archive.
- 12-hour timestamps with fractions of a second ("1:22:01.123 PM") were read twelve hours early in derived columns, the timeframe filter and the display.
- One unreadable value no longer leaves a derived column stuck building.
- A regex that could backtrack for minutes is refused instead of freezing the app.
- Formulas in export headers, tag names and SQL pane CSVs are neutralised.
- A runaway SQL pane query can be stopped, and a slow result can no longer paint over a newer one or land in another case.
- A case opened at startup, including by double-click, runs with that case's own plugin settings.
- An update or backup stopped part-way puts the install back, rollback restores the install manifest, and files a release no longer ships are removed.
- A case another Winnow window has open can't be deleted, and with two windows open each case keeps its own tags.
For plugin authors
The plugin API goes from 7 to 14. New since 0.3.0: api.register_dashboard and api.register_page_panel; the tab context's sqlPage, notesPage, notify, tabState, dropdownMenu and sectionPanes; a dashboard widget's live flag; and the signals render kind. One change can break a plugin: SQL pane results are held on the server now, so sqlPage results carry total and stopped, rows is the first 5,000, and sqlPage.selectedRows() is async. The saved-views API, which nothing in the app used, is gone; saved filters cover it. The table_histogram example is retired in favour of the built-in histogram.
Upgrading
Case files from 0.3.0 open as they are. python update.py updates in place, or download the archive below. Winnow needs Python 3.10 or newer (0.3.0's notes said 3.9, which was wrong: it never started on 3.9) and the packages in requirements.txt, where FastAPI is now held below 0.147 because 0.147 changed how form defaults are read.
Winnow 0.3.0
First release since 0.2.0, and a large one: the analysis layer around the grid grew into most of what an investigation needs, and this cycle closed a round of hardening on top of it.
What's new since 0.2.0
Dashboards and analysis profiles. Build a board from the grid — right-click a value to count it, or use the guided widget editor — and click any number to open the rows behind it. Two profiles ship ready to apply: KAPE triage and ESXi / UAC triage, each a bundle of plugins, a watchlist and a board.
Plugins. A full host with five extension points (ingest formats, tabs, API routes, row actions, toolbar panels) and seven bundled examples, off until you turn them on: raw NTFS $MFT/$J parsing, ESXi and UAC log parsing, a pivot table, session bookends, a lateral-movement graph, a time histogram you drag to set the timeframe, and a Claude assistant that sees the schema and writes queries.
Sessions you can compare. Named snapshots of your tags and notes live in the case file and travel with it. Comparing two shows counts per table, and clicking a count opens exactly those rows in the grid, each marked with which session tagged it.
More evidence in one place. Merge same-shaped exports from a fleet into one view. Sweep an indicator across every registered case, query several at once, or read their timelines side by side. Import Plaso storage files, Excel workbooks, and ESXi or UAC bundles as zip/tar archives with nested archives expanded.
Reading the data. Derived columns from timestamps in any format, from regex capture, from nested JSON and XML, or looked up against another table. A stack view for rarest-first values, an IOC watchlist, Markdown case notes, a folder tree in the sidebar, and a SQL pane with saved query tabs.
Getting in and out. Double-click launchers for Linux, macOS and Windows, OS file associations, and in-place updates that never touch your cases, plugins or workspace.
Hardening in this release
- The SQL pane's read-only guarantee is now enforced by the database connection rather than by scanning the query text, which a comment could talk past.
- A filter naming a column the table does not have is refused instead of dropped. Dropping it showed more rows than asked for, under a filter that claimed to be applied.
- Undo after loading a session no longer replays against tag state that load replaced.
- The cross-case indicator sweep no longer skips a table whose header contains a quote, which silently reported zero hits.
- The server validates the
Hostheader, so a web page cannot reach the API by re-pointing its own name at your machine. Use--allow-hostif you front Winnow with a name. - Opening a case refuses files that are not cases, rather than writing Winnow's tables into them.
- Plugins can no longer write to source or analyst tables.
- Switching cases no longer carries the previous case's notes, marks, jobs or filters into the next one.
- Several conditions that returned an internal error now report what actually happened and can be retried.
Upgrading
Case files from 0.2.0 open as they are; new tables are added on open. python update.py updates in place, or download the archive below. Needs Python 3.9+ and the four packages in requirements.txt.
Winnow 0.2.0
The file-associations release: double-click your evidence.
Highlights
- OS file associations (Windows + Linux, per-user, no admin): register Winnow as a handler for everything it reads — CSV/TSV, JSON/JSONL, SQLite, Excel, and any plugin-registered type — from Settings → File associations. Safe types can be made the double-click default, and newly added extensions ask once at launch.
- Quick-look cases: a file opened by association lands in a disposable case instantly — no naming dialog. Save it as a real case, copy its tables (tags and notes included) into an existing case, or discard it; navigating away asks first, so a quick look can never be stranded.
- Winnow case files are
.db-winnownow, with the new brand icon in the file manager. Existing.dbcases open unchanged — the launcher sniffs content, not extensions. - Idle shutdown: association-spawned servers exit on their own once no browser is connected; a
--no-idle-shutdownflag opts out. Servers coordinate through an instance registry, so multi-selecting five files opens one window, not five. - Sessions live in the case file and can be diffed for QC handoffs; splash screen and five theme families with the Harvest look as default; pinned columns; mouse back/forward across recent tabs; themed scrollbars; a term-file importer for Search all.
Fixes worth knowing about
- A path-traversal (zip-slip) in the in-place updater was fixed — a crafted archive could write outside the install directory. Update promptly.
- Ingest of broken files is far more forgiving: UTF-16 exports decode properly, a bad line mid-file keeps every parsed row (with the line named), one malformed JSONL line no longer discards the file, and unbalanced-quote damage is flagged.
- Remote session mode is a machine setting now and survives updates.
Update an existing install with python update.py; airgapped machines: --download-only on a connected box, --from <zip> on the target.
Winnow 0.1.0
First tagged release.
Winnow is a local viewer for the very large CSVs that DFIR triage produces — EvtxECmd, MFTECmd, Amcache, whole KAPE output folders — with the row-tagging workflow Timeline Explorer gets right. A 1.2M-row CSV imports in about 8 seconds, filtering it takes milliseconds, and scrolling stays smooth at any depth.
Everything runs locally: one Python server, no cloud, no CDN, no build step, and it works on an airgapped analysis box. Your work lives in a single SQLite case file you can hand to another analyst, and the evidence files themselves are never modified.
Install
pip install fastapi "uvicorn[standard]" python-multipart openpyxl
python server.py --case case.db --open timeline.csvIt opens in an app window — no address bar, its own taskbar entry — using Edge on Windows or Chrome elsewhere, falling back to a normal tab if neither is present.
Updating
python update.py # check for and install the next release
python update.py --rollback # undo itYour saved filters, tags, installed plugins, case list and case files are never touched by an update, and the previous version is backed up first. On a machine with no network, download the release elsewhere and apply it with python update.py --from <file>.zip.
Beta testers tracking unreleased work can use python update.py --dev.