Skip to content

fix(relay): Limit maximum number of otel logs deserialized from JSON - #6273

Open
klochek wants to merge 1 commit into
masterfrom
christopherklochek/ingest-1099-add-bounded-json-serializer
Open

fix(relay): Limit maximum number of otel logs deserialized from JSON#6273
klochek wants to merge 1 commit into
masterfrom
christopherklochek/ingest-1099-add-bounded-json-serializer

Conversation

@klochek

@klochek klochek commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@klochek
klochek requested a review from a team as a code owner July 30, 2026 17:25
@linear-code

linear-code Bot commented Jul 30, 2026

Copy link
Copy Markdown

INGEST-1099

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| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2ce987e. Configure here.

Comment thread relay-server/src/processing/logs/integrations/otel_json_deserializer.rs Outdated
Comment thread relay-server/src/processing/logs/integrations/otel_json_deserializer.rs Outdated
Comment thread relay-server/src/processing/logs/integrations/otel_json_deserializer.rs Outdated
@@ -46,7 +48,9 @@ pub fn expand(

let settings = match integration {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:49 passes max_expanded_log_count to otel::expand(format, &payload, max_expanded_log_count, produce).
  • otel.rs:36 parse_logs_data(format, payload, max_logs) receives the limit as max_logs.
  • otel.rs:45 uses LogsData::decode(payload) for OtelFormat::Protobuf, silently discarding max_logs.
  • otel.rs:22-31 iterates every decoded record and calls produce for 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 LogRecord entries, each processed through relay_ourlogs::otel_to_sentry_log.
Also found at 6 additional locations
  • relay-config/src/config.rs:642-642
  • relay-config/src/config.rs:733
  • relay-server/src/processing/logs/integrations/otel.rs:45-50
  • relay-server/src/processing/logs/mod.rs:58
  • relay-server/src/processing/logs/process.rs:50-51
  • relay-server/src/processing/logs/mod.rs:161

Identified by Warden · wrdn-dos-review · U83-KKN

Comment thread relay-server/src/processing/logs/integrations/otel_json_deserializer.rs Outdated
Comment thread relay-server/src/processing/logs/mod.rs Outdated
filter::feature_flag(ctx).reject(&logs)?;

let mut logs = process::expand(logs)?;
let mut logs = process::expand(logs, ctx.config.max_expanded_log_count())?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:161 passes ctx.config.max_expanded_log_count() into process::expand.
  • integrations/mod.rs receives the limit but only forwards it to otel::expand.
  • nel::expand calls serde_json::from_slice::<Vec<_>>(payload) with no count cap.
  • vercel::expand calls serde_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

@klochek
klochek force-pushed the christopherklochek/ingest-1099-add-bounded-json-serializer branch from 2ce987e to 6b0b235 Compare August 28, 2026 07:18
Comment on lines +39 to +45
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)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6b0b235. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant