stamparm/maltrail · error

just probed

Error message

just probed

What it means

In the HTTP request host-based trail lookup, the code calls `st.trails.db().get(&candidate)` twice: once with `is_some()` to test, then again with `.expect("just probed")` to unwrap. If the two lookups ever disagree — or the first check passes but the second returns `None` — the `expect` panics. This is a redundant double-lookup pattern whose invariant ('we just probed it, so it must still be there') is asserted rather than guaranteed.

Solutions

  1. Replace the check-then-unwrap double lookup with a single `if let Some(hit) = st.trails.db().get(&candidate)` that uses the returned reference directly — removes the invariant entirely.
  2. If cloning is needed, clone `info`/`reference` from the single `get` result instead of re-querying.
  3. Ensure the trails DB handle returned by `db()` is stable across calls within one request, or snapshot it once per request.
  4. Add a concurrency test that reloads the DB while requests are parsed to prove no panic path remains.

Example fix

// before
if st.trails.db().get(&candidate).is_some() {
    let hit = st.trails.db().get(&candidate).expect("just probed");
    let (info, reference) = (hit.info.to_string(), hit.reference.to_string());
    ...
}
// after
if let Some(hit) = st.trails.db().get(&candidate) {
    let (info, reference) = (hit.info.to_string(), hit.reference.to_string());
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

// single lookup; no second get() can disagree
if let Some(hit) = st.trails.db().get(&candidate) { /* use hit */ }

Try / catch

catch_unwind(AssertUnwindSafe(|| handle_http_host(st, ep)))
    .unwrap_or_else(|_| log::warn!("http host lookup panicked; request skipped"));

Prevention

When it happens

Trigger: An HTTP request whose `host + '/'` key is reported present by the first `db().get()` but absent on the immediate second `db().get()`, panicking at `.expect("just probed")`. Any non-deterministic `get` (concurrent DB reload/swap between the two calls, `get` with side effects or interior mutability) triggers it.

Common situations: Trails DB hot-reload racing request handling; a custom `db()` implementation whose `get` is not pure; copy-paste refactors where the boolean check and the unwrap query different keys.

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/95b1bc63fdc604db. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/process.rs:1137

            } else {
                // parts = url.split(check); every non-empty part is bracketed
                let url = match &prebuilt_url {
                    Some(u) => Cow::Borrowed(u.as_str()),
                    None => Cow::Owned(format!("{host}{path}")),
                };
                let trail = bracket_around(&url, &candidate);
                emit_ep(st, sec, usec, ep, PROTO::TCP, TRAIL::URL, Field::Text(trail), &info, &reference);
            }
            return;
        }
    }

    // `format!("{host}/")` allocated on every request; the candidate buffer is already sized.
    candidate.clear();
    candidate.push_str(&host);
    candidate.push('/');
    if st.trails.db().get(&candidate).is_some() {
        let hit = st.trails.db().get(&candidate).expect("just probed");
        let (info, reference) = (hit.info.to_string(), hit.reference.to_string());
        emit_ep(st, sec, usec, ep, PROTO::TCP, TRAIL::URL, Field::Text(candidate.clone()), &info, &reference);
        return;
    }

    if !st.cfg.use_heuristics || heuristics_suppressed {
        return;
    }

    // Forwarded-for headers are searched in the RAW packet bytes, case-insensitively. The
    // literal pre-condition comes first: asking a case-insensitive alternation for capture
    // groups walks the whole packet, and next to no request carries one of these headers.
    let mut src_ip_field = ep.src.render().as_str().to_string();
    if st.statics.forwarded_for_pre_condition.is_match(packet_bytes) {
        if let Some(caps) = st.statics.forwarded_for.captures(packet_bytes) {
            if let Some(m) = caps.get(2) {
                let forwarded = String::from_utf8_lossy(m.as_bytes()).to_string();
                src_ip_field = format!("{src_ip_field},{forwarded}");

View on GitHub (pinned to 77cfb06d76)