Skip to content

Commit 75192ba

Browse files
billyvgclaude
andcommitted
feat(integrations): Restrict webhook headers to Authorization and X-*
Narrow the custom webhook header allow list to only the Authorization header and X-* custom headers. Previously User-Agent, Accept, Date, and Prefer were also permitted; these are removed so callers can only set an auth credential and their own namespaced custom headers. The X-* allowance is unchanged (it is a separate prefix check), and the reserved-header guards still take precedence, so headers like X-Forwarded-* and X-Sentry-* remain blocked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 069446e commit 75192ba

2 files changed

Lines changed: 8 additions & 17 deletions

File tree

src/sentry/sentry_apps/api/parsers/sentry_app.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
from sentry.sentry_apps.utils.webhooks import VALID_EVENT_RESOURCES
1717
from sentry.utils.display_name_filter import is_spam_display_name
1818

19-
# Custom webhook headers are intentionally limited to application-level metadata
20-
# and common custom-header names. Names are compared case-insensitively.
21-
ALLOWED_WEBHOOK_HEADERS = frozenset({"authorization", "user-agent", "accept", "date", "prefer"})
19+
# Custom webhook headers are intentionally limited to Authorization and X-*
20+
# custom headers. Names are compared case-insensitively.
21+
ALLOWED_WEBHOOK_HEADERS = frozenset({"authorization"})
2222

2323
# RFC 7230 §3.2.6 — header field names are "tokens": letters, digits, and
2424
# the limited punctuation set below. Excludes separators and control chars.
@@ -256,8 +256,8 @@ def validate_webhookHeaders(self, value):
256256
raise ValidationError(f"'{name}' is a reserved header and cannot be overridden.")
257257
if normalized not in ALLOWED_WEBHOOK_HEADERS and not normalized.startswith("x-"):
258258
raise ValidationError(
259-
f"'{name}' is not an allowed webhook header. Use Authorization, "
260-
"User-Agent, Accept, Date, Prefer, or X-* custom headers."
259+
f"'{name}' is not an allowed webhook header. Use Authorization "
260+
"or X-* custom headers."
261261
)
262262
# Reject duplicate names (case-insensitive). This keeps the masked-value
263263
# round-trip unambiguous: the updater re-pairs masked entries to stored

tests/sentry/sentry_apps/api/endpoints/test_sentry_app_details.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -752,29 +752,20 @@ def test_set_webhook_headers(self) -> None:
752752
webhookHeaders=[
753753
"Authorization: Bearer token",
754754
"X-Example: value",
755-
"User-Agent: custom-agent",
756-
"Accept: application/json",
757-
"Date: Tue, 10 Jun 2026 12:00:00 GMT",
758-
"Prefer: respond-async",
755+
"X-Custom-Header: another",
759756
],
760757
status_code=200,
761758
)
762759
self.published_app.refresh_from_db()
763760
assert self.published_app.webhook_headers == [
764761
"Authorization: Bearer token",
765762
"X-Example: value",
766-
"User-Agent: custom-agent",
767-
"Accept: application/json",
768-
"Date: Tue, 10 Jun 2026 12:00:00 GMT",
769-
"Prefer: respond-async",
763+
"X-Custom-Header: another",
770764
]
771765
assert response.data["webhookHeaders"] == [
772766
f"Authorization: {MASKED_VALUE}",
773767
f"X-Example: {MASKED_VALUE}",
774-
f"User-Agent: {MASKED_VALUE}",
775-
f"Accept: {MASKED_VALUE}",
776-
f"Date: {MASKED_VALUE}",
777-
f"Prefer: {MASKED_VALUE}",
768+
f"X-Custom-Header: {MASKED_VALUE}",
778769
]
779770

780771
@override_options({"staff.ga-rollout": True})

0 commit comments

Comments
 (0)