fix(relay): Limit maximum number of otel logs deserialized from JSON - #6273
fix(relay): Limit maximum number of otel logs deserialized from JSON#6273klochek wants to merge 1 commit into
Conversation
| fn parse_logs_data(format: OtelFormat, payload: &[u8], max_logs: usize) -> Result<LogsData, Error> { | ||
| match format { | ||
| OtelFormat::Json => serde_json::from_slice(payload).map_err(|e| { | ||
| OtelFormat::Json => otel_json_deserializer::deserialize(payload, max_logs).map_err(|e| { |
There was a problem hiding this comment.
Limit error remapped to InvalidJson
Medium Severity
otel_json_deserializer::deserialize already returns logs::Error, including TooManyExpandedLogs, but parse_logs_data maps every failure to Invalid(InvalidJson). That drops the dedicated over-limit error and its InvalidLog outcome, so oversized expansions are misclassified as bad JSON.
Reviewed by Cursor Bugbot for commit 2ce987e. Configure here.
| @@ -46,7 +48,9 @@ pub fn expand( | |||
|
|
|||
| let settings = match integration { | |||
There was a problem hiding this comment.
OTel protobuf log expansion ignores max_expanded_log_count limit
otel::expand receives max_expanded_log_count for both JSON and protobuf, but only applies it to JSON, allowing a protobuf payload with millions of minimal log records to bypass the limit and exhaust CPU.
Evidence
mod.rs:49passesmax_expanded_log_counttootel::expand(format, &payload, max_expanded_log_count, produce).otel.rs:36parse_logs_data(format, payload, max_logs)receives the limit asmax_logs.otel.rs:45usesLogsData::decode(payload)forOtelFormat::Protobuf, silently discardingmax_logs.otel.rs:22-31iterates every decoded record and callsproducefor each, so a protobuf with millions of minimal records expands to millions of logs.- A crafted protobuf near the max envelope size (200 MiB) could contain millions of tiny
LogRecordentries, each processed throughrelay_ourlogs::otel_to_sentry_log.
Also found at 6 additional locations
relay-config/src/config.rs:642-642relay-config/src/config.rs:733relay-server/src/processing/logs/integrations/otel.rs:45-50relay-server/src/processing/logs/mod.rs:58relay-server/src/processing/logs/process.rs:50-51relay-server/src/processing/logs/mod.rs:161
Identified by Warden · wrdn-dos-review · U83-KKN
| filter::feature_flag(ctx).reject(&logs)?; | ||
|
|
||
| let mut logs = process::expand(logs)?; | ||
| let mut logs = process::expand(logs, ctx.config.max_expanded_log_count())?; |
There was a problem hiding this comment.
NEL and Vercel log integrations expanded without count limit
The max_expanded_log_count parameter is forwarded to OTEL but not to NEL or Vercel, allowing unbounded JSON array deserialization from untrusted payloads.
Evidence
mod.rs:161passesctx.config.max_expanded_log_count()intoprocess::expand.integrations/mod.rsreceives the limit but only forwards it tootel::expand.nel::expandcallsserde_json::from_slice::<Vec<_>>(payload)with no count cap.vercel::expandcallsserde_json::from_slice::<Vec<VercelLog>>(payload)for JSON and iterates all NdJson lines without applying any count limit.
Identified by Warden · wrdn-dos-review · UMT-LHG
2ce987e to
6b0b235
Compare
| relay_serialization::serde::deserialize(&mut de, max_ops).map_err(|e| { | ||
| relay_log::debug!( | ||
| error = &e as &dyn std::error::Error, | ||
| "Failed to parse logs data as JSON" | ||
| ); | ||
| Error::Invalid(DiscardReason::InvalidJson) | ||
| }) |
There was a problem hiding this comment.
Bug: Deserialization operation limit errors are incorrectly classified as InvalidJson, conflating resource exhaustion with malformed data and impacting observability.
Severity: MEDIUM
Suggested Fix
Update the map_err closure to differentiate between error variants from relay_serialization::serde::deserialize. Match on the error and map LimitExceeded to a new, specific DiscardReason (e.g., Complexity) and Serde errors to InvalidJson. This will likely require adding a new variant to the DiscardReason enum.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: relay-server/src/processing/logs/integrations/otel.rs#L39-L45
Potential issue: In the OTel JSON log processing, the error handling for deserialization
does not distinguish between different failure modes. The
`relay_serialization::serde::deserialize` function can fail with a `LimitExceeded` error
if the JSON is too complex, or a `Serde` error for malformed JSON. The current
implementation in `otel.rs` maps both error types to `DiscardReason::InvalidJson`. This
misclassifies payloads that exceed the operation budget as having invalid JSON, which
hinders observability and makes it difficult to detect and monitor abuse attempts
involving overly complex payloads.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6b0b235. Configure here.
| max_profile_size: ByteSize::mebibytes(50), | ||
| max_trace_metric_size: ByteSize::mebibytes(1), | ||
| max_log_size: ByteSize::mebibytes(2), | ||
| max_expanded_log_operations: 20000, |
There was a problem hiding this comment.
Default operation limit rejects valid logs
High Severity
The default max_expanded_log_operations of 20000 budgets serde operations, not log records. A typical OTEL log costs tens of operations, so this only covers a few hundred records. Collectors default to batches of 8192, and the HTTP body still allows 12 MiB, so previously valid JSON payloads will now be dropped as InvalidJson.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 6b0b235. Configure here.


No description provided.