Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

### Added

* **SEO and link-preview metadata for the published site (#479, #480, #483).** The homepage now ships Open Graph and Twitter Card tags with a generated 1200×630 brand card (`src/homepage/og-card.png`), a canonical URL, an inline SVG favicon, a `SoftwareApplication` JSON-LD block, and a keyword-forward `<title>`. The Pages build (`scripts/build_pages_site.sh`) additionally emits a root `robots.txt` referencing both the root and docs sitemaps and a root `sitemap.xml` covering the homepage, and publishes the social card. PyPI keywords in `pyproject.toml` expanded from 6 to 19 discovery terms. Closes #479, #480, #483.

* **`canarchy tui` is now a full-screen Textual app that streams the CAN bus live.** The TUI was a line-oriented shell that reprinted six panes after each command finished — it had no live/async streaming (`/capture` ran to completion then dumped a batch), no interactivity (no pane focus, filter, sort, row navigation, or scrollback), and hard-coded tiny backlogs. It is now a full-screen [Textual](https://textual.textualize.io/) application whose headline feature is watching the bus in real time. The engine/view split is preserved: the deterministic fold layer in `src/canarchy/tui.py` (`TuiState`, `_update_state`, and per-pane row extractors) stays as the tested model, a new `src/canarchy/tui_app.py` consumes it as the view, and a new `src/canarchy/tui_capture.py` `CaptureSession` streams frames through the existing `LocalTransport.capture_stream_events` generator on a daemon thread + queue (mirroring the transport gateway pattern), drained on the UI loop so one frame at a time flows through the same folding logic batch results use. Panes (Bus Status, Live Traffic, Decoded Signals, J1939 summary ribbon + table, UDS, an append-only Alerts log) are interactive Textual widgets: `/capture <iface>` starts a background live capture and `/stop` (or the `x` key) ends it; `/filter <pane> [text]` and `/sort <pane> [column]` filter and sort a pane; arrow keys navigate rows; `[`/`]` resize the operator-controlled backlog; `space` pauses the live feed; `c` clears; `ctrl+f` maximizes the focused pane. The command entry still runs real CANarchy commands and slash hotkeys through the shared parser, and the nested-front-end guard (`TUI_COMMAND_UNSUPPORTED`) is retained. `textual` is now a core dependency. **Breaking:** the interactive-only rewrite retires the text-mode shell and the one-shot `tui --command` mode; the TUI requires an interactive terminal and a non-TTY invocation prints guidance and exits non-zero — use the individual commands for scripted/automation runs. New Pilot-based app tests (`tests/test_tui_app.py`) and `CaptureSession` unit tests (`tests/test_tui_capture.py`) drive the scaffold backend's deterministic frame stream; the fold-layer snapshot tests are unchanged. A remaining follow-up: a finite-timeout `capture_stream` loop so live capture stops instantly on real hardware.
* **`datasets convert`/`stream` gain a `decoded-signal-csv` source format for pre-decoded per-ID signal CSVs like SynCAN (#461).** `datasets convert`/`stream` only understood raw arbitration-ID + payload-byte sources (`hcrl-csv`, `candump`, `comma-rlog`), but a meaningful class of public CAN intrusion-detection/ML datasets — SynCAN being a well-known MIT-licensed example — publish data as pre-decoded, normalized per-ID signal values (`Label,Time,ID,Signal1_of_ID,...`) rather than frames. Ingesting them previously meant hand-rolling a one-off byte-packing script. A new `--source-format decoded-signal-csv` (also a `datasets_convert` MCP enum value) handles this shape: it auto-detects the `Time`/`ID`/optional `Label` columns plus any per-ID signal columns, maps each row's `ID` token to a deterministic arbitration ID (`0x`-hex, plain decimal, a trailing integer like `id10` -> `10`, else a stable SHA-1-derived 11-bit ID), and packs the present normalized signal values into payload bytes as big-endian uint16 (`round(value * 0xFFFF)`, clamped), skipping empty signal cells. Real `Time` and `Label` are preserved — `Label` surfaces as `payload.label` in JSONL output, like `hcrl-csv`. The reconstructed payload bytes are synthetic (derived from normalized signal values, not captured verbatim), so the output suits frame-level analysis (`stats`, `re_*`, `compare`) with real timing/ID structure rather than byte-exact ECU replay; this is documented inline. The adapter (`src/canarchy/dataset_convert.py`) is pure and streaming, with new unit/CLI tests (`tests/test_dataset_provider.py`) over a SynCAN-style fixture and updated command/design specs. Closes #461.
* **`fuzz identify` — narrow a fuzz log to the culprit frame by replaying bisected windows (#464).** After a fuzz campaign or a captured anomaly, an operator knows *that* some frame in a log caused an effect but not *which* one; CANarchy had replay/fuzz/guided-fuzz primitives but no explicit narrowing workflow (CaringCaribou's `fuzzer identify`). A new `canarchy fuzz identify <log>` loads a candump capture or a JSONL fuzz log / replay plan and bisects toward the culprit: each round replays the lower half of the current candidate range, and the operator records whether the effect reproduced. Observations are supplied non-interactively via `--observe effect|no-effect` (repeatable) and/or a JSON `--observations` file, so the workflow stays automation-friendly with no hidden prompts. The narrowing engine (`src/canarchy/fuzz_identify.py`) is pure and deterministic — given the frame count and observations it returns the candidate range, the next window, and a resolved culprit in `ceil(log2(N))` rounds — so it is exhaustively unit-tested without hardware. One round runs per invocation: with an `--interface` (and without `--dry-run`) it replays the next window live behind the active-transmit safety model (`stderr` preflight, `--ack-active`) and reports the state so the operator records an observation and re-invokes; with a complete observation sequence up front it resolves the culprit with no transmission. JSON output includes the replayed/next window, the recorded observations, the candidate range and frames, the resolved culprit, a `confidence` value and `rationale`, and provenance (`source`, `frame_count`, `planned_rounds`). `--max-window` refuses to replay an over-large window (`FUZZ_IDENTIFY_WINDOW_TOO_LARGE`); invalid logs/observations return structured errors (exit 1). `fuzz identify` is a stateful, multi-round human-in-the-loop workflow and stays CLI-only (a documented MCP exclusion). New module + unit/CLI tests (`tests/test_fuzz_identify.py`) and design/test specs (`fuzz-identify.md`). Closes #464.
Expand Down
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ description = "CLI-first CAN security research toolkit"
readme = "README.md"
requires-python = ">=3.12"
license = "GPL-3.0-or-later"
keywords = ["can", "can-bus", "j1939", "uds", "security", "cli"]
keywords = [
"can", "can-bus", "canfd", "j1939", "uds", "doip", "iso-tp",
"automotive", "automotive-security", "car-hacking", "vehicle",
"socketcan", "candump", "dbc",
"fuzzing", "reverse-engineering", "security", "cli", "mcp",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
Expand Down
26 changes: 26 additions & 0 deletions scripts/build_pages_site.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,32 @@ uv run mkdocs build --strict

cp "$repo_root/src/homepage/index.html" "$site_root/index.html"
cp "$repo_root/src/homepage/site-brutalist.jsx" "$site_root/site-brutalist.jsx"
cp "$repo_root/src/homepage/og-card.png" "$site_root/og-card.png"

site_url="https://hexsecs.github.io/canarchy"

# On a GitHub *project* page this file is served from /canarchy/robots.txt,
# which crawlers do not treat as authoritative (only the host root counts),
# so the sitemaps below must also be submitted directly to search engines
# (e.g. Google Search Console / Bing Webmaster Tools). The file still serves
# SEO tooling that probes it and becomes authoritative if the site ever
# moves to a custom domain served from the root.
cat > "$site_root/robots.txt" <<EOF

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Publish crawler hints somewhere bots can read them

In the Pages workflow I checked, the site artifact is published at https://hexsecs.github.io/canarchy/, so this generated file is served as /canarchy/robots.txt. Crawlers only treat robots.txt as valid at the protocol/host root (/robots.txt; Google documents subdirectory robots files as invalid: https://developers.google.com/crawling/docs/robots-txt/robots-txt-spec), so the Sitemap: directives here will not be discovered from robots.txt for this project site. Unless the sitemap is submitted or linked another way, the crawler signposting added for the published site remains ineffective.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — you're right that /canarchy/robots.txt isn't authoritative for a project page, so its Sitemap: directives won't be auto-discovered. I've kept the file (it still serves SEO tooling that probes it, and it becomes authoritative if the site ever moves to a custom domain at the host root) but documented the limitation in the build script, and I'm adding "submit the sitemaps directly to Google Search Console / Bing Webmaster Tools" to the reach tracking issue (#484) since that's the discovery path that actually works here. The homepage's og: tags and JSON-LD are unaffected by this.


Generated by Claude Code

User-agent: *
Allow: /

Sitemap: $site_url/sitemap.xml
Sitemap: $site_url/docs/sitemap.xml
EOF

cat > "$site_root/sitemap.xml" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>$site_url/</loc>
</url>
</urlset>
EOF

if [[ ! -f "$site_root/index.html" ]]; then
echo "missing published homepage: $site_root/index.html" >&2
Expand Down
37 changes: 35 additions & 2 deletions src/homepage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,41 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>CANarchy — CAN bus security for the humans and agents</title>
<meta name="description" content="CANarchy is a stream-first CAN and J1939 runtime for security research, red teams, and agent-driven automation. One schema, many doors, capture everything." />
<title>CANarchy — CAN bus &amp; J1939 security research toolkit (CLI, TUI, MCP)</title>
<meta name="description" content="CANarchy is a stream-first CAN and J1939 toolkit for security research, red teams, and AI agents. Typed JSONL events, TUI, fuzzing, DBC workflows, and a built-in MCP server." />
<link rel="canonical" href="https://hexsecs.github.io/canarchy/" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='%230b0b0b'/%3E%3Crect x='6' y='6' width='52' height='52' fill='%23ffd400'/%3E%3Ctext x='32' y='40' text-anchor='middle' font-family='sans-serif' font-size='26' font-weight='900' fill='%230b0b0b'%3EC/A%3C/text%3E%3C/svg%3E" />

<meta property="og:type" content="website" />
<meta property="og:site_name" content="CANarchy" />
<meta property="og:title" content="CANarchy — CAN bus &amp; J1939 security research toolkit" />
<meta property="og:description" content="Stream-first CAN and J1939 toolkit for security research and AI agents. Typed JSONL events, TUI, fuzzing, DBC workflows, and a built-in MCP server." />
<meta property="og:url" content="https://hexsecs.github.io/canarchy/" />
<meta property="og:image" content="https://hexsecs.github.io/canarchy/og-card.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="CANarchy — stream-first CAN and J1939 toolkit for security research and AI agents" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="CANarchy — CAN bus &amp; J1939 security research toolkit" />
<meta name="twitter:description" content="Stream-first CAN and J1939 toolkit for security research and AI agents. Typed JSONL events, TUI, fuzzing, DBC workflows, and a built-in MCP server." />
<meta name="twitter:image" content="https://hexsecs.github.io/canarchy/og-card.png" />

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "CANarchy",
"description": "Stream-first CAN and J1939 toolkit for security research and AI agents. Every command emits typed, machine-readable events; includes a TUI, fuzzing, DBC workflows, and a built-in MCP server.",
"url": "https://hexsecs.github.io/canarchy/",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Linux, macOS, Windows",
"license": "https://www.gnu.org/licenses/gpl-3.0.html",
"downloadUrl": "https://pypi.org/project/canarchy/",
"softwareHelp": "https://hexsecs.github.io/canarchy/docs/",
"sameAs": ["https://github.com/hexsecs/canarchy", "https://pypi.org/project/canarchy/"],
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
}
</script>

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
Expand Down
Binary file added src/homepage/og-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.