Instructions to use mobility-model-zoo/picket-forest with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use mobility-model-zoo/picket-forest with Scikit-learn:
from huggingface_hub import hf_hub_download import joblib model = joblib.load( hf_hub_download("mobility-model-zoo/picket-forest", "sklearn_model.joblib") ) # only load pickle files from sources you trust # read more about it here https://skops.readthedocs.io/en/stable/persistence.html - Notebooks
- Google Colab
- Kaggle
- Picket (forest): CAN bus intrusion detection on a microcontroller
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
- Recipe: topics/security/tasks/can-ids.md
- Configuration: configs/security/can-ids/picket-forest.yaml
- Commit:
e6980dbcfd1d68e03d7bcd95dc6367beab41d2a6
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
- -
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-reported0.622
- attack-class F1 per frame, known vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)self-reported0.262
- false alarms per hour of the alarm rule, known vehicle known attack, median over 4 sets on can-ids-v3 (frozen)self-reported0.431
- false alarms per hour of the alarm rule, known vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)self-reported7.383
- attack-class F1 per frame, unknown vehicle known attack, median over 4 sets on can-ids-v3 (frozen)self-reported0.548
- attack-class F1 per frame, unknown vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)self-reported0.179
- false alarms per hour of the alarm rule, unknown vehicle known attack, median over 4 sets on can-ids-v3 (frozen)self-reported0.000
- false alarms per hour of the alarm rule, unknown vehicle known attack, worst over 4 sets on can-ids-v3 (frozen)self-reported0.687