Skip to content

Commit fb84a66

Browse files
loewenheimclaude
andcommitted
fix(eap): Prevent oversized attribute keys from consuming size budget
Fixes a bug in process_attributes where attribute keys that were too large to fit in the remaining space would still consume from the size budget before being rejected. This caused the global item size limit to be incorrectly depleted, potentially causing subsequent fields to be over-trimmed or removed. The fix checks if the key fits in the remaining space before consuming any size. Adds a test that verifies the fix by checking that a field processed after attributes is not affected by rejected oversized keys. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 6a9dc11 commit fb84a66

File tree

1 file changed

+52
-3
lines changed

1 file changed

+52
-3
lines changed

relay-event-normalization/src/eap/trimming.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,15 @@ impl Processor for TrimmingProcessor {
327327

328328
let mut split_idx = None;
329329
for (idx, (key, value)) in sorted.iter_mut().enumerate() {
330-
self.consume_size(key.len());
331-
332-
if self.remaining_size() == Some(0) {
330+
if let Some(remaining) = self.remaining_size()
331+
&& remaining < key.len()
332+
{
333333
split_idx = Some(idx);
334334
break;
335335
}
336336

337+
self.consume_size(key.len());
338+
337339
let value_state = state.enter_borrowed(key, None, ValueType::for_field(value));
338340
processor::process_value(value, self, &value_state).inspect_err(|_| {
339341
self.in_attributes = false;
@@ -372,6 +374,8 @@ mod tests {
372374
number: Annotated<u64>,
373375
#[metastructure(max_bytes = 40, trim = true)]
374376
attributes: Annotated<Attributes>,
377+
#[metastructure(trim = true)]
378+
footer: Annotated<String>,
375379
}
376380

377381
#[test]
@@ -386,6 +390,7 @@ mod tests {
386390
attributes: Annotated::new(attributes),
387391
number: Annotated::new(0),
388392
body: Annotated::new("This is longer than allowed".to_owned()),
393+
footer: Annotated::empty(),
389394
});
390395

391396
let mut processor = TrimmingProcessor::new(None);
@@ -459,6 +464,7 @@ mod tests {
459464
attributes: Annotated::new(attributes),
460465
number: Annotated::new(0),
461466
body: Annotated::new("This is longer than allowed".to_owned()),
467+
footer: Annotated::empty(),
462468
});
463469

464470
let mut processor = TrimmingProcessor::new(None);
@@ -533,6 +539,7 @@ mod tests {
533539
attributes: Annotated::new(attributes),
534540
number: Annotated::new(0),
535541
body: Annotated::new("This is longer than allowed".to_owned()),
542+
footer: Annotated::empty(),
536543
});
537544

538545
let mut processor = TrimmingProcessor::new(None);
@@ -590,11 +597,13 @@ mod tests {
590597
attributes: Annotated::new(attributes),
591598
number: Annotated::new(0),
592599
body: Annotated::new("Short".to_owned()),
600+
footer: Annotated::new("Hello World".to_owned()),
593601
});
594602

595603
// The `body` takes up 5B, the `"small"` attribute 13B, and the key "medium string" another 13B.
596604
// That leaves 9B for the string's value.
597605
// Note that the `number` field doesn't take up any size.
606+
// The `"footer"` is removed because it comes after the attributes and there's no space left.
598607
let mut processor = TrimmingProcessor::new(Some(40));
599608

600609
let state = ProcessingState::new_root(Default::default(), []);
@@ -663,6 +672,7 @@ mod tests {
663672
attributes: Annotated::new(attributes),
664673
number: Annotated::new(0),
665674
body: Annotated::new("Short".to_owned()),
675+
footer: Annotated::empty(),
666676
});
667677

668678
let mut processor = TrimmingProcessor::new(None);
@@ -715,4 +725,43 @@ mod tests {
715725
}
716726
"###);
717727
}
728+
729+
#[test]
730+
fn test_oversized_key_does_not_consume_global_limit() {
731+
let mut attributes = Attributes::new();
732+
attributes.insert("a", 1); // 9B
733+
attributes.insert("this_key_is_exactly_35_chars_long!!", true); // 35B key + 1B = 36B
734+
735+
let mut value = Annotated::new(TestObject {
736+
body: Annotated::new("Hi".to_owned()), // 2B
737+
number: Annotated::new(0),
738+
attributes: Annotated::new(attributes),
739+
footer: Annotated::new("Hello World".to_owned()), // 11B
740+
});
741+
742+
let mut processor = TrimmingProcessor::new(Some(30));
743+
let state = ProcessingState::new_root(Default::default(), []);
744+
processor::process_value(&mut value, &mut processor, &state).unwrap();
745+
746+
insta::assert_json_snapshot!(SerializableAnnotated(&value), @r###"
747+
{
748+
"body": "Hi",
749+
"number": 0,
750+
"attributes": {
751+
"a": {
752+
"type": "integer",
753+
"value": 1
754+
}
755+
},
756+
"footer": "Hello World",
757+
"_meta": {
758+
"attributes": {
759+
"": {
760+
"len": 45
761+
}
762+
}
763+
}
764+
}
765+
"###);
766+
}
718767
}

0 commit comments

Comments
 (0)