Picket (forest): CAN bus intrusion detection on a microcontroller

Version 0.2.0 · 2026-10-10 · status: experimental · topic: Automotive security · task: can-ids

Usage: Commercial use permitted · licence Apache-2.0.

Experimental. The output format may change in a minor version before 1.0.0.

Summary

Experimental, not ready for production use. A CAN intrusion detector that is first calibrated on benign traffic of the target vehicle, then flags classic CAN frames whose identifier is sent more often than calibrated, bursts of unknown identifiers and DLC changes, and raises alarms; a random forest score on the same features is reported as additional information. It runs as plain C on ESP32-class microcontrollers (16.5 KiB flash, 11.9 KiB RAM on an ESP32 in QEMU); C and Python give identical results.

picket-forest belongs to the topic Automotive security (security), task can-ids, variant forest. Trained from scratch.

Intended use

Research, teaching and bench testing of in-vehicle intrusion detection on classic CAN; a reproducible baseline for calibrated, microcontroller-sized CAN intrusion detection; a starting point for a security sensor that reports events to an IDS manager. Status: experimental, not ready for production use (see limitations).

Out-of-scope use

Deployment in vehicles on public roads: the model is not a validated or approved safety or security mechanism and makes no ISO/SAE 21434 or UNECE R155 claim; no safety-critical use without a vehicle-specific validation. Active countermeasures (blocking or injecting frames) based on its output. CAN FD and automotive Ethernet.

Input and output

Calibration (host, once per vehicle): benign candump logs of the vehicle -> picket_forest_table.h (security can-ids v2 table); cover driving, idle and parked phases, at least 60 minutes. Input (device): one classic CAN frame at a time (timestamp in microseconds, 11-bit identifier, DLC, up to 8 data bytes). Output: the rules decision per frame (count above the calibrated maximum by more than 25 %, unknown-identifier burst, DLC change), an alarm flag from the alarm stage (3 flagged frames within 200 ms, then 1 s hold-off) and the forest score (share of trees voting attack) as information.

How to run it

pip install "mobility-model-zoo[edge] @ git+https://github.com/mhabedank/mobility-model-zoo@picket-forest/v0.2.0"
import joblib
from huggingface_hub import hf_hub_download

from mobility_model_zoo.security.can_ids import calibrated as cal
from mobility_model_zoo.security.can_ids.release_export_v2 import reference

saved = joblib.load(hf_hub_download("mobility-model-zoo/picket-forest", "model.joblib", revision="v0.2.0"))
table = cal.calibrate(benign_captures)  # dicts with ts (s), can_id, dlc, data (n x 8), y (zeros)
scores, alarms = reference(capture, table, saved["model"], saved["threshold"])

On the device (C, tree model exported with emlearn; build with -ffp-contract=off):

/* Headers from mobility-model-zoo/picket-forest at v0.2.0; replace picket_forest_table.h with your vehicle's table. */
#include "picket_forest_v2.h"

static picket_forest_v2_t ids;
picket_forest_v2_init(&ids);
float score;
int alarm = picket_forest_v2_process(&ids, ts_us, can_id, dlc, data, &score, NULL);

The tag v0.2.0 always points to this version. For strict reproducibility, pin the commit hash of that tag instead (revision="<commit>").

Examples

Example 1: 01-fabrication.json

Source: synthetic (fabrication attack). 3000 CAN frames in bus order; the detector raises 1 alarm, at frame 1155. First five frames:

Time (µs) ID DLC Data
0 0x10E 8 00 20 10 10 00 00 0E 4E
3295 0x11C 8 00 81 40 10 00 00 1C ED
3455 0x505 8 00 84 42 10 00 00 05 DB
4514 0x34D 8 00 10 08 10 00 00 4D 75
6487 0x60F 8 00 0A 05 10 00 00 0F 2E

The full example with the expected score of every frame is examples/01-fabrication.json in the repository; the release gate reproduces every score and alarm bit-exactly with the C code of this release.

Example 2: 02-dos.json

Source: synthetic (dos attack). 3000 CAN frames in bus order; the detector raises 2 alarms, at frames 1151, 2930. First five frames:

Time (µs) ID DLC Data
0 0x10E 8 00 20 10 10 00 00 0E 4E
3295 0x11C 8 00 81 40 10 00 00 1C ED
3455 0x505 8 00 84 42 10 00 00 05 DB
4514 0x34D 8 00 10 08 10 00 00 4D 75
6487 0x60F 8 00 0A 05 10 00 00 0F 2E

The full example with the expected score of every frame is examples/02-dos.json in the repository; the release gate reproduces every score and alarm bit-exactly with the C code of this release.

Example 3: 03-fuzzing.json

Source: synthetic (fuzzing attack). 3000 CAN frames in bus order; the detector raises 3 alarms, at frames 1152, 2131, 2581. First five frames:

Time (µs) ID DLC Data
0 0x10E 8 00 20 10 10 00 00 0E 4E
3295 0x11C 8 00 81 40 10 00 00 1C ED
3455 0x505 8 00 84 42 10 00 00 05 DB
4514 0x34D 8 00 10 08 10 00 00 4D 75
6487 0x60F 8 00 0A 05 10 00 00 0F 2E

The full example with the expected score of every frame is examples/03-fuzzing.json in the repository; the release gate reproduces every score and alarm bit-exactly with the C code of this release.

Quality

Quality is measured against the labels of the datasets named below, on test data not used for training. Reference: labels of the can-train-and-test and ROAD datasets; KCID benign traces for false alarms. Benchmark: can-ids-v3.

Metric Value What it measures Reference Benchmark Items Date
f1_known_vehicle_known_attack_median 0.6216 attack-class F1 per frame, known vehicle known attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_known_vehicle_known_attack_worst 0.2616 attack-class F1 per frame, known vehicle known attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_known_vehicle_known_attack_median 0.4305 false alarms per hour of the alarm rule, known vehicle known attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_known_vehicle_known_attack_worst 7.383 false alarms per hour of the alarm rule, known vehicle known attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_unknown_vehicle_known_attack_median 0.5482 attack-class F1 per frame, unknown vehicle known attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_unknown_vehicle_known_attack_worst 0.1792 attack-class F1 per frame, unknown vehicle known attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_unknown_vehicle_known_attack_median 0 false alarms per hour of the alarm rule, unknown vehicle known attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_unknown_vehicle_known_attack_worst 0.687 false alarms per hour of the alarm rule, unknown vehicle known attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_known_vehicle_unknown_attack_median 0.4927 attack-class F1 per frame, known vehicle unknown attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_known_vehicle_unknown_attack_worst 0.1466 attack-class F1 per frame, known vehicle unknown attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_known_vehicle_unknown_attack_median 0 false alarms per hour of the alarm rule, known vehicle unknown attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_known_vehicle_unknown_attack_worst 7.857 false alarms per hour of the alarm rule, known vehicle unknown attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_unknown_vehicle_unknown_attack_median 0.7689 attack-class F1 per frame, unknown vehicle unknown attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_unknown_vehicle_unknown_attack_worst 0.2616 attack-class F1 per frame, unknown vehicle unknown attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_unknown_vehicle_unknown_attack_median 0 false alarms per hour of the alarm rule, unknown vehicle unknown attack, median over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
fa_per_hour_default_unknown_vehicle_unknown_attack_worst 0.861 false alarms per hour of the alarm rule, unknown vehicle unknown attack, worst over 4 sets can-train-and-test attack labels (official splits) can-ids-v3 4 2026-10-11
f1_road 0.9968 attack-class F1 per frame on ROAD (fabrication, fuzzing) ROAD injection labels can-ids-v3 5 2026-10-11
fa_per_hour_road_ambient 0 false alarms per hour on the ROAD test ambient captures ROAD ambient captures (no attacks) can-ids-v3 4 2026-10-11
fa_per_hour_kcid_ford_focus 0 false alarms per hour on 4.9 h of benign Ford Focus traffic (other drivers and days), calibration 15 min; vehicle in no training data KCID benign traces (no attacks) kcid 6 2026-10-11
fa_per_hour_kcid_honda_cr_v 0 false alarms per hour on 48.0 h of benign Honda CR-V traffic (other drivers and days), calibration 15 min; vehicle in no training data KCID benign traces (no attacks) kcid 2 2026-10-11
fa_per_hour_kcid_traverse_obd 0 false alarms per hour on 22.0 h of Traverse traffic with OBD requests, calibration 60 min including the diagnostic IDs KCID benign traces (no attacks) kcid 23 2026-10-11
forest_score_auroc_road 0.9565 AUROC of the forest score (information output, not the decision) on ROAD (fabrication, fuzzing) ROAD injection labels can-ids-v3 5 2026-10-11

Speed and memory

Budget: 16 KB RAM, 128 KB flash on ESP32-S3.

Metric Value Unit What it measures Measured on Items Date
latency_us_host 0.954 us whole chain per frame, host build incl. file I/O host arm64 (Darwin) (host) 200000 2026-10-11
latency_us_esp32_qemu 25 us whole chain per frame, median (emulator: not representative) ESP32 in Espressif QEMU, 240 MHz nominal (emulator) 4000 2026-10-11
latency_p99_us_esp32_qemu 58 us whole chain per frame, 99th percentile (emulator: not representative) ESP32 in Espressif QEMU, 240 MHz nominal (emulator) 4000 2026-10-11
flash_kb_esp32_qemu 16.5 KiB detector code and constants (features, forest, alarm stage), from the object files ESP32 in Espressif QEMU, 240 MHz nominal (emulator) None 2026-10-11
ram_kb_esp32_qemu 11.94 KiB detector static data plus the detector state ESP32 in Espressif QEMU, 240 MHz nominal (emulator) None 2026-10-11
latency_us 25 us whole chain per frame, median (emulator: not representative) ESP32 in Espressif QEMU, 240 MHz nominal (emulator) 4000 2026-10-11
latency_p99_us 58 us whole chain per frame, 99th percentile (emulator: not representative) ESP32 in Espressif QEMU, 240 MHz nominal (emulator) 4000 2026-10-11
flash_kb 16.5 KiB detector code and constants (features, forest, alarm stage), from the object files ESP32 in Espressif QEMU, 240 MHz nominal (emulator) None 2026-10-11
ram_kb 11.94 KiB detector static data plus the detector state ESP32 in Espressif QEMU, 240 MHz nominal (emulator) None 2026-10-11

Limitations and risks

  • Not ready for production use: release status experimental (constitution 2.2.0).
  • False alarms depend on the calibration: on ROAD's test drives 0 per hour with 142 min of calibration, about 15 per hour with 60 min, 87 with 15 min and 140 with 5 min. A calibration must cover the vehicle's operating modes (driving, idle, parked, diagnostics).
  • One new legitimate identifier after calibration floods alarms: on the AutoHack C-CAN bus one identifier missing from the calibration caused several hundred false alarms per hour; a diagnostic dongle plugged in after calibration causes about 1,100 per hour. After a fuzzing burst the unknown-identifier state can keep alarming for up to 2 s (see example 03).
  • The rules decide. The trained forest is reported as information only: on unseen vehicles it raised up to 19.6 false alarms per hour and missed injected fabrication attacks that the rules catch.
  • The shuffled-label control failed its pre-registered rule (the calibrated features are themselves anomaly signals); the test captures of can-train-and-test and ROAD were scored several times during development (all logged in the repository, feature 010).
  • Evidence on unseen vehicles: can-train-and-test unknown-vehicle splits (at most 0.86 false alarms per hour), KCID Ford Focus 4.9 h and Traverse 22 h without an alarm (the KCID Honda CR-V log shows only 6 identifiers behind a gateway and is weak evidence). Device numbers come from an emulator (ESP32 in QEMU); latency there is not representative.
  • Masquerade (a silenced ECU replaced at its normal timing) is not detected. Classic CAN only; no CAN FD.
  • No adversarial evaluation: an attacker who knows the rules can stay below the calibrated counts. The code, features and thresholds are public; do not rely on it as the only line of defence.
  • Calibration is done on the host and compiled into the device; on-device calibration is not implemented.

Training data and attribution

picket-forest 0.2.0 was trained on 0 published documents. Each is credited below with title, creators, source and licence. All of them were modified in the same way: The training texts are not published or redistributed; see the copyright policy.

Title Creators Source Licence

Teacher and labeling models

Route Model Hosting provider Region Role Terms checked

Dual-use considerations

Research, teaching and bench testing of intrusion detection on classic CAN buses: after a calibration on benign traffic of one vehicle, the detector flags frames that exceed the calibrated frame counts, unknown-identifier bursts (fuzzing) and DLC changes, and raises alarms for an IDS manager or a log. Status: experimental, not ready for production use. The model detects anomalies; it is not a safety mechanism and was not developed under ISO/SAE 21434 or ISO 26262. Do not use it to block or alter vehicle functions.

Privacy and personal data

The training texts were collected under the copyright and privacy rules of the project: opt-out signals were honoured, personal identifiers such as e-mail addresses, phone numbers and user handles were removed before labeling, and special categories of personal data were excluded except where a recorded decision allows them. The model labels passages of the text you give it and does not return stored training text. How texts are processed, who received them and how to object: privacy notice. AI Act classification: picket-forest 0.2.0. Contact: privacy@miskatonic-analytics.com.

Training recipe

Version history

Version Date Status Change Changes f1_known_vehicle_known_attack_median f1_known_vehicle_known_attack_worst fa_per_hour_default_known_vehicle_known_attack_median
0.2.0 2026-10-10 experimental initial Calibrate on the vehicle, then detect. A per-vehicle table learned on the host from benign traffic; 10 features relative to it (frame counts per identifier in 8 time buckets, unknown-identifier counts, DLC, payload changes), computed by the C library msml_cal on host and device alike. Rules on these features decide (count above the calibrated maximum by more than 25 %, unknown-identifier burst, DLC change), then the alarm stage; the forest score is published as information (owner decision 2026-10-11). can-ids-v3: ROAD F1 0.997, 0 false alarms per hour on the ROAD test drives, at most 0.86 per hour on unknown vehicles; KCID Ford Focus and Traverse 0 alarms in 26.9 h. Experimental: calibration-dependent false alarms, new identifiers flood alarms, emulator-only device evidence. 0.6216 0.2616 0.4305

License

Apache-2.0 Commercial use permitted · licence Apache-2.0.

Citation

@misc{mobility-model-zoo-picket-forest,
  title  = {Picket (forest): CAN bus intrusion detection on a microcontroller, mobility-model-zoo},
  author = {Habedank, Martin},
  year   = {2026},
  url    = {https://huggingface.co/mobility-model-zoo/picket-forest}
}

About the zoo

Part of mobility-model-zoo, a collection of small, fast mobility models grouped by topic. Topic collection: Automotive security. Source code, recipes and release records: https://github.com/mhabedank/mobility-model-zoo.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including mobility-model-zoo/picket-forest

Evaluation results

  • attack-class F1 per frame, known vehicle known attack, median over 4 sets on can-ids-v3 (frozen)
    self-reported
    0.622
  • attack-class F1 per frame, known vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)
    self-reported
    0.262
  • false alarms per hour of the alarm rule, known vehicle known attack, median over 4 sets on can-ids-v3 (frozen)
    self-reported
    0.431
  • false alarms per hour of the alarm rule, known vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)
    self-reported
    7.383
  • attack-class F1 per frame, unknown vehicle known attack, median over 4 sets on can-ids-v3 (frozen)
    self-reported
    0.548
  • attack-class F1 per frame, unknown vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)
    self-reported
    0.179
  • false alarms per hour of the alarm rule, unknown vehicle known attack, median over 4 sets on can-ids-v3 (frozen)
    self-reported
    0.000
  • false alarms per hour of the alarm rule, unknown vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)
    self-reported
    0.687