Added
-
The graph player shows byte flow as moving grain. Ribbons carry small
particles drifting from source to destination - density and speed follow
each flow's current rate, inside the same frozen layout. On by default for
byte-active conn replays (where rate is a measured claim), off for
count-only graphs, always toggleable (flow on|off), and the default
respects reduced-motion preferences. Filter clicks now snap the scale
instantly instead of gliding. -
The graph player carries a scale gauge. A small bottom-left bracket shows
what a ribbon's height is worth (200 B/s,5k/s), reading the exact scale
the ribbons are drawn with - so the gauge can never disagree with the
picture. It breathes with the scale infill, sits still inabsolute,
follows the bytes/conns metric, and rides into saved clips. -
sigwood initoffers to skip detectors you have no logs for. Decline a
source during setup and the wizard now names the default-hunt detectors that
would have read it and offers a one-keystrokedetectexclusion (e.g.
detect = "default, !beacon, !scan") - so a box without Zeek stops seeing
per-run "conn.log not found" notes it can do nothing about. Re-running init
after adding the source offers to lift the exclusion; a hand-written custom
detectvalue is never touched, and declining the offer writes nothing. -
Pick a graph by name:
sigwood graph conn(alsodns,pihole). Kind
names select just those artifacts from configured sources; names compose
(sigwood graph conn dns) and a single selected kind may stream to stdout or
an exact--outfile. Kind names and paths don't mix - a file literally named
connis reached as./conn. With kinds selected, only those families are
probed, so a broken or unreadable unselected source no longer fails the run. -
Graph flow grain is bidirectional. Byte-active conn replays now show
responder traffic as reverse grain - particles drifting destination-to-source
in the source's color - split from forward grain by each flow's true
responder-byte share (Zeek'sresp_bytes, newly carried in the canonical
connection schema). Ratios are exact at every aggregation tier - server
folds and in-player rollups both sum bytes before dividing - so a
download-heavy flow honestly shows mostly reverse grain. Ribbon shapes,
counts, and saved-artifact geometry are unchanged; the optional direction
ratio travels with clips, and graphs without responder data simply show
forward grain as before. -
The report says how it was made. Text and HTML/PDF reports now carry a
provenance row -generated: <timestamp> · sigwood <version>- and, for CLI
runs,as: <the exact command line>- so a saved report answers "which tool,
which version, when, invoked how" by itself. The JSON feed carries both facts
asrun_summary.generated_at(ISO UTC) andrun_summary.invocation
(additive; no schema bump).
Changed
- An "admin session" can no longer span days. Hosts with periodic privileged
automation (a sudo cron under 45 minutes) used to chain their session
open/close lines into one enormous "admin session" review unit - up to two
weeks wide on long lookbacks - hiding unrelated findings under a wrong label.
Session recognition now declines any candidate longer than 8 hours (measured:
real interactive sessions top out well under 2 hours, automation chains start
near a day), and the affected findings simply keep their own shapes. Genuine
sessions, update runs, and reboot handling are unchanged. - Hardware-key logins and kernel dumps no longer flood the syslog report.
A second calibrated drain3 mask recognizes space-separated hex-pair runs (four
or more pairs - FIDO2/CBOR debug dumps, kernel oopsCode:lines), so a dump
that repeats at every login shares one template instead of minting a fresh
"rare" burst each time. Measured on a month of real fleet logs: the per-login
burst class disappears while every MEDIUM finding and every genuinely
once-ever event is preserved. Colon-joined MAC addresses and ordinary
two-pair text (dates,port 22 80) are never masked; raw log lines are
displayed byte-for-byte as always. - Syslog rows share one grammar. Burst spans render through the same compact form
as family and transaction rows (with a new honest seconds tier below one minute -
45s, not0m), burst counts pluralize properly, and a transaction row now leads
with its rare-line magnitude (19 rare lines) instead of the internal
"member findings" phrase; the member count remains in evidence and JSON. Program
mixes de-duplicate case variants for display (CRON, cronreads once, first
spelling kept) - counts and machine output keep both. - The report header names sources, not glob patterns. The
records:line reads
12,345 syslog · 90,000 Zeek conninstead of12,345 *.log* · 90,000 conn*.log*.
The JSON feed additively carries the mapping asrun_summary.record_labels
(no schema bump);record_countskeys are unchanged. - Syslog review units roll up only when it pays. Family and burst folding now
starts at four rare lines (previously two and three): below that, a summary row plus
its expansion costs as much space as the lines themselves, so one to three rare
lines surface individually. Recognized transactions still group from two findings -
their label carries meaning beyond compression. Tunable as before via
family_min_size/burst_min_sizeunder[detectors.syslog]. - Capsule summaries are tokens-only. The per-template digest lines inside family
and burst rows are gone; thetokens:scent line may now span up to two lines, and
opening a row's sample always reveals something new (the raw lines). rsyslog's
#011/#012/#015whitespace escapes read as separators while distilling tokens,
so multi-line commands no longer render as glued tokens; raw lines stay verbatim. - Transaction rows in HTML expand straight to their raw log lines. The
severity-pill toggle on an admin-session or update-run row now opens the members'
sampled raw lines under thin per-member separators instead of one-line member
summaries; JSON member records additively carry the same bounded samples, in
chronological member order. Separators name severity, program, and line count
only - internal grouping vocabulary no longer appears in any output - and a JSON
member record carries atierkey only when the member really is a family or
burst rollup (a plain rare line omits it). Log content is never rewritten:
whatever words an operator's own lines contain render verbatim. - Graph artifacts open in the full three-column view. The player's view
segment now lands with the middle tier on - services for conn, resolvers or
query types for dns, dispositions for Pi-hole - matching how the graph is
actually read; one click onhostscollapses it. The segment labels name the
view states plainly. Clips inherit the new default; previously saved artifacts
are unchanged (they are self-contained). graphwindows a directory like the hunt does. A baresigwood graph
against a Pi-hole (or any flat) directory now reads the lastdefault_window
of available data - peeking rotation files' first timestamps and skipping the
rest instead of decompressing the whole archive - and the artifact discloses
the window.--all, explicit timeframes, and single named files behave
exactly as before.warn_above = 0now disables the large-dataset prompt. Previously 0 meant
prompt before analyzing any amount of data; it now switches the advisory prompt
off entirely - the config-side equivalent of passing-yevery run, suited to
cron and other unattended schedules. Set a small positive value to keep an
aggressive prompt.- The report wordmark matches the graph player's brand mark. The
sigwood
token in the HTML/PDF report header now renders in the same serif face and
light/dark color pair as the graph player's wordmark; the· threat hunt
tagline keeps its bold sans treatment. A parity test pins both artifacts to one
identical font stack and color pair so the two marks cannot drift apart.
Fixed
- A reboot is never mislabeled an "update run", and its
rebootedmarker can
no longer vanish from the report. On RHEL-family hosts a normal boot emits
lines that satisfy the update-run grammar (dracut, the auditd stop/start pair,
SELinux policy load), so a routine reboot could form a medium "update run"
review unit that swallowed the reboot itself. Update-run recognition now
ignores anchors inside a detected boot window (measured bounds), and a
rebootedburst is never claimed by any review unit - the reboot always
stays a visible row, including after a genuine update-then-reboot. - Graph flow particles read as streams, not stripes. Same-moment spawns used
to move in lockstep and bunch into vertical bands that marched across a
ribbon, most visibly after a filter click. Each particle now carries its own
slight speed variation, a restarting ribbon fills uniformly along its length,
and the round dots are replaced by short motion streaks that follow the
ribbon's curve - longer for faster flow - with a gentle brightness shimmer
lifted off the ribbon color. Particles now pause with playback: a paused
frame is fully still, and a filter click while paused recomposes the grain
without motion. Flow direction, the toggle and its defaults, and saved
artifacts are unchanged. - The graph header now counts the files that fed the graph, not the archive it
scanned. The source cell used to report every file discovered under the
source directory (+ 294 otherson a large rotation archive) even when the
rendered window drew from a handful of them. The loader now records each
file's kept time span and the header names only the files whose data
overlaps the final rendered window; saved clips keep the parent artifact's
set. The live system journal never records file paths. - The HTML report now discloses an underfilled window like the text report.
The HTML header's window row carries the same span parenthetical the text
banner always had - the bare data span normally, and the
(Xh data span in Yd window)disclosure when the loaded data underfills the
requested lookback. The two surfaces share one formatter so they cannot drift. - Progress lines name the directory when file names repeat. A dated Zeek archive
loads several files that share one rotation name, and the load narration used to
render identicalloaded conn.00:00:00-00:00:00.log.gzlines for all of them. When
any file names in one load collide, every line now carries one parent-directory
component (loaded 2026-05-01/conn.00:00:00-00:00:00.log.gz), and a corrupt-file
warning names the same disambiguated file. Loads without repeated names render
exactly as before.