Repository navigation
[0.10.0] - 2026-09-29
Security
-
Running the tests no longer touches the operator's own configuration, state, or CAN bus (#531). The suite depended on the machine it ran on. A single full run left two fuzz campaign directories in the real
~/.canarchy/findings(thirty-eight had accumulated on the machine where this was found), and with a~/.canarchy/config.tomlselectingudp_multicastand a default interface, nine tests failed and python-can opened a real multicast socket. The default transport backend ispython-canonsocketcan, so on a machine with a live interface an ordinarypytestrun could reach real hardware. Two autouse fixtures now apply by default: state is redirected to a per-test temporary directory with everyCANARCHY_*variable cleared, and any attempt to open a real CAN interface fails immediately, naming the test and the interface. python-can's in-processvirtualbus is still allowed, as is the localhost server the web tests bind. Tests that genuinely need the real environment are markedintegration, excluded from the default run, and selected with-m integration. Configuration-sensitivity is deliberately not hidden: theuds servicesregressions from #530 still set a configured default interface through their own fixtures, verified by reintroducing that defect and confirming they still fail under isolation. The suite now reports the same result with and without an operator config present. -
A configured default interface no longer turns the UDS service catalog into active bus probing (#530).
uds servicesis dual-mode: without an interface it lists the static UDS service catalog, with one it probes an ECU. The default-interface fallback was applied before that distinction was evaluated, so on any machine with[transport].default_interfaceorCANARCHY_DEFAULT_INTERFACEset, a barecanarchy uds services— and the MCPuds_servicestool, which has no interface and no acknowledgement parameter at all — transmitted 17 diagnostic requests to the configured bus instead of answering from the catalog. An agent asking for a reference lookup could not have authorised that and had no way to see it coming.uds servicesnow opts out of the default-interface fallback entirely: active probing requires an interface on the command line, the catalog is returned before any transport client is constructed, and the answer is the same under eitherCANARCHY_REQUIRE_ACTIVE_ACKsetting (previously the "safe" setting turned the wrong behaviour into an outright error). The MCP server additionally pins the exact argv that reference-only tools may produce and fails closed withREFERENCE_ONLY_TOOL_VIOLATIONrather than running anything else. Explicit CLI probing is unchanged. One visible consequence:uds services --dry-runwith no interface now plans againstinterface: nullrather than the configured default, which is what the corresponding real invocation would do. Sibling dual-mode commands were audited:xcp commands,j1587 pids, andj2497 midsare reference-only and outside the fallback set, andj1939 pgn/j1939 spnswitch on a capture file rather than an interface, so none shared the defect.
Added
-
Traffic investigation starts from a linked identifier explorer (#547). The TUI groups frames by bus and standard/extended CAN ID, showing count, recent rate, age, last payload, changed-byte offsets, and activity rather than making operators chase repeated frames through a scrolling log.
vopens the chronological log; selecting history freezes the complete raw hex/bit and decoded-evidence inspector without stopping capture, with visible incoming/loss counts andF4to return live. Filters for ID, PGN, and J1939 source address use the same frame grammar as CLIfilter(which now acceptssa==); invalid typed filters and unknown sort fields report an error instead of silently changing the view. History and memory are bounded by the backlog cap. -
DBC decode results appear in the Traffic explorer (#547). Decoded-message frames and correlated signals now populate the identifier summary and event log, so an offline
decode --dbcrun can be inspected without a separate raw-frame command. Live follow respects active traffic filters, returns to the newest visible event after historical inspection, and honors backlog increases as well as decreases. -
The TUI now uses responsive, task-focused workspaces (#546). Traffic, Signals, J1939, UDS, and Findings get the full body height one at a time instead of sharing a fixed four-pane grid.
Alt+1–Alt+5switch views,F3expands the compact activity band, and the selected row has a contextual inspector on wide terminals or anEnterdrill-down on narrow ones. First-run guidance points to live capture and the offline demo; workspace switches and resizes keep the selection, retained rows, and running capture. Leaving drill-down restores table keyboard focus, and widening the terminal returns to the side-by-side layout automatically. -
Synthetic datasets that work with no network access (#460). Every dataset in the
catalogprovider downloads from a host (zenodo.org, figshare.com, huggingface.co, ocslab.hksecurity.net) that a typical sandboxed agent environment blocks, so the documented dataset workflow could not run there at all — and failed late, as a transport error deep inside a replay. A secondofflineprovider now ships four synthetic datasets generated locally from protocol rules:can-basic(classic CAN with a discoverable counter and checksum),j1939-basic(J1939 with a BAM/TP sequence and a DM1 active fault),can-intrusion(attack-labelled intrusion-detection rows), andsignal-decoded(pre-decoded per-ID signal columns).datasets fetch offline:<name>writes real data into the cache and returns its path, after whichdatasets convert,datasets stream,stats,j1939 summary, andreplay --filework against a local file with no egress. Generation is deterministic, so a given dataset always produces the same bytes and provenance hashes stay stable across machines; nothing is downloaded and no third-party dataset content is redistributed. The data is synthetic and says so in its licence string, aSYNTHETIC:description prefix, and asynthetic: truemachine field — it is for exercising tooling, not for research findings. -
Review fixes for the offline provider (#460): the generated BAM sequence is addressed to the global destination
0xFFrather than node0x00, matching the0x20BAM control byte in its announcement, so protocol-aware consumers no longer see a self-contradictory session (generator version bumped tov2, which changes the cache path so no stale data is reused). Generated datasets are published atomically, so a write interrupted by a full disk cannot leave a truncated file that the next fetch serves as cached. A cache-write failure now exits 2 rather than 1, separating a storage problem from bad input. Anddatasets fetchon a provider that materialises data reportsdata_is_local: truewith next steps naming the local path — previously it said "Provenance only — no data was downloaded" and pointed atdatasets download/datasets replay, both of which fail for a generated dataset while the data sat in the cache. -
Sessions are reproducible research records (#504).
session savenow records input provenance — a SHA-256 hash, size, and modification time for every capture and DBC it names, the provider repo/commit/ref when a DBC came from the provider cache, the effective configuration, an environment snapshot, and the CANarchy version — alongside the context it always kept. Five new subcommands build on that:session verifyre-hashes every recorded input and artifact offline and reports what changed, what is missing, and what is needed to reproduce the analysis (exit 1 when degraded,--rootfor relocated inputs);session attachrecords an analysis output with its hash, the command that produced it, and the inputs it derives from (--embedkeeps the result inside the record);session annotateappends timestamped operator notes that can target recorded entries; andsession bundle/session importmove a whole session — manifest plus copies of its files — to another directory or machine without depending on the original absolute paths. Each is also exposed as an MCP tool. Sessions saved earlier keep loading unchanged and reportprovenance_available: falsewith an explicit warning naming the command that upgrades them; nothing in the session surface executes a recorded command, opens a transport, or serializes credentials.
Documentation
- Added a maintainer launch packet with ecosystem listing copy, a released-package demo without CAN hardware, MCP registry prerequisites, and search-engine submission instructions (#484).
Changed
-
Docs pages carry their own search and social metadata (#482). All 41 user-facing pages — getting started, the user guide, every tutorial, and every cookbook recipe — now set a unique
description:in front matter instead of inheriting one site-wide sentence, and Material'ssocialplugin renders a per-page card so a shared docs URL unfurls with that page's own title and summary. Internal design specs, test specs, and benchmark records stay published and searchable on the site but are markednoindexand dropped from the sitemap, which now lists 41 user-facing pages rather than ~140 mostly internal ones. The docs workflow also builds (without deploying) on pull requests, so a broken docs build is caught before it reaches the deploy path. -
Homepage is now static HTML and CSS (#478). The landing page previously shipped an empty
<div id="root">filled in by React development builds and an in-browser Babel compiler, so crawlers that do not execute JavaScript — and every social scraper — saw no page content, and visitors paid for a compile on every load. Headings, copy, comparison table, and links are now in the initial payload, client-side script is limited to the mobile menu and the install copy button, and the Pages build fails if that content or the no-framework guarantee regresses. The hero and shell release tags now read 0.9.2 instead of the stale 0.7.0.
Fixed
-
Decoded signals no longer appear twice in the TUI (#550). Parent
decoded_messageand childsignalevents now share a source-frame index, so the Signals pane displays one row per observation while preserving units and repeated equal-valued frames. The fold keeps the best available numeric timestamp for future trend views and conservatively retains legacy events without a correlation index instead of guessing from values. Standalone signal-event timestamp propagation remains tracked in #525. -
TUI commands no longer lose non-event answers (#521). Stats, DBC inspection, doctor, config, J1939 reference lookups, errors, help, and version text now open a scrollable, selectable result view instead of updating invisible fold state or printing behind the full-screen app.
F2switches between results and live panes,Escreturns to panes, and retained event rows and capture continue unchanged. Explicit JSON/JSONL commands remain available in the result view. The active-command guard still refuses transmit operations when--helpor--versionis used as a positional value after--. -
/stopin the TUI no longer acts on arguments it cannot parse (#542)._run_slashdiscarded whatever followed/stop, so/stop "ended a running capture instead of reporting the unmatched quote — a destructive action taken on exactly the input the documented contract says to refuse./stopnow takes no arguments: an unmatched quote, a dangling backslash, or an ordinary extra token is reported in the alerts log and the capture keeps running, while a bare/stopbehaves as before.REQ-TUI-15has been narrowed to the commands that actually tokenise (/capture,/stop);/filterand/sortintentionally take raw text so a filter needle can contain a quote character, and they validate their pane argument rather than tokenising. -
datasets provider listexplains an empty provider set instead of printing a bare heading (#543). With every provider disabled the command that exists to show the effective provider configuration rendered only its title — no providers, no search order, no reason. Text mode now reports(none registered),Search order: (none), and the[datasets.providers.<name>]key to re-enable one. JSON output is unchanged:providersandsearch_orderwere already empty lists and stay that way, with no human decoration added. -
MCP schema descriptions no longer tell agents that dataset refs and session names must be filesystem paths (#544). The
-restriction note added with the stdin guard opened with "Must be a real filesystem path", and it is appended to every restricted parameter — including ones that take a dataset ref, a remote URL, a manifest file id or a session name.datasets_replay_plan.sourcepublished as "Dataset ref (e.g. catalog:candid) or remote candump URL. Must be a real filesystem path", which would steer an agent away from the supported value. The note now states only that-is unavailable and why, leaving each parameter's own description to say what it accepts. Runtime behaviour is unchanged —-is still refused withSTDIN_MCP_EXCLUDED. -
Argument errors from the
canarchycommand now honour--jsonand--jsonl(#519). Every argparse failure from the installed console script printed the human-readablecommand:/error:/hint:block even when the caller explicitly asked for machine-readable output, so an agent needed one parser for runtime errors and a second for usage errors — the failures automation hits most often. The console script entersmain()with no argument list, and the requested output format was detected from that empty list rather than from the real command line, so the format was resolved as text before argparse ever looked atsys.argv; every in-process test passed an explicit argument list and could not see it. The effective argument vector is now resolved first and handed to both the format detection and the parser, so unrecognised options, unknown commands and subcommands, missing required arguments, and values that fail type conversion all return the canonicalINVALID_ARGUMENTSenvelope in the requested format, still exiting 1. Text output is unchanged for callers that do not ask for JSON, and successful command output is untouched. When several output flags appear together — itself a usage error, since they are mutually exclusive — the envelope reporting it is rendered by the fixed precedence--json>--jsonl>--text>--table, matching what successfully parsed commands already do. Regression coverage runs the installed console script as a real subprocess, because that is the only route that reproduces the defect. -
Opening
/helpin the TUI no longer erases the captured history (#517). The shared hotkey layer reported/helpand/clearwith the same "handled locally" disposition, and the full-screen app reset every pane on it — so asking for the hotkey table emptied the traffic, decoded, J1939, and UDS tables, dropped the retained row stores behind them, and posted a misleadingpanes clearedalert. Because the stores were discarded too, removing a filter could not bring the history back. The disposition now distinguishes a read-only handler from one that clears pane data, and only the clearing signal authorises a front end to discard its view state./helpleaves rows, row stores, counters, filters, sort state, and the live capture untouched, during capture and while the feed is paused;/clearand theckey still clear everything. -
A typo in a TUI slash command no longer kills the application (#518). Typing
/capture "raisedValueError: No closing quotationout of an unguardedshlex.splitand tore the full-screen app down with a traceback, losing the analysis view — the ordinary command path already caught this, the slash path did not. Slash arguments are now tokenised behind the same guard, so an unmatched quote or a dangling backslash is reported in the alerts log and the session keeps running./capturealso validates before it touches the capture session: it takes exactly one interface, and no argument, an empty or whitespace-only token (/capture "", which previously started a capture on nothing and announcedcapture started on), or extra arguments (/capture can0 --candump, previously silently ignored) are each refused with their own diagnostic. Nothing rejected reaches the capture path, so a running capture is neither stopped nor replaced and the displayed history is preserved. -
[datasets].search_ordernow actually decides which provider a dataset ref resolves against (#514). The key was parsed out of~/.canarchy/config.tomland then thrown away: the registry built its order from hard-coded registration order, so an operator who setsearch_ordergot no reordering, no warning, and no error. That was harmless whilecatalogwas the only provider; with theofflineprovider shipped there are two, and provider order is what a bare ref (road, noprovider:prefix) is resolved by — so the setting that was meant to control it silently did nothing. Providers are now registered in the configured order, and the effective order is reported bydatasets provider listas asearch_orderlist, a per-providerorderindex, and aSearch order:line in text mode. The default is unchanged and now pinned by a test that uses two providers sharing a dataset name:catalogcomes first, so a bare ref still prefers the real dataset over synthetic data. A provider that is enabled but not listed is appended after the listed ones rather than dropped —search_orderis a preference, not an allow-list, so a partial list never quietly makesoffline:refs unresolvable;[datasets.providers.<name>].enabled = falseremains the way to exclude one and still wins over listing it. An unknown provider name in the list now fails everydatasetscommand with a structuredDATASET_PROVIDER_NOT_FOUND(exit 1) naming the entry and the known providers, and asearch_orderthat is not a list of names fails withDATASET_SEARCH_ORDER_INVALID, both as JSON errors rather than tracebacks. -
An MCP file parameter set to
-no longer wedges the whole agent session (#516).-is a documented CLI sentinel meaning "read the capture from stdin", and the MCP server forwarded it straight through to the CLI. But the MCP server owns stdin as its JSON-RPC transport, so the reader consumed protocol bytes instead of capture text:capture_infowithfile: "-"never returned, and every later tool call in that session was starved behind the stolen stream. One schema-valid argument value silently killed the connection, with no error and no way for an agent to tell what had happened. The server now refuses-at the tool boundary with error codeSTDIN_MCP_EXCLUDED, on every parameter it can reach — capture and DBC paths, path arrays such ascompare.files, session artifacts and bundles, dataset replay inputs, and the fuzz/replay file inputs. The refusal is reported as an MCP tool failure and carries the canonical envelope with a hint naming what to pass instead, and because it happens before the CLI argv is built, no reader and no worker thread is ever created against the protocol stream. Each affected parameter's schema description now states the restriction, so an agent learns the rule fromlist_toolsrather than by hanging. The CLI pipelines are untouched:capture-info --file -,stats --file -,filter --file -, and the--stdinJSONL variants work exactly as before. -
SynCAN is no longer catalogued as MIT-licensed (#513).
datasets inspect syncanreported a permissive MIT licence, but the dataset ships bespoke ETAS GmbH / Robert Bosch GmbH terms that allow non-commercial use only, forbid redistribution of the dataset or modified versions, and require a citation in any work that uses it. Operators and agents acting on the old label could have re-hosted or commercialised data they have no licence to. The catalog entry now states the real terms and links the licence text, and a new test fails whenever a catalog entry advertises a permissive licence identifier that has not been checked against the publisher's own terms. -
TUI capture drain no longer crashes during teardown (#509). The 0.1s drain timer started at mount kept firing while the app exited, after the widget tree was gone, so a status refresh could raise
NoMatchesfrom an unhandled timer callback. The drain now stops once the app is shutting down, and the status, J1939 ribbon, and alert helpers tolerate an absent node. -
Guided-fuzz review fixes (#503). Raw adapter I/O failures retain transport diagnostics rather than being reported as disk errors. Non-finite rate/duration values are rejected as usage errors before archive creation in active and dry-run modes.
-
Guided fuzz findings survive corpus pruning and interrupted runs (#503). Active campaigns now save exact payloads, responses, lineage, and campaign settings incrementally in unique evidence directories.
--findings-dir/ MCPfindings_dirselects the root; failures report archive location and stop further sends. Existing corpus files remain compatible, and dry-run creates no archive. -
RE signal-name suggestions use exact Intel/Motorola bit overlap (#502). Unaligned Motorola signals retain valid matches across byte boundaries, and disjoint fields sharing a byte no longer produce false DBC matches. Confidence uses the actual overlapping-bit fraction; output fields are unchanged.
-
Capture comparisons preserve standard/extended identifier types (#500). Messages sharing a numeric CAN ID now retain independent metrics and new/dropped flags. Rows expose
is_extended_id, and category summaries add typed*_identifierslists while retaining numeric*_idslists for compatibility. -
TUI live capture now stops promptly on idle buses, preserves completed and paused capture buffers until they are drained, blocks replacement of a worker that has not stopped, and exposes queue-loss telemetry instead of silently dropping events (#498).