[WEB-8110] fix: sanitize page list order_by against an allowlist (GHSA-2v48)#9387
[WEB-8110] fix: sanitize page list order_by against an allowlist (GHSA-2v48)#9387mguptahub wants to merge 2 commits into
Conversation
…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>
|
Linked to Plane Work Item(s) References This comment was auto-generated by Plane |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughPage list ordering now uses an explicit allowlist and sanitization before applying query parameters. Contract tests cover malicious, allowlisted, descending, omitted, and effective ChangesPage order_by hardening
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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_ALLOWLISTalongside existing order-by allowlists inutils/order_queryset.py. - Applies
sanitize_order_by()to theorder_byquery param inPageViewSet.get_queryset. - Adds contract regression tests covering malicious and allowlisted
order_byvalues 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. |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
apps/api/plane/app/views/page/base.pyapps/api/plane/tests/contract/app/test_page_order_by_allowlist_app.pyapps/api/plane/utils/order_queryset.py
… (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>
Fixes the app project-pages residual of GHSA-2v48-qcjw-74ch (HIGH).
Problem
PageViewSet.get_querysetpassed the raworder_byquery 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:order_by=password) raisesFieldError→ 500 DoS;order_by=owned_by__password) performs ORM relational traversal.Verified empirically against
origin/preview(500 onpassword/bogus__field; 200 traversal onowned_by__password).Fix
Add
PAGE_ORDER_BY_ALLOWLISTtoutils/order_queryset.pyand wrap the param with the existingsanitize_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_atdefault.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 checkgreen.Summary by CodeRabbit
order_byparameter against an allowlist.