Skip to content

ref(security): Stop accepting Expect-CT, HPKP, and Expect-Staple reports - #6230

Open
mrduncan wants to merge 8 commits into
masterfrom
ref/remove-expect-ct-hpkp-expect-staple-reports
Open

ref(security): Stop accepting Expect-CT, HPKP, and Expect-Staple reports#6230
mrduncan wants to merge 8 commits into
masterfrom
ref/remove-expect-ct-hpkp-expect-staple-reports

Conversation

@mrduncan

@mrduncan mrduncan commented Jul 14, 2026

Copy link
Copy Markdown
Member

#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 17th now

Refs getsentry/sentry#119638

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 jjbayer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks reasonable to me. We'll have to remove the sentry-side relay_integration tests in order for CI to pass.

@Dav1dde

Dav1dde commented Jul 15, 2026

Copy link
Copy Markdown
Member

This is the ~simplest change so that we stop accepting these reports and keep this PR manageable. The remaining cleanup should be straightforward as a follow up.

To fully stop ingestion we'll also have to remove it from the EventType enum and ideally also the fields on the Event schema:

    /// 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.

mrduncan added a commit to getsentry/sentry that referenced this pull request Jul 31, 2026
…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>
Comment thread relay-server/src/processing/errors/errors/raw_security.rs
mrduncan and others added 2 commits August 24, 2026 12:54
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>
@mrduncan
mrduncan marked this pull request as ready for review August 24, 2026 21:59
@mrduncan
mrduncan requested a review from a team as a code owner August 24, 2026 21:59
Comment thread relay-event-normalization/src/event.rs
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.

@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.

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 909dddf. Configure here.

Comment thread relay-server/src/processing/errors/errors/security.rs Outdated
mrduncan and others added 2 commits August 24, 2026 16:02
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>
Comment on lines +1346 to 1375
/// 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));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What are these new tests?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Warden was getting grumpy about this possibility in #6230 (comment), seemed like a reasonable thing to have test assurances for, but happy to remove

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.

3 participants