ref(security): Stop accepting Expect-CT, HPKP, and Expect-Staple reports - #6230
ref(security): Stop accepting Expect-CT, HPKP, and Expect-Staple reports#6230mrduncan wants to merge 8 commits into
Conversation
HPKP, Expect-CT, and Expect-Staple are dead in all shipping browsers, so these report types can no longer be produced. Relay now rejects them at ingest: the security report classifier no longer recognizes them, and the dedicated expect-ct-report / expect-staple-report content types are refused at the endpoint. The interface types, EventType variants, and Event fields are intentionally retained for a later full-removal PR that coordinates with the Sentry backend. Refs getsentry/sentry#119638 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jjbayer
left a comment
There was a problem hiding this comment.
Looks reasonable to me. We'll have to remove the sentry-side relay_integration tests in order for CI to pass.
To fully stop ingestion we'll also have to remove it from the /// HPKP (security) reports.
#[metastructure(pii = "true", legacy_alias = "sentry.interfaces.Hpkp")]
#[metastructure(omit_from_schema)] // we only document error events for now
pub hpkp: Annotated<Hpkp>,
/// ExpectCT (security) reports.
#[metastructure(pii = "true", legacy_alias = "sentry.interfaces.ExpectCT")]
#[metastructure(omit_from_schema)] // we only document error events for now
pub expectct: Annotated<ExpectCt>,
/// ExpectStaple (security) reports.
#[metastructure(pii = "true", legacy_alias = "sentry.interfaces.ExpectStaple")]
#[metastructure(omit_from_schema)] // we only document error events for now
pub expectstaple: Annotated<ExpectStaple>,Can be done in a follow-up, but maybe easier to do it in one sweep. Without making these changes, outdated customer hosted Relays and custom clients can still send these reports. |
…ts (#119685) Relay is dropping ingest support for Expect-CT, HPKP, and Expect-Staple security reports in getsentry/relay#6230 — these report types are dead in all shipping browsers (HPKP removed from Chrome 72 / Firefox 72; Expect-CT removed from Chromium 107; Expect-Staple never shipped broadly) and can no longer be produced. This unblocks removing support from Relay, which uses these integration tests. Refs getsentry/relay#6230 Refs #119638 Co-authored-by: Claude <noreply@anthropic.com>
Follow-up to 9fbaceb, which stopped accepting these reports at ingest but deliberately kept the protocol surface for a coordinated removal. This does that removal: the EventType variants, the hpkp / expectct / expectstaple fields on the Event schema, and the now-unreachable interface types and their raw parsers. Ingestion behavior is unchanged -- reports are already rejected at the endpoint with an `invalid` outcome. What changes is that an event which an older upstream Relay already classified as one of these types is now forwarded as a `default` event, so it counts against the error quota rather than the security quota. getsentry/sentry#121028 removed the Sentry-side tests that exercised these types through Relay's normalizer, so this no longer breaks Sentry CI. Removing the dead Sentry source (eventtypes, interfaces, grouping strategies) is a follow-up that must land after Sentry bumps its sentry-relay dependency. Refs getsentry/sentry#119638 Refs getsentry/sentry#121028 Co-Authored-By: Claude <noreply@anthropic.com>
Warden flagged the untagged `CspVariant` deserialization as an unbounded recursion risk: a ~1 MiB payload can carry hundreds of thousands of nesting levels, which would overflow the stack and abort the process. The claim does not hold. `serde_json` enforces a recursion limit of 128 by default, the untagged buffering into `Content` goes through `deserialize_any` which is depth checked, and Relay never enables `unbounded_depth` nor calls `disable_recursion_limit`. Payloads nested 130 to 1,000,000 levels deep are rejected with a clean syntax error. `SecurityReportType::from_json` is unaffected too, since `IgnoredAny` uses the iterative `ignore_value` path. That protection was assumed rather than verified, so pin it down with tests that exercise both entry points well past any reachable payload size. They fail if the limit is ever lifted. Also truncate the payload attached to the Sentry scope when a report fails to parse, using `DebugBytes` like `Item` does. It was copying the whole attacker controlled body, up to `max_event_size` (1 MiB by default), on a trivially reachable path, justified by a stale comment pointing at `extract_event`, which no longer exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Security item forwarded by an older upstream Relay can still carry a removed `hpkp`, `expectct` or `expectstaple` event type. Those no longer parse, so the event is downgraded to `default` and serialized back into an `ItemType::Event`, but `Security::event_category` kept reporting `Security` unconditionally. Quotas, outcomes and store writes therefore disagreed with the item that was actually forwarded, and `RecordKeeper` tripped its quantity assertion in debug builds. Derive the category from the parsed event instead, which is the behavior the changelog already describes.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 909dddf. Configure here.
An older upstream Relay can still forward a `Security` item carrying one of the removed `hpkp`, `expectct` or `expectstaple` event types. Those no longer parse, so the event was downgraded to `default` and serialized back into an `ItemType::Event`, while the envelope had counted the item as `Security`. Deriving the category from the parsed event instead only moved the mismatch: incoming envelopes still count the item as `Security` while `ExpandedError` reported `Error`, tripping the `RecordKeeper` quantity assertion in debug builds. Enforce the invariant the `Generic` expansion already follows, where the item type determines the data category and the event is coerced to match. `Security::event_category` is `Security` again, the declared event type is discarded so an item cannot claim to be a transaction, and an item which does not hold a security report is rejected with the same `invalid` (`security_report_type`) outcome the endpoint emits for the raw reports. Refs getsentry/sentry#119638 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| /// A CSP report whose body is `depth` levels of nested JSON arrays. | ||
| fn deeply_nested_csp_report(depth: usize) -> Vec<u8> { | ||
| let mut payload = br#"{"csp-report":"#.to_vec(); | ||
| payload.extend(std::iter::repeat_n(b'[', depth)); | ||
| payload.extend(std::iter::repeat_n(b']', depth)); | ||
| payload.push(b'}'); | ||
| payload | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_deeply_nested_csp_report_is_rejected() { | ||
| // Nesting must not recurse until the stack overflows. `serde_json` caps recursion at 128 | ||
| // levels and Relay never lifts that limit. | ||
| let payload = deeply_nested_csp_report(1_000_000); | ||
|
|
||
| let mut event = Event::default(); | ||
| let error = Csp::apply_to_event(&payload, &mut event).unwrap_err(); | ||
|
|
||
| assert!(error.is_syntax(), "expected a syntax error, got: {error}"); | ||
| assert_eq!(event, Event::default()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_deeply_nested_report_type_deduction_is_bounded() { | ||
| let payload = deeply_nested_csp_report(1_000_000); | ||
|
|
||
| // The classifier ignores the report body, so it still recognizes the type. | ||
| let report_type = SecurityReportType::from_json(&payload).unwrap(); | ||
| assert_eq!(report_type, Some(SecurityReportType::Csp)); | ||
| } |
There was a problem hiding this comment.
Warden was getting grumpy about this possibility in #6230 (comment), seemed like a reasonable thing to have test assurances for, but happy to remove

#6314 should fix the lint failures here.
HPKP, Expect-CT, and Expect-Staple are dead in all shipping browsers, so these report types can no longer be produced. Relay now rejects them at ingest: the security report classifier no longer recognizes them, and the dedicated expect-ct-report / expect-staple-report content types are refused at the endpoint.
Planned removal:
August 17thnowRefs getsentry/sentry#119638