ref(integrations): route HTTP header filtering through data collection config#6788
3 issues
Medium
IP headers (X-Forwarded-For / X-Real-Ip) leak when data_collection is enabled with default header denylist - `sentry_sdk/integrations/_wsgi_common.py:7-9`
When a user supplies an explicit data_collection config, _filter_headers routes header scrubbing through _apply_key_value_collection_filtering. If http_headers.request is omitted, _http_headers_from_value resolves it to {"mode": "denylist"} with no extra terms, so only the built-in _SENSITIVE_DENYLIST applies. That denylist contains none of the terms needed to match x-forwarded-for or x-real-ip, so those IP-address headers (PII) pass through unfiltered. This is a regression relative to the send_default_pii-derived mapping (_map_from_send_default_pii), which — when PII is off — explicitly adds the terms ["forwarded", "-ip", "remote-", "via", "-user"] and the legacy SENSITIVE_HEADERS path which scrubs X_FORWARDED_FOR and X_REAL_IP. A user who enables data_collection for an unrelated purpose (e.g. cookies) inadvertently starts sending client IP headers that were previously scrubbed.
Low
`use_annotated_value` ignored in data-collection branch of `_filter_headers` - `sentry_sdk/integrations/_wsgi_common.py:222-235`
When data collection is enabled, _filter_headers delegates to _apply_key_value_collection_filtering without passing a substitute, so filtered/sensitive header values always become the string SENSITIVE_DATA_SUBSTITUTE regardless of the use_annotated_value argument. The legacy else branch instead uses AnnotatedValue.removed_because_over_size_limit() when use_annotated_value=True. Data is still scrubbed in both cases, so this is not a leak, but event-processor callers (e.g. wsgi.py:357, aiohttp.py:465, aws_lambda.py:415, quart.py:250, tornado.py:279, sanic.py:427, gcp.py:243) that rely on the default use_annotated_value=True will silently lose the AnnotatedValue _meta annotation whenever data_collection is configured. Consider deriving a substitute from use_annotated_value and passing it to _apply_key_value_collection_filtering (and the cookie loop) to preserve consistent behavior.
`url.full`, `url.path`, `http.query`, and `client.address` are dropped in `_get_request_attributes` when only `data_collection` is configured - `tests/integrations/asgi/test_asgi.py:875-906`
_get_request_attributes in _asgi_common.py gates http.query, url.path, url.full, and client.address behind should_send_default_pii(). That helper reads only the raw send_default_pii option and returns False when a user configures data_collection alone (without send_default_pii). Because this branch routes only HTTP header filtering through data_collection while these URL/query/client attributes still key off send_default_pii, a user who adopts data_collection as a replacement for send_default_pii will silently lose these OTel span attributes even though their data_collection config (e.g. query_params defaults to collect) implies they should be captured. The two new tests mask this gap by passing send_default_pii=True alongside data_collection, which also emits an unasserted DeprecationWarning from _resolve_data_collection. This is dropped telemetry (fail-safe direction), not over-collection, and likely reflects an incomplete migration, so severity is low.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| security-review | 0 | 2m 38s | $0.62 |
| code-review | 1 | 7m 16s | $2.95 |
| find-bugs | 2 | 24m 3s | $6.87 |
| skill-scanner | 0 | 11m 33s | $0.19 |
⏱ 45m 31s · 7.3M in / 310.4k out · $10.64