fix(sentry): stop capturing request bodies and scrub Postgres DETAIL rows - #866
Open
blarghmatey wants to merge 3 commits into
Open
fix(sentry): stop capturing request bodies and scrub Postgres DETAIL rows#866blarghmatey wants to merge 3 commits into
blarghmatey wants to merge 3 commits into
Conversation
… DETAIL Two separate ways learner data reaches Sentry, neither covered by send_default_pii. Request bodies are not gated on the PII flag. The SDK sets request.data unconditionally at sentry_sdk/integrations/_wsgi_common.py:123, and max_request_body_size (checked at :61) is the only control. The plugin already set it to "small", but "small" admits bodies up to 1,000 bytes, and the Open edX write endpoints that actually error are xblock handler POSTs -- graded problem submissions, routinely well under that cap. So the existing setting did not protect the payload it was chosen to protect. Default to "never"; SENTRY_SEND_HTTP_REQUEST_BODIES still lets an operator widen it per deployment. Postgres appends a DETAIL line to constraint violations that echoes the whole offending row, and psycopg puts it in str(exc), so it ships inside the exception value where no SDK privacy option reaches it. Measured on mitxonline MITXONLINE-6PK: a SCIM PATCH IntegrityError reproducing a learner email address three times per event, 46,764 occurrences since 2026-05-27. before_send now truncates at the DETAIL marker across exception values, logentry, and the legacy top-level message, keeping the primary error that identifies the failure. The scrub runs as the first statement in the filter so the module's documented fail-open handler returns an already-scrubbed event; a privacy control that fails open is not one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RVJKTGk9KHUTN2xfU59ujX
This was referenced Sep 8, 2026
fix(sentry): set max_request_body_size to small and scrub Postgres DETAIL rows
mitodl/mit-learn#3915
Merged
Open
Open
Merged
Open
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Breadcrumbs remain unsanitized, and release metadata and documentation need updates.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Hardens the Sentry plugin against learner-data exposure.
Changes:
- Disables HTTP request-body capture by default.
- Scrubs PostgreSQL
DETAIL:rows from Sentry events. - Adds regression and configuration tests.
File summaries
| File | Description |
|---|---|
tests/test_sentry.py |
Tests scrubbing and request-body settings. |
settings/sentry.py |
Implements scrubbing and safer defaults. |
Review details
Suppressed comments (1)
src/ol_openedx_sentry/ol_openedx_sentry/settings/sentry.py:336
- The public settings table still documents this default as
"small"(src/ol_openedx_sentry/README.rst:70-73). That now gives operators incorrect privacy behavior expectations; update the README default and opt-in guidance alongside this change.
max_request_body_size=env_tokens.get(
"SENTRY_SEND_HTTP_REQUEST_BODIES", "never"
),
- Files reviewed: 2/2 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Addresses all three Copilot review threads on #866. 1. Breadcrumbs (and more) were unscrubbed. Verified against sentry-sdk 2.55.0: the first pass enumerated three paths and missed breadcrumbs[].message (integrations/logging.py:311 -- exactly MITXONLINE-6PK's shape, mechanism=logging), logentry.params (:274), and frames[].vars, which is populated because include_local_variables defaults to True (consts.py:1028, utils.py:616). Confirmed the old implementation leaked on all three before changing it. Replaced with a recursive walk rather than a longer path list, so it does not go stale when the SDK grows another such field; the walk rewrites only str leaves, with a test pinning that. Copilot also suggested normalizing exception-valued params. Not needed: client._prepare_event serializes the event before calling before_send (client.py:650 vs :658), so every leaf is already a JSON primitive and no live exception objects remain. 2. The fail-open contract said "returned unfiltered", which stopped being true when the scrub moved ahead of it. Corrected in the module design note and README.rst: fail-open returns the event rather than dropping it, and the DETAIL scrub is the one thing that still runs, because it is the filter's first statement. Also corrected the README settings table, which still documented SENTRY_SEND_HTTP_REQUEST_BODIES as defaulting to "small" -- the suppressed comment on the same review. It is "never" now, with the reasoning recorded next to it. 3. Version 0.4.0 -> 0.5.0 per AGENTS.md:237, minor rather than patch because this changes shipped default behavior. uv.lock regenerated so its recorded version matches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RVJKTGk9KHUTN2xfU59ujX
The SDK repr()s frame locals and non-string logging params before before_send, so the DETAIL line arrives with a literal backslash-n and the old "\nDETAIL:" find missed it. Match both forms, and add an SDK-driven test so the fixtures stop using a shape the SDK never produces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MYc2F3cFvSCfVzqeRMshGy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This repo
Changes the
SENTRY_SEND_HTTP_REQUEST_BODIESdefault inol_openedx_sentryfrom"small"to"never", and adds the PostgresDETAIL:scrub to the existingsentry_event_filter.Unlike the standalone Django apps in this change, this plugin was never on the SDK default — the author chose
"small"deliberately, in the same commit that made PII opt-in. But"small"still admits bodies up to 1,000 bytes, and the Open edX write endpoints that show up in the error data are xblock handler POSTs, whose payloads can fall under that cap. So the setting does not reliably protect the payload it was chosen to protect."never"does; operators can still widen it per deployment via the env token.The scrub runs as the first statement in the filter, so the module's documented fail-open handler returns an already-scrubbed event. A privacy control that fails open is not one.
This applies to every Open edX deployment that installs
ol_openedx_sentry.What
Two independent ways learner data reaches Sentry, neither of them governed by
send_default_pii. Part of a ten-repo change on branchtmacey/sentry-scrub-request-bodies.1. HTTP request bodies are captured with no PII gate
request_info["data"]is set unconditionally inRequestExtractor.extract_into_event. The only control ismax_request_body_size, checked byrequest_body_within_bounds:neversmallmediumalwaysVerified against sentry-sdk source:
request_body_within_boundsdoes the bounds check,RequestExtractor.extract_into_eventsets the body unconditionally, and"medium"is themax_request_body_sizedefault. The repos in this change pin 1.9.0 to 2.68.0, and the behaviour is the same across them.The bound is checked against the declared
Content-Length, and a missing or malformed header counts as 0. Under WSGI, Django then reads 0 bytes, so nothing gets past it. Under ASGI, Django takesCONTENT_LENGTHfrom the header and reads the whole body, so on learn-ai and mit-learn a chunked request with noContent-Lengthis captured in full. The Starlette integration (ol-analytics-api) attaches no body when the header is absent.Which endpoints that actually means, measured in Sentry over the 30 days to 2026-09-08 (
dataset=errors,http.method:[POST,PUT,PATCH], grouped by transaction):PATCH /scim/v2/Users/{uuid}POST /api/v1/webhooks/content_files/POST /api/v1/enrollments/POST .../xblock/{usage_id}/handler/{handler}/POST /cms/pages/{page_id}/edit/POST /api/profile/details/POST /api/checkout/result/POST /api/checkout/redeem_discount/Enrollment, checkout, profile edits, SCIM user attributes, and xblock handler submissions — governed by a knob nobody set.
2. Postgres
DETAIL:lines leak rows through the exception textA constraint violation carries a
DETAIL: Failing row contains (...)line reproducing the whole offending row, and psycopg puts it instr(exc). It therefore ships inside the exception value, where no SDK privacy option reaches it —send_default_piigoverns user/cookie/header capture andmax_request_body_sizegoverns bodies; neither touches exception text.Measured on MITXONLINE-6PK: a SCIM
PATCHIntegrityErroronusers_userwhoseDETAILline reproduces a learner email address three times per event. First seen 2026-05-27, 46,764 occurrences, last seen 2026-09-08 — still firing.before_sendnow walks the whole serialized event and truncates any string at itsDETAIL:line, whether the newline arrives raw or as a literal\n(the SDKrepr()s frame locals and non-string log params beforebefore_sendruns). It keeps the primary error that names the failure and drops the row echo plus anyHINT/CONTEXTafter it.Testing
Unit tests next to the module use a real MITXONLINE-6PK exception value with the learner identifiers replaced. They assert the primary message survives, the email and external UUID do not, and
HINT/CONTEXTare dropped, for both the rawDETAIL:line and the repr'd form the SDK produces for frame locals and log params. One test goes through a real SDK client, withsentry_event_filterand a fake transport, and checks that no outgoing event carries the email. Against the pre-review module, 4 of the tests fail, that one included.Not in scope
send_default_piiand the salted-user-hash work are deliberately untouched here — different knob, different failure mode, tracked separately.🤖 Generated with Claude Code
https://claude.ai/code/session_01RVJKTGk9KHUTN2xfU59ujX
The other nine PRs
Same branch name (
tmacey/sentry-scrub-request-bodies) in each repo. Independent — no merge order required.neversmallsmallsmallsmallsmallsmall(asrequest_bodies, SDK 1.9.0)smallsmallsmall