Releases: helixmap/sigwood
Release list
sigwood v0.2.8
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
...
sigwood v0.2.7
Added
- Recognized syslog transactions. The syslog detector now folds an administrative
session (login through logout) or a system update run (package, kernel-module, and
policy activity) into one labeled review unit per host - for exampleupdate run · 4 member findings · 1m- with every member finding preserved behind it: a compact
drill-down at-vin text, an expandable row in HTML, and complete member evidence
in JSON. Severity still comes only from the privileged program class; recognition
groups findings, it never grades them. Default on;recognize_transactions = false
under[detectors.syslog]restores the previous behavior exactly. - Host suppression allowlist. A third flat-list kind silences a chatty machine's
system logs whole-host: dot-freehosts*drop-ins inallowlist.d/(one fnmatch glob
orre:regex per line, matched case-insensitively against the syslog host column),
applied before analysis across all three syslog feeds - flat files, the system journal,
and Zeeksyslog.log. DNS and connection logs are untouched. The run banner gains a
third coverage clause (suppressed 9,412 rows from 2 hosts), JSON carries the new
host_rows/host_total/hosts_matchedsuppression fields, andsigwood init
seeds a blankhoststemplate whose header warns that host suppression removes the
whole host's story and shifts relative rarity. Host lists are local-only - sigwood
never ships one.
Changed
- Steadier terminal narration. The terminal cursor is hidden during narrated
analyze, digest, graph, and export runs and restored across clean exits, failures,
and interrupts; interactive prompts show it while you type. Quiet runs,
dumb terminals, and redirected output see no cursor-control bytes at all.
Fixed
- Cross-feed syslog duplication no longer hides unique lines. When the same
host's messages reach both the local system-log feed (files or the journal) and
Zeek'ssyslog.log, the duplicate coverage doubled that host's template counts, so
a line that should have surfaced as a unique rare event silently left the rare set.
sigwood now arbitrates per host: a host present in the local feed keeps its local
rows only, and Zeek contributes just the hosts the local feed lacks. The run
summary discloses it in counts (system logs: 1 host carried by both the local feed and Zeek syslog.log - kept the local rows (16,094 Zeek rows set aside));
loaded record counts are unchanged. Hosts only Zeek can see are unaffected, and
hostless (unknown) lines are never arbitrated.
Security
- Artifacts are private by default. Every directory sigwood creates is now mode
0700 and every file it writes is 0600 - reports, digests, graph artifacts, exports,
config and its backups, and allowlist seeds - independent of the process umask, and
re-applied when an artifact is overwritten in place. The CLI also sets a 077 umask
backstop for every command exceptinit. - Loose-home advisory. When an existing sigwood home is group- or world-accessible,
each run prints a one-line stderr reminder with the exactchmod 700to close it.
sigwood never changes permissions on a directory it did not create. - A system-wide
/etc/sigwoodconfig keeps ordinary umask-governed permissions so
non-root users can still read it.
sigwood v0.2.6
Changed
- Syslog capsule detail lines distill harder and fill the row. A capsule whose
sampled content fits on one line now shows a singletokens:line: the members'
words in order, duplicates removed, complete. Larger capsules keep up to three
per-pattern lines, now allowed 200 characters instead of 80. Opaque identifiers are
filtered more precisely: audit record ids such asmsg=audit(...)and kernel
ring-buffer stamps no longer crowd out readable content, while IP addresses, ports,
sizes, dates, and version strings are always kept. - The severity pill is the sample expander in HTML reports. The separate
"sampled log lines" control is gone; the[M]/[L]/[I]pill toggles it instead
(a muted+beside the pill marks expandable rows; the colored pill itself keeps
one constant width everywhere), and opening a capsule swaps its distilled lines for
the raw sample. Sampled lines are syntax-highlighted: timestamp bright, host and
program each in their own color, in both light and dark themes. The report remains
JavaScript-free, and printed or PDF output carries no interactive control. - The HTML timestamp column hugs its content. The leading stamp column is sized
to the stamp itself (wider only under--utc, whose stamps carry a zone suffix),
removing the fixed dead gap that previously sat between a capsule's timestamp and
its hostname.
Fixed
- No more stray
|above the report. The render spinner no longer runs while the
report itself streams to the same terminal, which previously stranded an orphaned
spinner frame above the findings on every interactive run. - Capsule detail lines align with the timestamp column in text reports; they
previously sat one column to the left. [detectors.syslog].line_trim_limitnow works. The published config key was
read by nothing; it now trims lone rare-line titles as documented.- Severity pill text is readable in both themes. White-on-color pill text
measured as low as 2.1:1 contrast in dark mode; each severity now carries a
per-theme ink color, every pairing clears the 4.5:1 accessibility bar, and a test
pins the bar so future palette changes cannot regress it. One background moved
slightly to reach the bar: the light-theme low-severity blue.
sigwood v0.2.5
Fixed
- A lone rare line read from the systemd journal now leads with its timestamp. Journal
entries carry their time as a separate field rather than inside the message text, so these
rows previously rendered as bare message text in an otherwise time-ordered section - the
common case on a modern Linux install, where sigwood prefers the journal. The row now
starts with the same syslog-shaped stamp the grouped rows use. Lines from syslog files are
unchanged: they already begin with their own wall clock, and sigwood never adds a second
stamp to a line that has one.
Changed
-
Grouped syslog rows now show a few distilled lines from what they group, and stay on one
line each in HTML reports. A review unit, burst, or reboot row is followed by up to three
short fragments drawn from distinct message shapes inside it, so the row invites a closer
look instead of only counting one. Fragments keep addresses, ports, process ids, exit codes
and sizes, and drop only long opaque identifiers such as hashes and session tokens. On
screen, syslog rows are clipped with a trailing ellipsis rather than wrapped or scrolled -
widen the window to see more; printed reports still wrap so nothing is cut from a PDF. The
leading stamp on those rows is now written in syslog's own wall-clock shape
(Jul 12 21:57:33), so it reads in the same grammar as the log lines beside it. On a 7-day
measurement corpus every grouped row carried fragments, with none empty. -
Rare syslog findings now separate a privileged program channel from the routine
rarity sieve. Exact program membership in a shipped, operator-replaceable roster keeps
security-critical families and singletons at MEDIUM; other rare families and singletons
render as default-visible LOW, while bursts and reboots remain INFO. Family and burst rows
now lead with their first timestamp,-vshows a three-line member sample, and HTML
reports provide a closed full-sample expansion without leaking that body into printed
reports. On the same 7-day measurement corpus, the report reads 12 privileged + 16
routine + 10 info findings (38 total, under a minute to scan) - the increase over the
prior 29 is privileged rows formerly buried inside info bursts, now independently
reviewable. -
Syslog rare-event output now groups isolated lines into per-host, per-program review
units and normalizes long identifier-like hexadecimal-character runs during template
mining. Temporal bursts and reboot handling remain separate, while raw log text stays
intact in evidence. On the measurement corpus, a 7-day multi-host run that previously
reported 44 isolated rare-line findings now reports 19 review units (29 findings in
total, from 56); a single quiet day reports about a dozen. Grouping changes how the
findings are presented and counted, not what the detector observes - every rare line is
still present in exactly one burst, family, or standalone finding.
sigwood v0.2.4
Fixed
scanvertical and horizontal findings now describe the time window that actually
triggered them, not the whole loaded span. These scans fire when enough distinct ports
or hosts appear inside a sliding window, but the reported evidence - connection count,
scan-state ratio, top states, port-range entropy, and host velocity - was previously
computed over every connection the pair exchanged across the entire log. A short scan
burst buried in a long benign baseline between the same two hosts was therefore diluted:
because severity and ranking are driven by the scan-state ratio, a real burst could be
under-severitied and pushed down the report by the surrounding benign traffic. Evidence,
severity, and rank now all reflect the triggering window. Block and slow scans are
unchanged (their evidence was already window- and span-correct by design).
Changed
- The DNS detector's default surface threshold now matches its 1.8 high-entropy bar.
On the measurement corpus, 87 of 105 DNS findings scored from 1.5 up to but not
including 1.8; the 18 findings at or above 1.8 remain under the new default. - The default hunt is now a curated set rather than every available detector. Fresh
installs, omitted selection,--detect=, and--detect=defaultselect aws, beacon, dns,
scan, and syslog; duration remains runnable by name while its severity evidence is rebuilt.
Reports and dry runs disclose that opt-in remainder. Explicit--detect=alland existing
configs withdetect = "all"are unchanged and continue to run everything. This is a
quieter default, not a claim that detector logic became smarter.
sigwood v0.2.3
Added
- The syslog detector now reads the live systemd journal directly - no
sudo, no export
step, no durable capture file. On a systemd hostsigwood syslog(andsigwood hunt) invoke
journalctl --output=jsonfor the invoking user's readable system journal, normalize every
entry into the same five columns as flat syslog, and analyze them identically. A new
--syslog-source=auto|journal|files|offflag (and[sigwood].syslog_source, defaultauto)
chooses the local carrier:autoprefers the journal and falls back to the configured
syslog_dirfiles when journalctl is absent/unusable or the journal has no usable entries;
journalrequires it;filesuses the flat directory only;offdisables the local lane.
Exactly one local carrier is used per run - sigwood never merges the journal and flat files.
Journal access depends entirely on the invoking user's permissions; sigwood never invokes sudo.
Requires systemd 236+ (for--output-fields); a single journal entry over 1 MiB fails the run
visibly rather than being silently truncated.sigwood initgains a compound "system logs"
choice that detects and recommends the best local source. - syslog ingestion now reads ISO-8601 / RFC-3339 timestamps in addition to RFC 3164 - the
high-precision format stock rsyslog writes on Ubuntu/Pop 24.04 and newer. ISO stamps carry an
explicit year and offset, so they convert directly to UTC and are not subject to the RFC 3164
year-guess. Discovery accepts an ISO line only when it also carries a host and a colon-terminated
program tag, so an ISO-timestamped application log (such asdnf.log) is not mistaken for syslog.
Changed
- A connection graph now replays bytes across each connection's lifetime.
sigwood graphon a
Zeek conn log previously drew a connection's entire byte total in the single bin where it started,
so one long transfer landed as a spike at its start and the rest of its life looked idle. Byte
ribbons now spread that recorded total at a constant rate across the connection's recorded
duration, while connection counts stay anchored at starts (those surfaces readconn starts).
Zeek records a connection's total and duration but not the timing of bytes within it, so the even
spread is an explicit model, not a claim that a bursty transfer was uniform - the artifact names
the assumption, and discloses any recorded byte mass clipped outside the window it shows. Bands
engage only where a row carries both positive bytes and a positive duration; zero-duration rows,
DNS, and Pi-hole graphs are unchanged. - Existing installs migrate from files-only to journal-preferred
auto. A config that never
setsyslog_sourcenow defaults toautoon a systemd host, sosigwood syslogprefers the
live journal (falling back to yoursyslog_dirfiles if the journal is unavailable or empty).
The run discloses which source it used. To keep the previous file-only behavior, set
syslog_source = "files". A config with an explicitly-emptysyslog_dircontinues to disable
the local lane unchanged. - The "permission denied" message for an unreadable log now gives correct, least-privilege advice:
it suggestsusermod -aGonly for a group-readable file owned by a known log-reader group
(adm); for a root-only (0600) log, or one owned by a privileged group, it points at adjusting
group ownership or an ACL instead of recommending a group join that would not help or would
over-grant.
sigwood v0.2.2
Changed
- The
graphverb is now resilient on real-world data: a valid log always produces a graph.
Instead of failing on a dense or very large source, the builder degrades within the player -
adapting host and service rankings, bin width, and smoothing - until it fits. Timelines with
a long, sparse lead-in are trimmed to the active window, so a handful of long-lived flows no
longer stretch the axis. The player's header is now a labeled provenance readout - source,
window, entity counts, bin size, and the exactsigwood huntcommand for the same log - and a
Zeek directory that lacks byte or service detail still renders a connection-count graph.
Fixed
sigwood graphno longer aborts with a "too dense for smooth interaction" error on an
ordinary day of dense logs; an oversized graph degrades to fit the animation instead of
failing.
Security
- Releases are now published through PyPI Trusted Publishing: each distribution is built and
uploaded by a tag-triggered CI workflow that authenticates to PyPI over OIDC, with no
long-lived upload token stored anywhere. Every published file carries a PEP 740 publish
attestation - a Sigstore-backed, verifiable record that it was uploaded by this project's
release pipeline. This is publication provenance (which pipeline uploaded the file), not a
claim about how the code was built or that the code is safe.
sigwood v0.2.1
[0.2.1] - 2026-07-13
Added
- A
graphverb (sigwood graph <path>) that writes a self-contained HTML artifact -
one file, no server, no external resources, no network calls - replaying a log's flows
as an animated Sankey with time, speed, and filter controls. A Zeek directory produces
a conn graph (hosts vs the services they reach) and a dns graph (clients vs the domains
they look up); Pi-hole adds a disposition lane showing what happened to each query
(blocked,forwarded,cached, orlocal). Likedigest, it reads before the
allowlist and states facts, not verdicts, and every artifact ends with the exact
sigwood huntcommand for the same log.
Changed
- The curated
commondomain allowlist now ships its generic UUID-label rule disabled
(commented out). It suppressed any query embedding a UUID under any domain - a lexical
shape an exfil or beacon labeling scheme can simply choose - so it could drop hostile
queries before analysis. Devices that chatter with UUID labels are better suppressed by
their vendor apex; some users may see new DNS findings from such devices after
upgrading. The rule stays in the file, commented, for anyone who wants to re-enable it
knowingly. - A syslog burst finding without an observed reboot is no longer described as
"resembling a boot or batch event" - the neutral description states the cluster and
nothing more, and the reboot wording appears only on bursts a detected boot event
actually claimed. - TOML allowlist stanzas are now documented as what they do today - structured
suppression with a comment and per-detector scope - rather than as a classification
mechanism no shipped detector consumes yet. A stanza missing itsmatchkey (or a
stanza file that isn't valid TOML) now fails with an actionable message naming the
file instead of a rawKeyError.
Fixed
- Pointing sigwood at a directory that holds a mix of log families no longer silently
drops the minority families: the run now says on stderr which family won the routing
vote and what was sampled, a detector skipped because the positional target routed
elsewhere now says so (instead of the misleading "not configured"), anddigest
notes each directory it skips in a multi-file invocation instead of passing over it
without a word. - A detector that crashes mid-run no longer reads as a clean night. The run still
continues past the crash (sibling detectors' findings are unaffected), but the failure
is now disclosed everywhere a scheduled run looks: the process exits nonzero, the JSON
feed carries it underrun_summary.detectors_failed(name → reason; empty{}on a
clean run), the HTML/PDF report header shows a failure row, and the text report ends
with afailed:line so a saved report is honest too. The FAQ's cron alerting recipe
now pages on failed detectors as well as findings.
sigwood v0.1.1
[0.1.1] - 2026-07-11
Changed
- Source and development install instructions in the README now create a virtualenv
first, so apip install -ein a fresh clone doesn't hit the PEP 668
externally-managed-environmentrefusal on Debian, Raspberry Pi OS, or Fedora. - Splunk export now reports a TLS certificate-verification failure with an actionable
message, naming theverify_tls = falsesetting under[export.splunk]for a
self-signed certificate on a trusted network, instead of a generic connection error. - Source distributions no longer bundle the test suite - it shipped without the support
files needed to collect it, so it was unusable - and a release-time gate checks the
sdist carries no tests. - Package metadata now declares the license as the SPDX
license = "MIT"expression with
license-files, dropping the deprecated MIT license classifier, and sets the operating
system classifier to POSIX.
Fixed
- The beacon detector no longer silently drops a genuine beaconing flow to a unicast host
whose IPv4 address ends in.255(a valid host in any network wider than a /24). Its
pre-filter now classifies non-unicast destinations and sources with the standard-library
ipaddressmodule rather than a string-prefix test, so multicast, link-local (including
IPv4169.254.0.0/16), and the255.255.255.255limited broadcast are still excluded
before scoring while real unicast hosts are kept. - Copy polish in the
initwizard prompts. sigwood initno longer lets a source you explicitly skip during setup quietly come
back. A skipped default source (Zeek or syslog) is now written as
<key> = "" # disabled during setup, so the config merge cannot silently re-enable it;
removing a source still reverts it to the shipped default.- The
--dry-runpreview now counts Zeek logs in datedzeekctllayouts (the
YYYY-MM-DD/andcurrent/subdirectories) the way a real run discovers them, and shows
(unreadable)for a directory it cannot read instead of a misleading(0 files). - A closed downstream pipe (for example
sigwood hunt | head) now exits quietly with
Unix SIGPIPE semantics instead of printing aBrokenPipeErrortraceback. - The loader no longer crashes at import on platforms without the POSIX
grp/pwd
modules; permission-denied diagnostics fall back to numeric owner and group ids there.
Security
- Output now strips terminal control bytes through a single sanitizer - including the
surrogate-escaped bytes a non-UTF-8 filename decodes to - so a hostile file or directory
name in a scanned tree can no longer inject terminal escape sequences, whether it reaches
the analyst through a stderr diagnostic or a text, html, or csv report. - That sanitizer now also covers the remaining command-line surfaces - the error boundary,
digestnarration, the--dry-runbanner, and loader and export status messages - so a
hostile file, directory, or configured path name cannot inject terminal escapes through
any of them.