stamparm/maltrail · warning

trail update unavailable

Error message

trail update unavailable: {reason}

What it means

The trail update subsystem returned Outcome::Unavailable(reason): Maltrail's own updater (core/update.py) is absent or cannot be used, so the Rust sensor cannot refresh the trails file. It logs "trail update unavailable: {reason}" and, via warn_if_trails_are_stale, flags a trails file older than one update period — IOCs added after the file was written will not be detected.

Solutions

  1. Install or restore core/update.py alongside the sensor so Maltrail's own updater is available.
  2. Re-run the deployment from a full Maltrail checkout/package including the core directory.
  3. Manually refresh the trails file (download from upstream) and restart the sensor as a stopgap.
  4. Keep warn_if_trails_are_stale alerts visible and monitor trails file mtime until the updater is restored.
Defensive patterns

Strategy: fallback

Validate before calling

import os
if not os.path.exists('core/update.py'):
    print('core/update.py missing: trail refresh will be unavailable')

Type guard

fn updater_available(core_dir: &Path) -> bool { core_dir.join("update.py").is_file() }

Try / catch

match refresh_trails().await {
    Outcome::Unavailable(reason) => { log_unavailable(&reason); manual_trails_refresh(); }
    _ => {}
}

Prevention

When it happens

Trigger: refresh_trails in run() receives trailupdate::Outcome::Unavailable(reason), typically when core/update.py does not exist next to the sensor installation, or the update mechanism cannot be invoked for the configured cfg.trails_file.

Common situations: Deploying only the Rust sensor binary without the Maltrail Python core directory; installing from a package that omits core/update.py; moving the binary away from the repository checkout so the updater path no longer resolves.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/c3aeecd2a0a125b6. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/main.rs:1006

            }
        }
        trailupdate::Outcome::Disabled => {
            if !quiet && startup {
                cprintln!(
                    "[i] trail updates disabled ('DISABLE_TRAIL_UPDATES'); using '{}' as-is",
                    cfg.trails_file.display()
                );
                warn_if_trails_are_stale(cfg);
            }
        }
        trailupdate::Outcome::Unavailable(reason) => {
            ceprintln!("[!] cannot update trails: {reason}");
            ceprintln!(
                "[?] the sensor needs Maltrail's own updater (core/update.py) to refresh '{}'; \
                 without it, IOCs added since that file was written are NOT detected",
                cfg.trails_file.display()
            );
            output::log_error(&format!("trail update unavailable: {reason}"), true);
            warn_if_trails_are_stale(cfg);
        }
        trailupdate::Outcome::Failed(reason) => {
            ceprintln!("[!] trail update failed ({reason}); continuing with the existing trails");
            output::log_error(&format!("trail update failed: {reason}"), true);
            warn_if_trails_are_stale(cfg);
        }
    }
}

/// Loudly flag a trails file older than one update period — the failure mode that cost a live
/// detection: an old file looks perfectly healthy while the sensor quietly misses new IOCs.
fn warn_if_trails_are_stale(cfg: &Config) {
    let Some(age) = trailupdate::trails_age_secs(&cfg.trails_file) else {
        ceprintln!("[!] no trails file at '{}' - the sensor will detect NOTHING", cfg.trails_file.display());
        return;
    };
    if age > cfg.update_period.max(1) {

View on GitHub (pinned to 77cfb06d76)