vectordotdev/vector · critical

invalid timestamp

Error message

invalid timestamp

What it means

The fluent source decodes MessagePack bodies in the fluent forward protocol. Fluent's EventTime is an 8-byte extension (ext type 0): 4 bytes big-endian Unix seconds plus 4 bytes nanoseconds. After the length check, Vector converts with Utc.timestamp_opt(seconds, nanoseconds).single().expect("invalid timestamp"). chrono rejects nanoseconds >= 2,000,000,000 and seconds outside its roughly +-262k-year window by returning None, so a well-sized but out-of-range extension panics the decoding task.

Source

Thrown at src/sources/fluent/message.rs:122

                    .next_element()?
                    .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;

                if bytes.len() != 8 {
                    return Err(serde::de::Error::custom(format!(
                        "expected exactly 8 bytes for binary encoded fluent timestamp, got {}",
                        bytes.len()
                    )));
                }

                // length checked right above
                let seconds = u32::from_be_bytes(bytes[..4].try_into().expect("exactly 4 bytes"));
                let nanoseconds =
                    u32::from_be_bytes(bytes[4..].try_into().expect("exactly 4 bytes"));

                Ok(FluentEventTime(
                    Utc.timestamp_opt(seconds.into(), nanoseconds)
                        .single()
                        .expect("invalid timestamp"),
                ))
            }
        }

        deserializer.deserialize_any(FluentEventTimeVisitor)
    }
}

/// Value for fluent record key.
///
/// Used mostly just to implement value conversion.
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub(super) struct FluentValue(rmpv::Value);

impl From<rmpv::Value> for FluentValue {
    fn from(value: rmpv::Value) -> Self {
        Self(value)
    }

View on GitHub (pinned to 3708c39b12)

Solutions

  1. Fix or sanitize the sender so EventTime nanoseconds stay below 1,000,000,000 and seconds are sane
  2. Patch the visitor to return a serde error instead of expecting, so one bad event fails that message's decode rather than panicking
  3. Restrict which hosts can reach the fluent TCP port
  4. Upgrade Vector once the fix lands

Example fix

// before
Ok(FluentEventTime(
    Utc.timestamp_opt(seconds.into(), nanoseconds)
        .single()
        .expect("invalid timestamp"),
))

// after
match Utc.timestamp_opt(seconds.into(), nanoseconds).single() {
    Some(ts) => Ok(FluentEventTime(ts)),
    None => Err(E::custom(format!(
        "EventTime out of range: seconds={seconds}, nanoseconds={nanoseconds}"
    ))),
}
Defensive patterns

Strategy: validation

Validate before calling

fn valid_fluent_event_time(seconds: u32, nanos: u32) -> bool {
    let secs_ok = (seconds as i64) >= -8_334_601_228_800 && (seconds as i64) <= 8_210_266_876_799;
    secs_ok && nanos < 2_000_000_000
}

// in the visitor, before constructing the timestamp:
if !valid_fluent_event_time(seconds, nanoseconds) {
    return Err(E::custom("EventTime out of range"));
}

Type guard

fn parse_fluent_event_time(seconds: u32, nanos: u32) -> Option<chrono::DateTime<chrono::Utc>> {
    Utc.timestamp_opt(seconds as i64, nanoseconds).single()
}

Try / catch

// inside Deserialize: return Err instead of expect so serde fails per message
match Utc.timestamp_opt(seconds.into(), nanoseconds).single() {
    Some(ts) => Ok(FluentEventTime(ts)),
    None => Err(E::custom(format!(
        "EventTime out of range: seconds={seconds}, nanos={nanoseconds}"
    ))),
}

Prevention

When it happens

Trigger: A fluentd/fluent-bit peer (or any forward-protocol client) sending EventTime whose nanosecond word is >= 2e9 - for example milliseconds or microseconds written into the nanos field - or garbage seconds. The 8-byte length check passes, then chrono refuses the value.

Common situations: Interop with non-compliant forwarders, corrupted TCP frames, replayed or fuzzed captures against the fluent endpoint. One malformed timestamp in a MessagePack array kills the source task and the pipeline with it.

Related errors


AI-assisted analysis of vectordotdev/vector@3708c39b12 (2026-08-20). Data as JSON: /api/errors/c3f6cfd0dbd32659. Report an issue: GitHub.