stamparm/maltrail · error

wildcard alert

Error message

wildcard alert

What it means

Test assertion in wildcard_key_builds_the_python_trail: after pumping > NO_SUCH_NAME_PER_HOUR_THRESHOLD queries under one parent domain, observe() must eventually emit NxAlert::Wildcard carrying a Python-style trail string; the expect fails if no wildcard alert was produced. It encodes the contract that sustained NXDOMAIN traffic under one suffix is classified as a wildcard.

Solutions

  1. Confirm observe() still returns NxAlert::Wildcard once the hourly threshold is exceeded
  2. Check the wildcard key builder still aggregates the parent domain instead of per-subdomain keys
  3. Verify the test's key/parent-domain setup matches the detector's expectations
Defensive patterns

Strategy: validation

Validate before calling

let mut got_wildcard = false;
for i in 0..(settings::NO_SUCH_NAME_PER_HOUR_THRESHOLD + 2) {
    if matches!(nx.observe(key, &format!("h{i}.evil.com"), 10), Some(NxAlert::Wildcard { .. })) { got_wildcard = true; }
}
assert!(got_wildcard, "wildcard alert never fired");

Prevention

When it happens

Trigger: The loop runs NO_SUCH_NAME_PER_HOUR_THRESHOLD+2 times yet no NxAlert::Wildcard variant is returned — the wildcard detector never fires (threshold logic, key building, or counting regressed) or observe now returns a different NxAlert variant.

Common situations: Changing the wildcard detection threshold, renaming/alert-variant refactors, or a bug where repeated subdomains are deduplicated so the counter never crosses the threshold.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at sensor/src/heuristics/nxdomain.rs:180

            if let Some(NxAlert::Exact { trail }) = nx.observe("a.com", "a.com", 10) {
                assert_eq!(trail, "a.com");
                alerts += 1;
            }
        }
        assert_eq!(alerts, 1, "the key is dropped after alerting, so it fires once");
    }

    #[test]
    fn wildcard_key_builds_the_python_trail() {
        let mut nx = NxCounters::default();
        let key = "*.evil.com";
        let mut trail = None;
        for i in 0..(settings::NO_SUCH_NAME_PER_HOUR_THRESHOLD + 2) {
            if let Some(NxAlert::Wildcard { trail: t, .. }) = nx.observe(key, &format!("h{i}.evil.com"), 10) {
                trail = Some(t);
            }
        }
        let trail = trail.expect("wildcard alert");
        assert!(trail.starts_with('('), "{trail}");
        assert!(trail.ends_with(").evil.com"), "{trail}");
        assert!(trail.contains("h1,"), "{trail}");
    }

    #[test]
    fn entropy_and_consonants() {
        // sanity: a high-entropy DGA-looking label beats the 3.5 threshold
        assert!(label_entropy("xkqwzlvbnmfghjd") > settings::SUSPICIOUS_DOMAIN_ENTROPY_THRESHOLD);
        assert!(label_entropy("aaaa") < 0.001);
        assert_eq!(label_entropy(""), 0.0);
        assert_eq!(consonant_count("google"), 3);
        assert!(consonant_count("xkqwzlvbnmf") > settings::SUSPICIOUS_DOMAIN_CONSONANT_THRESHOLD);
    }
}

View on GitHub (pinned to 77cfb06d76)