Skip to content

fix(security): scope cycle/module lookup by workspace in advance analytics chart - #9685

Open
jadhavgaurav wants to merge 1 commit into
makeplane:previewfrom
jadhavgaurav:fix/analytics-workspace-scope
Open

fix(security): scope cycle/module lookup by workspace in advance analytics chart#9685
jadhavgaurav wants to merge 1 commit into
makeplane:previewfrom
jadhavgaurav:fix/analytics-workspace-scope

Conversation

@jadhavgaurav

@jadhavgaurav jadhavgaurav commented Aug 26, 2026

Copy link
Copy Markdown

Description

ProjectAdvanceAnalyticsChartEndpoint.work_item_completion_chart (in apps/api/plane/app/views/analytic/project_analytics.py) looked up the Cycle/Module named by the cycle_id/module_id query params with a bare Cycle.objects.filter(id=cycle_id) / Module.objects.filter(id=module_id) — no workspace filter.

The CycleIssue/ModuleIssue querysets used to build the chart are correctly scoped via base_filters (which includes workspace__slug), so they return no issues for a foreign ID. But the Cycle/Module lookup itself was unscoped, so it succeeded for any cycle/module UUID in the database. Its start_date/end_date drive the date range used to build the per-day series in the response, so a member of workspace A could pass a cycle_id/module_id belonging to workspace B and get that foreign cycle's/module's start and end dates back, byte for byte, in the chart response — an IDOR that lets an attacker enumerate cycle/module date ranges across every workspace by iterating UUIDs.

Fix: scope both lookups with workspace__slug=self._workspace_slug, matching every other query in this file (base_filters always includes workspace__slug, and the sibling ProjectAdvanceAnalyticsEndpoint/StatsEndpoint scope their Cycle/Module counts the same way). A foreign ID now resolves to no cycle/module, so the existing "not found" branch returns the same {"data": [], "schema": {}} it already returns when a cycle/module has no start_date.

This is a minimal, two-line change (plus a regression test) — no new authorization mechanism, just closing the missing filter to match the existing pattern in the same file.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Screenshots and Media (if applicable)

N/A — backend-only authorization fix.

Test Scenarios

Added apps/api/plane/tests/contract/app/test_project_advance_analytics_workspace_scope_app.py:

  • A member of workspace A passing a foreign cycle_id from workspace B gets {"data": [], "schema": {}} — no leaked dates (fails without the fix, previously leaked the foreign cycle's real date range).
  • Same for a foreign module_id.
  • Positive control: a cycle_id belonging to the requester's own workspace still returns populated chart data.

Ran locally against a local Postgres instance:

pytest plane/tests/contract/app/test_project_advance_analytics_workspace_scope_app.py -vv
# 3 passed

Also ran the broader cycle/module/analytics contract suite (-k "cycle or module or analytic") — 13 passed, 4 pre-existing failures unrelated to this change (they fail identically on preview with or without this fix, due to a missing RabbitMQ broker / APP_BASE_URL in this local environment, not to any behavior this PR touches).

ruff check and ruff format --check pass on both changed files.

References

Fixes #9601


Disclosure: this change was prepared with AI assistance (Claude), then reviewed, tested, and submitted by a human.

Summary by CodeRabbit

  • Bug Fixes
    • Improved workspace isolation for work-item completion analytics.
    • Prevented cycle and module data from another workspace from appearing in chart results.
    • Analytics now return empty data for unauthorized cross-workspace references while preserving valid results within the requester’s workspace.

…ytics chart

ProjectAdvanceAnalyticsChartEndpoint's work-items chart looked up the Cycle and
Module named by the cycle_id/module_id query params with a bare
Cycle.objects.filter(id=cycle_id) / Module.objects.filter(id=module_id), with no
workspace filter. The CycleIssue/ModuleIssue querysets used to build the chart
were correctly scoped via base_filters (which includes workspace__slug), so they
returned no issues for a foreign ID, but the cycle/module lookup itself was not.
That let any member of workspace A pass a cycle_id or module_id belonging to
workspace B and get that cycle's/module's start_date and end_date back in the
chart response, since those fields drive the date range used to build the
per-day series. Iterating UUIDs would let an attacker enumerate cycle/module date
ranges across every workspace.

Both lookups are now scoped with workspace__slug=self._workspace_slug, matching
every other query in this file (base_filters always includes workspace__slug,
and the sibling ProjectAdvanceAnalyticsEndpoint/StatsEndpoint scope Cycle/Module
counts the same way). A foreign ID now resolves to no cycle/module, so the
existing "not found" branch returns the same empty {"data": [], "schema": {}}
it already returns for a cycle/module with no start_date.

Added contract tests asserting a foreign workspace's cycle/module dates are not
returned (this fails without the fix, leaking the foreign date range in the
generated series), plus a positive control confirming a cycle in the requester's
own workspace still returns its chart data.

Fixes makeplane#9601

Co-authored-by: Claude <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Aug 26, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Aug 26, 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 Plus

Run ID: d9c58590-ee7d-4c2b-a6d2-614bf733fd11

📥 Commits

Reviewing files that changed from the base of the PR and between d0a30f4 and 1f19a35.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/analytic/project_analytics.py
  • apps/api/plane/tests/contract/app/test_project_advance_analytics_workspace_scope_app.py

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The analytics endpoint now scopes cycle and module lookups to the current workspace. Contract tests verify that foreign workspace dates are excluded and local workspace data remains available.

Changes

Analytics workspace scoping

Layer / File(s) Summary
Scope cycle and module analytics lookups
apps/api/plane/app/views/analytic/project_analytics.py
Cycle and module queries now include the current workspace slug.
Validate foreign and local workspace behavior
apps/api/plane/tests/contract/app/test_project_advance_analytics_workspace_scope_app.py
Contract tests cover foreign cycle and module identifiers, plus a local cycle positive control.

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

Merge Risk: ⚪ Minimal · up to 1f19a

The change scopes cycle and module lookups to the requested workspace while preserving same-workspace chart behavior; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: dheeru0198, pablohashescobar

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 2 files. 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 describes the security fix: workspace-scoping cycle and module lookups in the advance analytics chart.
Description check ✅ Passed The description is complete. It explains the issue, fix, affected behavior, test scenarios, validation results, and linked issue.
Linked Issues check ✅ Passed The changes satisfy issue #9601. Both Cycle and Module lookups now include workspace scoping, and regression tests cover foreign identifiers and a valid workspace-local identifier.
Out of Scope Changes check ✅ Passed The changes are limited to the requested two-line workspace-scoping fix and focused regression tests. No unrelated code changes are present.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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.

security: analytics charts endpoint leaks cross-workspace cycle and module dates via unscoped ID lookup

2 participants