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
- Fix or sanitize the sender so EventTime nanoseconds stay below 1,000,000,000 and seconds are sane
- Patch the visitor to return a serde error instead of expecting, so one bad event fails that message's decode rather than panicking
- Restrict which hosts can reach the fluent TCP port
- 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
- Never expect() inside Deserialize implementations; external input must produce Err, not panic
- Unit-test deserializers with nanos >= 2e9 and extreme seconds
- Restrict fluent endpoint exposure to known forwarders
- Add fuzzing (cargo-fuzz) for the msgpack decoder
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
- Timestamp out of range
- invalid timestamp
- invalid timestamp
- path and query should never fail to parse
- Serializer does not support JSON
AI-assisted analysis of vectordotdev/vector@3708c39b12 (2026-08-20).
Data as JSON: /api/errors/c3f6cfd0dbd32659.
Report an issue: GitHub.