Skip to content

[WEB-8110] fix: sanitize page list order_by against an allowlist (GHSA-2v48)#9387

Open
mguptahub wants to merge 2 commits into
previewfrom
web-8110/page-order-by-allowlist
Open

[WEB-8110] fix: sanitize page list order_by against an allowlist (GHSA-2v48)#9387
mguptahub wants to merge 2 commits into
previewfrom
web-8110/page-order-by-allowlist

Conversation

@mguptahub

@mguptahub mguptahub commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes the app project-pages residual of GHSA-2v48-qcjw-74ch (HIGH).

Problem

PageViewSet.get_queryset passed the raw order_by query param straight into .order_by(self.request.GET.get("order_by", "-created_at")). In Django 4.2, .order_by(<field>) resolves field names at call time, so:

  • an unknown field (order_by=password) raises FieldError500 DoS;
  • a valid relation path (order_by=owned_by__password) performs ORM relational traversal.

Verified empirically against origin/preview (500 on password/bogus__field; 200 traversal on owned_by__password).

Fix

Add PAGE_ORDER_BY_ALLOWLIST to utils/order_queryset.py and wrap the param with the existing sanitize_order_by() before it reaches .order_by() — the same pattern already used by the issue/project/view/notification endpoints. Unknown or malformed values fall back to the safe -created_at default.

Scope

This covers only the app project-pages residual. The 3 external-REST-API sites in the advisory are handled by #9348. EE Wiki counterpart: WEB-8111 (plane-ee). EE project pages are already protected via _safe_order_by.

Tests

tests/contract/app/test_page_order_by_allowlist_app.py — 10 tests (invalid field / relation-path rejected → 200, allowlisted accepted, default). Fail-before verified (invalid fields return 500 before the fix). ruff + manage.py check green.

Summary by CodeRabbit

  • Bug Fixes
    • Improved page list sorting to validate and sanitize the order_by parameter against an allowlist.
    • Prevented unsafe sorting inputs from causing errors or attempting disallowed field traversal.
    • Preserved approved sorting options (including ascending/descending variants) and applied a safe default when omitted.
    • Ensured consistent ordering with pinned favorites and a stable pagination tie-break.

…A-2v48)

PageViewSet.get_queryset passed the raw order_by query param into
.order_by(). In Django 4.2 .order_by() resolves field names at call time,
so an unknown field (e.g. order_by=password) raises FieldError → 500 DoS,
and a valid relation path (e.g. order_by=owned_by__password) enables ORM
relational traversal (GHSA-2v48-qcjw-74ch).

Add PAGE_ORDER_BY_ALLOWLIST to utils/order_queryset.py and wrap the param
with the existing sanitize_order_by() before it reaches .order_by(),
matching the issue/project/view/notification endpoints. Unknown or
malformed values fall back to the safe -created_at default.

Covers only the app project-pages residual; the 3 external-REST-API sites
in the advisory are handled by PR #9348. EE Wiki counterpart: WEB-8111.

Add contract regression tests (fail-before verified).

Co-authored-by: Plane AI <noreply@plane.so>
@mguptahub mguptahub requested a review from dheeru0198 as a code owner July 10, 2026 04:07
Copilot AI review requested due to automatic review settings July 10, 2026 04:07
@makeplane

makeplane Bot commented Jul 10, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62741268-e4e0-4c31-a445-34397c65bf88

📥 Commits

Reviewing files that changed from the base of the PR and between 96b69d0 and 0c8e115.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/page/base.py
  • apps/api/plane/tests/contract/app/test_page_order_by_allowlist_app.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/plane/tests/contract/app/test_page_order_by_allowlist_app.py
  • apps/api/plane/app/views/page/base.py

📝 Walkthrough

Walkthrough

Page list ordering now uses an explicit allowlist and sanitization before applying query parameters. Contract tests cover malicious, allowlisted, descending, omitted, and effective order_by values.

Changes

Page order_by hardening

Layer / File(s) Summary
Ordering contract and queryset wiring
apps/api/plane/utils/order_queryset.py, apps/api/plane/app/views/page/base.py
Defines allowed page ordering fields and sanitizes order_by before applying it to the page queryset, while preserving favorite priority and adding stable ID tie-breaking.
Ordering regression coverage
apps/api/plane/tests/contract/app/test_page_order_by_allowlist_app.py
Adds fixtures and contract tests for unsafe values, allowlisted ascending and descending values, omitted parameters, and actual name-based response ordering.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: pablohashescobar, Saurabhkmr98, dheeru0198

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely matches the main change: sanitizing page list order_by against an allowlist.
Description check ✅ Passed The description covers the problem, fix, scope, and tests, but it doesn't use every template section such as Type of Change or References.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch web-8110/page-order-by-allowlist

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

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.

Pull request overview

This PR addresses a security hardening gap in the app “project pages” list endpoint by adding an allowlisted sanitizer for the order_by query parameter before it reaches Django ORM .order_by(), preventing invalid-field FieldError (500) and relational-path traversal attempts.

Changes:

  • Introduces PAGE_ORDER_BY_ALLOWLIST alongside existing order-by allowlists in utils/order_queryset.py.
  • Applies sanitize_order_by() to the order_by query param in PageViewSet.get_queryset.
  • Adds contract regression tests covering malicious and allowlisted order_by values for the pages endpoint.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
apps/api/plane/utils/order_queryset.py Adds PAGE_ORDER_BY_ALLOWLIST to support safe page list ordering sanitization.
apps/api/plane/app/views/page/base.py Sanitizes the incoming order_by param before passing it to Django .order_by().
apps/api/plane/tests/contract/app/test_page_order_by_allowlist_app.py Adds contract regression coverage for injected and allowlisted order_by inputs.

Comment thread apps/api/plane/app/views/page/base.py Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/plane/app/views/page/base.py`:
- Around line 104-115: Merge the queryset’s duplicate .order_by() calls into one
call, preserving "-is_favorite" as the primary sort and using
sanitize_order_by(self.request.GET.get("order_by", "-created_at"),
PAGE_ORDER_BY_ALLOWLIST, default="-created_at") as the user-controlled fallback;
remove the later overriding .order_by("-is_favorite", "-created_at") call.

In `@apps/api/plane/tests/contract/app/test_page_order_by_allowlist_app.py`:
- Around line 35-69: Add direct unit coverage for sanitize_order_by using
PAGE_ORDER_BY_ALLOWLIST, including valid ascending/descending fields, unknown
and relation paths, double-dash, empty/None, and whitespace inputs with expected
default fallback. Strengthen TestPageOrderByAllowlist integration coverage by
using a fixture that creates multiple distinctly named pages and asserting
response ordering for ascending and descending order_by values, rather than
checking only status_code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b5bf425-96c5-4b16-bb63-561bfb68618e

📥 Commits

Reviewing files that changed from the base of the PR and between dc9d80b and 96b69d0.

📒 Files selected for processing (3)
  • apps/api/plane/app/views/page/base.py
  • apps/api/plane/tests/contract/app/test_page_order_by_allowlist_app.py
  • apps/api/plane/utils/order_queryset.py

Comment thread apps/api/plane/app/views/page/base.py Outdated
… (review)

Address CodeRabbit + Copilot on #9387: the sanitized .order_by(user) was
immediately overridden by a later .order_by("-is_favorite", "-created_at"),
so the order_by param had no effect on the result (dead code) and cost an
extra query-build step.

Merge them into one .order_by("-is_favorite", <sanitized>, "id") — matching
the EE project-pages viewset — so favourites stay pinned first, the
allowlisted user ordering actually applies as the secondary sort, and id is
a stable pagination tiebreak. The no-param default is unchanged
(-created_at). Add a test asserting order_by=name / -name actually reorders
the results.

Co-authored-by: Plane AI <noreply@plane.so>
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.

2 participants