Skip to content

Guard JSON serialization against json 3.0 encoding errors - #3075

Draft
sentry-junior[bot] wants to merge 5 commits into
masterfrom
fix/json-3-encoding
Draft

Guard JSON serialization against json 3.0 encoding errors#3075
sentry-junior[bot] wants to merge 5 commits into
masterfrom
fix/json-3-encoding

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Problem

json 3.0.0 (released today) turns the json 2.8+ deprecation warning ("UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0") into a hard Encoding::UndefinedConversionError raise whenever JSON.generate/JSON.dump encounters a String tagged with a non-UTF-8 encoding (e.g. ASCII-8BIT/BINARY) containing bytes invalid for the target encoding. This matches the stack trace in #2462, which points straight at envelope/item.rb#to_s#serializetransport.rb#serialize_envelope.

Places identified and protected

Unguarded JSON.generate/JSON.dump call sites that could receive attacker/user-controlled strings with unexpected encodings:

  • Envelope::Item#to_s, #lost_event_byte_size, #size_breakdown (the exact reported crash path)
  • Transport#serialize_envelope (envelope headers)
  • Event#to_json_compatible
  • TelemetryEventBuffer#add_item, #send_items
  • Transport::DebugTransport#log_envelope, DebugStructuredLogger#capture_log_event (debug-only, still hardened)
  • Breadcrumb#serialized_data, Utils::TelemetryAttributes#attribute_hash (already rescued broadly, but previously discarded the whole payload instead of sanitizing it)

Fix

  • Added Sentry::Utils::EncodingHelper.deep_encode_utf_8, which recursively walks Hash/Array/String structures and force-encodes/scrubs any String to valid UTF-8.
  • Each call site above now retries with deep_encode_utf_8-sanitized input if JSON.generate/JSON.dump raises EncodingError/JSON::GeneratorError, so valid data is preserved instead of being dropped.
  • Added a last-resort rescue EncodingError, JSON::GeneratorError in Transport#send_envelope, mirroring the existing SizeExceededError handling, so if per-item sanitization still can't produce valid JSON, the event is recorded as a lost event (:send_error) instead of crashing the background worker.

Fixes #2462

Testing

  • Added spec/sentry/utils/encoding_helper_spec.rb for deep_encode_utf_8.
  • Added regression specs in envelope/item_spec.rb, breadcrumb_spec.rb, and transport_spec.rb that stub JSON.generate to raise EncodingError (simulating json 3.0 behavior regardless of the json gem version actually loaded in CI) and assert recovery/no-crash behavior.
  • Could not run the Ruby test suite directly in this sandbox (no Ruby runtime available); please run CI/rspec to confirm.

Requested by neel.

--

View Junior Session [Sentry]

@sl0thentr0py

Copy link
Copy Markdown
Member

I don't want the deep encode, we should clean up possible encoding issues wherever we fill up the data

@sl0thentr0py

Copy link
Copy Markdown
Member

rebase latest master

@sl0thentr0py
sl0thentr0py force-pushed the fix/json-3-encoding branch 2 times, most recently from dea181c to c40f83d Compare September 7, 2026 12:44
sentry-junior Bot and others added 4 commits September 7, 2026 14:44
json 3.0 turns the json 2.8+ deprecation warning (UTF-8 string passed as
BINARY) into a hard raise (Encoding::UndefinedConversionError) when
JSON.generate/JSON.dump encounters a String tagged with a non-UTF-8
encoding that contains invalid bytes for the target encoding.

Add Utils::EncodingHelper.deep_encode_utf_8 to recursively sanitize
Hash/Array/String payloads, use it to proactively protect the JSON
generation call sites that were not already guarded:

- Envelope::Item#to_s / #lost_event_byte_size / #size_breakdown
- Transport#serialize_envelope (envelope headers)
- Event#to_json_compatible
- TelemetryEventBuffer#add_item / #send_items
- DebugTransport#log_envelope, DebugStructuredLogger#capture_log_event
- Breadcrumb#serialized_data, Utils::TelemetryAttributes#attribute_hash

Also add a last-resort rescue for EncodingError/JSON::GeneratorError in
Transport#send_envelope so a single unrecoverable event is recorded as
a lost event (like SizeExceededError) instead of crashing the
background worker.

Fixes #2462

Co-Authored-By: Neel Shah <6536764+sl0thentr0py@users.noreply.github.com>
Transport::Configuration has no #debug attribute; the debug flag lives
on the top-level Configuration. Store it on the Transport instance
(mirroring how @sdk_logger/@dsn are cached) and use that instead.

Caught by transport_spec.rb's new encoding-error test.
… retries

Per review feedback: drop the reactive 'catch JSON.generate, deep-encode,
and retry' pattern from serialization exit points (Envelope::Item,
Transport#serialize_envelope headers, Event#to_json_compatible,
TelemetryEventBuffer, DebugTransport, DebugStructuredLogger). Those
are reverted to plain JSON.generate/JSON.dump.

Instead, sanitize encoding proactively wherever the affected data
actually gets filled in:

- Breadcrumb#data= now force-encodes/scrubs strings in the arbitrary
  user-supplied data hash to valid UTF-8 (both on construction and on
  later assignment), matching the existing pattern used for
  Breadcrumb#message=, SingleException#value, and RequestInterface's
  body/headers.
- Utils::TelemetryAttributes#attribute_hash now sanitizes String
  attribute values directly, and sanitizes non-scalar (Hash/Array)
  attribute values before JSON.generate rather than reacting to a
  raised error.

Transport#send_envelope keeps its rescue for EncodingError/
JSON::GeneratorError as a last-resort safety net (log + record lost
event) for any case not already covered by the above, without
attempting to fix and retry.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deprecation warning with json gem 2.8.0+

1 participant