Skip to content

chore(integrations): log Jira issue.updated webhook payloads behind a flag#114822

Open
hobzcalvin wants to merge 1 commit intomasterfrom
log-jira-issue-updated-webhook
Open

chore(integrations): log Jira issue.updated webhook payloads behind a flag#114822
hobzcalvin wants to merge 1 commit intomasterfrom
log-jira-issue-updated-webhook

Conversation

@hobzcalvin
Copy link
Copy Markdown
Contributor

Adds a temporary organizations:jira-issue-updated-payload-logging Flagpole feature that, when enabled for a linked org, causes the Jira Cloud issue.updated webhook handler to log the full webhook payload plus a focused log line whenever the changelog contains a project change. We need this data to design issue-link rewriting when a Jira issue is moved between projects.

Resolves ISWF-1512

… flag

Adds a temporary `organizations:jira-issue-updated-payload-logging`
Flagpole feature that, when enabled for a linked org, causes the Jira
Cloud `issue.updated` webhook handler to log the full webhook payload
plus a focused log line whenever the changelog contains a `project`
change. We need this data to design issue-link rewriting when a Jira
issue is moved between projects.
@hobzcalvin hobzcalvin requested review from a team as code owners May 4, 2026 23:44
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 4, 2026

@hobzcalvin hobzcalvin requested a review from GabeVillalobos May 4, 2026 23:45
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label May 4, 2026
Comment on lines +114 to +116
"issue_id": issue.get("id"),
"webhook_event": data.get("webhookEvent"),
"changed_fields": [item.get("field") for item in changelog_items],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Full Jira webhook payload logged to durable operational sink

The handler logs the entire Jira issue.updated webhook body ("payload": data) plus assignee/reporter fields nested in fields to the application logger. Jira issue payloads routinely contain real user PII (assignee/reporter emailAddress, displayName, accountId), customer org identifiers, and free-form issue summaries/descriptions that may contain customer-confidential content. Sending the raw payload to logs (a durable, vendor-visible sink) exposes user and customer data well beyond what the feature requires, which is project-change metadata.

Verification

Traced the data flow: data = request.data is the parsed Jira webhook body. Jira Cloud jira:issue_updated webhooks include issue.fields.assignee, issue.fields.reporter, issue.fields.creator with emailAddress/displayName/accountId, plus summary/description free text and user (the actor) with email. The new extra dict embeds the entire data under payload and additionally embeds fields.project (which together with the issue context links to a named customer's Jira instance). The logger is the standard module logger, which routes to durable operational logging. The skill explicitly calls out logging request.body/webhook payloads/identity-provider payloads as a high-severity privacy sink.

Identified by Warden wrdn-pii · JEH-S4Q

Comment on lines +33 to +42
def _payload_logging_enabled(integration_id: int) -> bool:
"""True if any org linked to this Jira integration has the
`jira-issue-updated-payload-logging` feature enabled.

A Jira integration can be shared by multiple Sentry orgs, and
`features.has` needs an `Organization`, so we have to walk the linked
`OrganizationIntegration` rows and look each org up.
"""
contexts = integration_service.organization_contexts(integration_id=integration_id)
for oi in contexts.organization_integrations:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The function _payload_logging_enabled introduces an N+1 database query pattern in the Jira issue.updated webhook handler, causing unnecessary latency on every request.
Severity: MEDIUM

Suggested Fix

The organization_id values are already available in the contexts.organization_integrations object. Use these IDs directly for the feature flag check. Alternatively, fetch all required organization objects in a single batch query instead of making individual database calls in a loop.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry/integrations/jira/webhooks/issue_updated.py#L33-L42

Potential issue: The function `_payload_logging_enabled` is called unconditionally on
every `issue.updated` webhook request, introducing an N+1 database query pattern. It
first fetches all `OrganizationIntegration` rows for the integration, then issues a
separate `organization_service.get_organization_by_id()` call for each linked
organization. This overhead occurs on every webhook even when the associated feature
flag is disabled for all linked organizations, adding unnecessary latency to a
high-frequency webhook handler.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 2 potential issues.

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 7bfa989. Configure here.

logger.info(
"jira.issue-updated.project-changed",
extra={**payload_extra, "project_change": project_change},
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unprotected diagnostic code can break webhook processing

Medium Severity

The call to _payload_logging_enabled on every webhook request makes multiple RPC calls (integration_service.organization_contexts + N × organization_service.get_organization_by_id) that are not wrapped in a try/except. If any of these calls raises an exception (e.g., transient database/network error), it propagates up and the dispatch method returns a 500, causing handle_assignee_change and handle_status_change to be skipped entirely. Temporary diagnostic code can thus break real webhook processing.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7bfa989. Configure here.

id=oi.organization_id, include_teams=False, include_projects=False
)
if org and features.has(PAYLOAD_LOGGING_FEATURE, org.organization):
return True
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Redundant RPC calls on every webhook request

Medium Severity

_payload_logging_enabled calls integration_service.organization_contexts on every webhook request, duplicating the same RPC call already made by bind_org_context_from_integration a few lines earlier. It then makes N additional organization_service.get_organization_by_id calls (one per linked org). All of this runs on every issue.updated webhook regardless of whether the feature is enabled for any org, adding unnecessary latency and load to a high-throughput endpoint.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7bfa989. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Backend Test Failures

Failures on 2fcaa1e in this run:

tests/snuba/api/endpoints/test_organization_events_timeseries_spans.py::OrganizationEventsStatsSpansMetricsEndpointTest::test_handle_nans_from_snuba_top_nlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_timeseries_spans.py:212: in test_handle_nans_from_snuba_top_n
    assert timeseries["values"] == build_expected_timeseries(
E   AssertionError: assert [{'confidence...ne, ...}, ...] == [{'confidence...Y>, ...}, ...]
E     
E     At index 6 diff: {'timestamp': 1777852800000, 'value': 2.0, 'incomplete': True, 'incompleteReason': 'INCOMPLETE_BUCKET', 'sampleCount': 1, 'sampleRate': 1.0, 'confidence': 'low'} != {'incomplete': False, 'timestamp': 1777852800000.0, 'value': 2, 'sampleCount': <ANY>, 'sampleRate': <ANY>, 'confidence': <ANY>}
E     
E     Full diff:
E       [
E           {
E     -         'confidence': <ANY>,
E     ?                       -- ^^
E     +         'confidence': None,
E     ?                        ^^^
E               'incomplete': False,
E     -         'sampleCount': <ANY>,
E     ?                        ^^^^^
E     +         'sampleCount': 0,
E     ?                        ^
E     -         'sampleRate': <ANY>,
E     ?                       -- ^^
E     +         'sampleRate': None,
E     ?                        ^^^
E     -         'timestamp': 1777334400000.0,
E     ?                                   --
E     +         'timestamp': 1777334400000,
E     -         'value': 0,
E     +         'value': 0.0,
E     ?                  ++
E           },
E           {
E     -         'confidence': <ANY>,
E     ?                       -- ^^
E     +         'confidence': None,
E     ?                        ^^^
E               'incomplete': False,
E     -         'sampleCount': <ANY>,
E     ?                        ^^^^^
E     +         'sampleCount': 0,
E     ?                        ^
E     -         'sampleRate': <ANY>,
E     ?                       -- ^^
E     +         'sampleRate': None,
E     ?                        ^^^
E     -         'timestamp': 1777420800000.0,
E     ?                                   --
E     +         'timestamp': 1777420800000,
E     -         'value': 0,
E     +         'value': 0.0,
E     ?                  ++
... (111 more lines)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant