stamparm/maltrail · error
[!] not built - cargo build --release --manifest-path…
Error message
[!] %s not built - cargo build --release --manifest-path sensor/Cargo.toml
What it means
refnet.py's replay() drives the compiled sensor binary (default sensor/target/release/maltrail-sensor) against a generated pcap. Before running, it checks the binary exists; if it doesn't, replay exits telling you the exact cargo command to build it. This is a precondition check for the replay/scoring workflow, not a failure of replay itself.
Solutions
- Build the sensor: cargo build --release --manifest-path sensor/Cargo.toml
- If a custom path was passed, verify the file exists there or omit --sensor to use the default target/release path
- Re-run `cargo clean` victims by rebuilding after any clean or toolchain switch
- In CI, ensure the build step precedes the replay step
Example fix
# before python sensor/tools/refnet.py replay ... # binary missing # after cargo build --release --manifest-path sensor/Cargo.toml python sensor/tools/refnet.py replay ...
Defensive patterns
Strategy: validation
Validate before calling
import os sensor = "sensor/target/release/maltrail-sensor" assert os.path.isfile(sensor), "run: cargo build --release --manifest-path sensor/Cargo.toml"
Prevention
- Build the sensor before running refnet replay
- In CI, make build a hard prerequisite of replay/scoring steps
- Avoid cargo clean right before replay runs
When it happens
Trigger: Running refnet.py replay (via main, without --score) when sensor/target/release/maltrail-sensor does not exist — the project was never built with cargo build --release, a `cargo clean` was run, or a custom --sensor path points to a non-existent file.
Common situations: Fresh clone without building; switching toolchains/target dirs; CI running the replay step before the build step; typos in a custom sensor path.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- [!] no VERSION constant found in
- [!] no maltrail-sensor package entry found in
- [x] invalid IP address
- not a Maltrail provenance sidecar (bad magic)
- provenance sidecar is truncated
AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13).
Data as JSON: /api/errors/d8466620f727cc72.
Report an issue: GitHub.
Appendix: source
Thrown at sensor/tools/refnet.py:224
false = [_ for _ in seen if _ not in planted and _ not in explained and _[0] not in scanners]
hours = meta["minutes"] / 60.0
per_1000_day = (len(logged) / max(hours, 1e-9)) * 24.0 * (1000.0 / meta["hosts"])
return {
"planted": len(planted), "detected": len(detected), "missed": sorted(missed)[:10],
"scans_planted": len(scanners), "scans_detected": len(scans_found),
"false_positives": len(false), "false_examples": sorted(false)[:10],
"benign_packets": meta["benign_packets"], "events": len(logged),
"detection_rate": 100.0 * len(detected) / max(len(planted), 1),
"fp_per_100k_benign": 100000.0 * len(false) / max(meta["benign_packets"], 1),
"events_per_day_per_1000_hosts": per_1000_day,
}
def replay(pcap, out, trails, sensor=None):
sensor = sensor or os.path.join(ROOT, "sensor", "target", "release", "maltrail-sensor")
if not os.path.isfile(sensor):
raise SystemExit("[!] %s not built - cargo build --release --manifest-path sensor/Cargo.toml" % sensor)
logdir = os.path.join(out, "logs")
if not os.path.isdir(logdir):
os.makedirs(logdir)
conf = os.path.join(out, "sensor.conf")
with io.open(conf, "w", encoding="utf8") as handle:
# USE_HEURISTICS EXPLICITLY. An absent key reads as false, so a config that simply omits it
# runs with every heuristic off - and the first version of this file did exactly that, then
# reported 0/3 scans detected as though the sensor had missed them. A reference network that
# forgets to turn on what it is measuring produces confident, meaningless numbers.
handle.write(u"MONITOR_INTERFACE any\nCAPTURE_BUFFER 10MB\nSENSOR_NAME refnet\n"
u"USE_HEURISTICS true\n"
u"DISABLE_CHECK_SUDO true\nDISABLE_TRAIL_UPDATES true\nUSE_SERVER_UPDATE_TRAILS false\n"
u"UPDATE_PERIOD 86400\nLOG_DIR %s\nTRAILS_FILE %s\n" % (logdir, trails))
started = time.time()
subprocess.check_call([sensor, "-c", conf, "-r", pcap], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return logdir, time.time() - started
View on GitHub (pinned to 77cfb06d76)