Skip to content

fix: scope filter-based bulk user delete to match listing filters - #7021

Merged
Shivam-nagar23 merged 2 commits into
mainfrom
fix/bulk-delete-filter-scope
Sep 7, 2026
Merged

Shivam-nagar23 merged 2 commits into
mainfrom
fix/bulk-delete-filter-scope

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown

Requested by Shivam Nagar, Pawan Kumar · Slack thread

Fixes #7020

Description

Before: DELETE /orchestrator/user/bulk accepts a listingRequest filter ({searchKey, status}) in place of explicit ids, used by the UI's "select all matching results across pages" bulk-delete flow. The backend resolved that filter into a delete target list via getUserIdsHonoringFilters, which called the same query builder the listing endpoint uses (GetQueryForUserListingWithFilters) but skipped the normalization the listing endpoint (GetAllWithFilters) applies first — SetDefaultValuesIfNotPresent (default page size) and setStatusFilterType (status filtering). With no default Size, the query builder emitted no LIMIT; combined with the skipped status filtering, a request such as {"listingRequest":{"searchKey":"","status":["inactive"]}} — intended to delete only inactive users matching an (empty) search — resolved to every active, non-API-token user. This is exactly what happened in the incident this PR fixes: an admin intending to bulk-delete 108 inactive users instead deactivated every user account on the platform.

After: filter resolution for the delete path is normalized and scoped identically to the listing path, and a filter-based bulk delete is rejected outright unless it carries at least one concrete filter field — an empty/default filter can no longer silently widen into "delete everyone".

How

In pkg/auth/user/UserService.go, getUserIdsHonoringFilters now:

  1. Rejects the request (400, util.ApiError) if it carries no concrete filter (empty SearchKey and no status filter applied) — no filter means no filter-based bulk delete.
  2. Calls impl.userCommonService.SetDefaultValuesIfNotPresent(request, false) and setStatusFilterType(request) before building the query via helper.GetQueryForUserListingWithFilters, exactly mirroring what GetAllWithFilters (the listing endpoint) does before calling the same query builder.

Added isStatusFilterApplied next to the existing setStatusFilterType OSS/enterprise hook in UserService_ent.go (a no-op returning false in this OSS build, matching the existing convention for enterprise-extendable filter behavior) so the "no concrete filter" check has a forward-compatible way to recognize a status filter if/when one is added.

The explicit ids-based delete path (deleteUsersByIds) is untouched — it already worked correctly per the original investigation.

Testing

  • Added pkg/auth/user/UserService_bulkdelete_filter_test.go:
    • TestGetUserIdsHonoringFilters_RejectsUnscopedFilter — regression test for the exact incident payload (SearchKey: "", no other concrete filter): asserts the call is now rejected and the repository query is never executed.
    • TestGetUserIdsHonoringFilters_MatchesListingQuery — asserts that once a concrete filter (SearchKey) is present, the exact query text and params getUserIdsHonoringFilters builds are identical to what the listing endpoint (GetAllWithFilters) would build for the same input.
  • go build ./pkg/auth/user/... and go build ./... succeed (the one pre-existing build failure, an unrelated missing vendored Azure DevOps module in pkg/deployment/gitOps/git, reproduces identically on a clean main checkout before this change).
  • go test ./pkg/auth/user/... -run TestGetUserIdsHonoringFilters -v -vet=off passes both new tests. Note: this package's two pre-existing test files (UserService_test.go, UserFlows_test.go) already fail to compile against the current UserServiceImpl/UserCommonServiceImpl APIs independent of this change (confirmed by reproducing the identical compile errors on a clean checkout before any edits here) — that pre-existing breakage is out of scope for this fix, and this package isn't part of the repo's make test-unit target (which only runs ./pkg/pipeline) so it isn't caught by CI either. gofmt -l is clean on all changed files.

Checklist:

  • The title of the PR states what changed and the related issues number (used for the release note).
  • Does this PR requires documentation updates?
  • I've updated documentation as required by this PR.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have tested it for all user roles.
  • I have added all the required unit/api test cases.

Does this PR introduce a user-facing change?

Fixed a bug where a filter-based bulk user delete (e.g. "select all across pages" then delete) could delete far more users than the filter implied, up to every user on the platform. Filter-based bulk delete is now scoped identically to the user listing filters and requires at least one concrete filter.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J5DesTG5XvPdmPCDuL6izq


Generated by Claude Code

DELETE /orchestrator/user/bulk with a listingRequest filter (instead of
explicit ids) resolved the delete target list via the same query
builder the listing endpoint uses, but skipped the normalization the
listing endpoint applies first (SetDefaultValuesIfNotPresent,
setStatusFilterType). With no default page size and no status
filtering applied, a filter such as {"searchKey":"","status":["inactive"]}
resolved to every active user instead of the intended filtered subset,
causing a bulk delete of far more users than intended.

getUserIdsHonoringFilters now applies the same normalization the
listing path (GetAllWithFilters) applies before building the query,
and rejects a filter-based bulk delete that carries no concrete filter
criteria instead of letting it silently resolve to "every user". The
explicit ids-based delete path is unchanged.

Fixes #7020

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5DesTG5XvPdmPCDuL6izq
@bito-code-review

Copy link
Copy Markdown

Bito Automatic Review Skipped - Draft PR

Bito didn't auto-review because this pull request is in draft status.
No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change draft PR review settings here, or contact your Bito workspace admin at shivam@devtron.ai.

@claude
claude Bot marked this pull request as ready for review September 4, 2026 03:42
@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Author

/review


Generated by Claude Code

@sonarqubecloud

sonarqubecloud Bot commented Sep 7, 2026

Copy link
Copy Markdown

@Shivam-nagar23
Shivam-nagar23 merged commit 5916f39 into main Sep 7, 2026
10 checks passed
@Shivam-nagar23
Shivam-nagar23 deleted the fix/bulk-delete-filter-scope branch September 7, 2026 06:19
Ash-exp pushed a commit that referenced this pull request Sep 22, 2026
…lters

getUserIdsHonoringFilters (used by both BulkDeleteUsers and enterprise's
BulkUpdateStatus) resolved listingRequest filters directly through the
query builder without the normalization the listing endpoint
(GetAllWithFilters) applies, and had no guard against an effectively
empty filter. A request with an empty searchKey and no status filter
would silently widen into "every user", as it did in production.

Ports the OSS fix from #7021 (commit
4b2bf38) to this repo's own copy of
the same code: reject a filter-based bulk operation with no concrete
filter, and apply SetDefaultValuesIfNotPresent/setStatusFilterType
before building the query, exactly as the listing path does. Adds
isStatusFilterApplied here reflecting enterprise's own real status
filter (a non-empty Status slice), rather than the OSS stub which
always returns false.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5DesTG5XvPdmPCDuL6izq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Filter-based bulk user delete (DELETE /orchestrator/user/bulk) deletes far more users than the filter implies

3 participants