Skip to content

fix(scim): return 400 for malformed startIndex and count - #1007

Open
omlahore wants to merge 1 commit into
phasehq:mainfrom
omlahore:fix/scim-pagination-params
Open

fix(scim): return 400 for malformed startIndex and count#1007
omlahore wants to merge 1 commit into
phasehq:mainfrom
omlahore:fix/scim-pagination-params

Conversation

@omlahore

@omlahore omlahore commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Both SCIM list endpoints parse their pagination parameters with a bare int():

start_index = max(int(request.GET.get("startIndex", 1)), 1)
count = min(int(request.GET.get("count", SCIM_DEFAULT_COUNT)), SCIM_DEFAULT_COUNT)

ee/authentication/scim/views/users.py:138 and views/groups.py:235. Those values come straight from the SCIM client, nothing upstream catches ValueError, and the result is a 500.

Three ways in:

?startIndex=abc   ValueError: invalid literal for int() with base 10: abc
?count=           ValueError: invalid literal for int() with base 10: 
?count=-5         ValueError: Negative indexing is not supported.

The third one is the least obvious. A negative count survives min(-5, 100) and reaches the queryset as qs[offset : offset + count], and QuerySet.__getitem__ rejects a negative slice bound.

This matters a bit more than a normal 400/500 mixup because the caller is an IdP. Okta and Entra treat a 5xx as a transient provisioning failure and retry it, where a 400 tells them the request itself is wrong.

The change

Both call sites go through a new parse_pagination_params in scim/utils.py. It returns a SCIM 400 with scimType=invalidValue for a non-integer, clamps startIndex to 1 and a negative count to 0, per RFC 7644 section 3.4.2.4.

I did not invent the shape of any of this. views/audit.py:83-90 already wraps the same two int() calls in try/except (ValueError, TypeError), and _list_users already returns scim_invalid_filter(...) two lines below for a bad filter. This just applies the handling that is already there to the parameters that were missed.

Tests

backend/tests/ee/authentication/scim/test_pagination_params.py, 13 cases: defaults, valid values, non-integer startIndex and count across several shapes, the clamp to 1, the negative count, and the cap at SCIM_DEFAULT_COUNT.

pytest tests/ee/authentication/scim/ is 200 passed, so nothing regressed.

On CI

The image build job will go red on this the way it does on my other PRs. docker/login-action gets Secret source: None on fork PRs and fails before any build step. The frontend and backend test jobs are the ones that matter here, and this change is backend only.

_list_users and _list_groups parsed the SCIM pagination parameters with a
bare int(), so any non-integer value from the client raised ValueError and
surfaced as a 500. A negative count was worse: it reached the queryset as
qs[offset : offset + count] and Django rejects a negative slice bound with
ValueError too.

Both now go through parse_pagination_params, which reports a bad value as a
SCIM 400 with scimType=invalidValue, clamps startIndex to 1 and a negative
count to 0 per RFC 7644 section 3.4.2.4. views/audit.py already guarded its
int() calls this way; this brings the SCIM endpoints in line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant