Skip to content

fix(gitlab): URL-encode project IDs for issue creation and lookup#114287

Open
sentry[bot] wants to merge 1 commit intomasterfrom
seer/fix/gitlab-issue-creation-404
Open

fix(gitlab): URL-encode project IDs for issue creation and lookup#114287
sentry[bot] wants to merge 1 commit intomasterfrom
seer/fix/gitlab-issue-creation-404

Conversation

@sentry
Copy link
Copy Markdown
Contributor

@sentry sentry Bot commented Apr 29, 2026

GitLab issue creation failed with a 404 Not Found error when a project was selected via the autocomplete search. The root cause was that the handle_search_repositories endpoint in src/sentry/integrations/gitlab/search.py was returning the project["path_with_namespace"] (e.g., group/subgroup/project) as the project identifier value. This path, containing unencoded slashes, was then used directly in the GitLab API request URL by GitLabApiClient.create_issue and GitLabApiClient.get_project without proper URL-encoding, leading to a malformed request and a 404 from GitLab.

This fix addresses the issue in two ways:

  1. Corrected Autocomplete Value: Modified handle_search_repositories in src/sentry/integrations/gitlab/search.py to return str(project["id"]) as the value for project choices. This ensures that the autocomplete consistently provides numeric project IDs, aligning with the expected format for GitLab API calls and preventing path-based identifiers from being submitted.
  2. Defense-in-Depth URL Encoding: Added from urllib.parse import quote and applied quote(str(project), safe="") to the project argument in GitLabApiClient.create_issue and GitLabApiClient.get_project in src/sentry/integrations/gitlab/client.py. This ensures that even if a path-based project ID (e.g., from a stale saved default in project_issue_defaults) is passed, it will be correctly URL-encoded before being sent to the GitLab API. This mirrors existing safe practices in other GitLab client methods like search_project_issues.

This resolves the 404 for users who previously had path-based project IDs stored as defaults or who selected projects via the autocomplete search.

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

Fixes SENTRY-5P1P

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 29, 2026
@rodolfoBee
Copy link
Copy Markdown
Member

The original error came from this ticket.

In this case, Sentry is using the following format for API requests:
https://gitlab.com/api/v4/projects/helpap44/VIE/praterstern/issues

Which is incorrect per GitLab api. The following formats are correct (URL encoded or projectID):
https://gitlab.com/api/v4/projects/helpap44%2FVIE%2Fpraterstern/issues
https://gitlab.com/api/v4/projects/[PROJECT_ID]/issues

@github-actions
Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 7ccb8b4 in this run:

tests/sentry/integrations/gitlab/test_search.py::GitlabSearchTest::test_finds_project_resultslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/gitlab/test_search.py:97: in test_finds_project_results
    assert resp.data == [
E   AssertionError: assert [{'label': 'G...'value': '2'}] == [{'label': 'G...ry2/sentry2'}]
E     
E     At index 0 diff: {'label': 'GetSentry / Sentry', 'value': '1'} != {'value': 'getsentry/sentry', 'label': 'GetSentry / Sentry'}
E     
E     Full diff:
E       [
E           {
E               'label': 'GetSentry / Sentry',
E     -         'value': 'getsentry/sentry',
E     +         'value': '1',
E           },
E           {
E               'label': 'GetSentry2 / Sentry2',
E     -         'value': 'getsentry2/sentry2',
E     +         'value': '2',
E           },
E       ]
tests/sentry/integrations/gitlab/test_search.py::GitlabSearchTest::test_finds_project_results_with_paginationlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/gitlab/test_search.py:144: in test_finds_project_results_with_pagination
    assert resp.data[0] == {"value": "getsentry/sentry", "label": "GetSentry / Sentry"}
E   AssertionError: assert {'label': 'Ge... 'value': '1'} == {'label': 'Ge...entry/sentry'}
E     
E     Omitting 1 identical items, use -vv to show
E     Differing items:
E     {'value': '1'} != {'value': 'getsentry/sentry'}
E     
E     Full diff:
E       {
E           'label': 'GetSentry / Sentry',
E     -     'value': 'getsentry/sentry',
E     +     'value': '1',
E       }

@rodolfoBee rodolfoBee marked this pull request as ready for review April 29, 2026 10:15
@rodolfoBee rodolfoBee requested review from a team as code owners April 29, 2026 10:15
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 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 7896b73. Configure here.

return self.get(GitLabApiClientPath.project.format(project=project_id))
return self.get(
GitLabApiClientPath.project.format(project=quote(str(project_id), safe=""))
)
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.

Missing URL encoding in get_issue breaks defense-in-depth

Medium Severity

The defense-in-depth quote() encoding was added to get_project and create_issue but not to get_issue, which has the same vulnerability. In issues.py, the get_issue flow calls client.get_issue(project_id, issue_num) followed by client.get_project(project_id) inside the same try block. When project_id is a path-based identifier (from a stored external issue key containing path_with_namespace), get_issue will still 404 because its project_id isn't URL-encoded, and the now-fixed get_project call on the next line is never reached due to the exception.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7896b73. Configure here.

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