Skip to content

fix: prevent ORM field injection via segment parameter in analytics (GHSA-93x3-ghh7-72j3)#8864

Merged
sriramveeraghanta merged 2 commits intopreviewfrom
fix/orm-field-injection-analytics-segment
Apr 7, 2026
Merged

fix: prevent ORM field injection via segment parameter in analytics (GHSA-93x3-ghh7-72j3)#8864
sriramveeraghanta merged 2 commits intopreviewfrom
fix/orm-field-injection-analytics-segment

Conversation

@sriramveeraghanta
Copy link
Copy Markdown
Member

@sriramveeraghanta sriramveeraghanta commented Apr 7, 2026

Summary

  • Fixes ORM field reference injection vulnerability in SavedAnalyticEndpoint where the segment query parameter was passed directly to Django's F() expression without validation, allowing authenticated workspace members to extract arbitrary related model fields (e.g. password hashes, emails, tokens)
  • Centralizes analytics field allowlists (VALID_ANALYTICS_FIELDS, VALID_YAXIS) into analytics_plot.py as single source of truth, replacing duplicate inline lists across 3 endpoints
  • Adds defense-in-depth validation in build_graph_plot() and extract_axis() so no current or future caller can pass unsafe field references
  • Fixes ExportAnalyticsEndpoint allowlist using estimate_point instead of estimate_point__value

Security Advisory

https://github.com/makeplane/plane/security/advisories/GHSA-93x3-ghh7-72j3

Test plan

  • Verify AnalyticsEndpoint accepts valid segment values and rejects invalid ones
  • Verify SavedAnalyticEndpoint now rejects arbitrary segment values like workspace__owner__password
  • Verify ExportAnalyticsEndpoint accepts estimate_point__value as a valid field
  • Verify build_graph_plot() raises ValueError for invalid x_axis/y_axis/segment inputs

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced analytics parameter validation to prevent invalid data processing across all analytics endpoints.
    • Improved error handling for unsupported dimension and segment values with consistent validation rules.
    • Strengthened validation to ensure data integrity when building analytics reports.

…GHSA-93x3-ghh7-72j3)

Centralize analytics field allowlists into VALID_ANALYTICS_FIELDS and
VALID_YAXIS constants in analytics_plot.py. Add defense-in-depth
validation in build_graph_plot() and extract_axis() so no caller can
pass arbitrary field references to Django F() expressions. Add missing
segment validation to SavedAnalyticEndpoint. Also fixes ExportAnalytics
using "estimate_point" instead of "estimate_point__value".
Copilot AI review requested due to automatic review settings April 7, 2026 08:51
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

The changes consolidate analytics field validation by introducing centralized validation constants (VALID_ANALYTICS_FIELDS and VALID_YAXIS) in the utilities module and updating analytics endpoints to enforce strict parameter validation. Validation now occurs both in the API views and utility functions using shared validation rules instead of hardcoded local lists.

Changes

Cohort / File(s) Summary
Analytics Validation Constants
apps/api/plane/utils/analytics_plot.py
Added module-level validation constants VALID_ANALYTICS_FIELDS and VALID_YAXIS. Enhanced extract_axis() and build_graph_plot() functions to validate input parameters and raise ValueError for unsupported x_axis, y_axis, and segment values.
Analytics Endpoint Validation
apps/api/plane/app/views/analytic/base.py
Replaced local hardcoded validation lists with imports of VALID_ANALYTICS_FIELDS and VALID_YAXIS from utilities. Updated AnalyticsEndpoint.get(), ExportAnalyticsEndpoint.post(), and SavedAnalyticEndpoint.get() to enforce centralized validation rules for axis and segment parameters.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Endpoint as Analytics Endpoint
    participant Utils as analytics_plot Utils
    participant DB as Database

    Client->>Endpoint: Request with x_axis, y_axis, segment
    Endpoint->>Endpoint: Validate params against VALID_ANALYTICS_FIELDS<br/>and VALID_YAXIS
    alt Invalid Parameters
        Endpoint-->>Client: Return validation error
    else Valid Parameters
        Endpoint->>Utils: extract_axis(queryset, x_axis)
        Utils->>Utils: Validate x_axis in VALID_ANALYTICS_FIELDS
        alt Invalid x_axis
            Utils-->>Endpoint: Raise ValueError
            Endpoint-->>Client: Return error
        else Valid x_axis
            Utils->>DB: Extract axis data
            DB-->>Utils: Return data
            Endpoint->>Utils: build_graph_plot(..., y_axis, segment)
            Utils->>Utils: Validate y_axis & segment
            alt Invalid y_axis or segment
                Utils-->>Endpoint: Raise ValueError
                Endpoint-->>Client: Return error
            else Valid
                Utils->>Utils: Build plot data
                Utils-->>Endpoint: Return graph data
                Endpoint-->>Client: Return analytics response
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With whiskers twitched and validation tight,
I've consolidated constants, oh what a sight!
No more scattered lists in disarray,
The axes now dance in a safer way,
Fail-fast and true, the plot will grow bright! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides comprehensive context on the vulnerability, changes made, and test plan, but does not follow the required template structure with defined sections. Structure the description using the repository template with explicit sections: Description, Type of Change, Screenshots/Media, Test Scenarios, and References.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the security fix addressing ORM field injection in the analytics segment parameter with CVE reference.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/orm-field-injection-analytics-segment

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 and usage tips.

dheeru0198
dheeru0198 previously approved these changes Apr 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/plane/app/views/analytic/base.py (1)

191-214: ⚠️ Potential issue | 🟡 Minor

Security fix for segment is correct, but stored x_axis/y_axis values are not validated.

The segment validation at lines 208-212 correctly addresses the security vulnerability. However, x_axis and y_axis are retrieved from stored query_dict (lines 197-198) and only checked for truthiness (lines 200-204), not against VALID_ANALYTICS_FIELDS/VALID_YAXIS.

If legacy saved analytics contain field values that are no longer in the allowlist (or were never valid), build_graph_plot() will raise ValueError, resulting in a 500 error instead of a 400.

Proposed fix to validate stored x_axis/y_axis
         x_axis = analytic_view.query_dict.get("x_axis", False)
         y_axis = analytic_view.query_dict.get("y_axis", False)

-        if not x_axis or not y_axis:
+        if not x_axis or not y_axis or x_axis not in VALID_ANALYTICS_FIELDS or y_axis not in VALID_YAXIS:
             return Response(
-                {"error": "x-axis and y-axis dimensions are required"},
+                {"error": "x-axis and y-axis dimensions are required and the values should be valid"},
                 status=status.HTTP_400_BAD_REQUEST,
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/app/views/analytic/base.py` around lines 191 - 214, The stored
x_axis and y_axis from AnalyticView.query_dict are only checked for truthiness
but not validated against allowed fields, causing build_graph_plot to raise
ValueError for legacy/invalid saved values; update the get method to validate
x_axis is in VALID_ANALYTICS_FIELDS and y_axis is in VALID_YAXIS (or the
appropriate allowlist constants) before calling build_graph_plot, and if either
is invalid return a Response with a 400 status and a clear error message; keep
existing segment validation that uses VALID_ANALYTICS_FIELDS and ensure you
reference AnalyticView.query_dict, the get method, and build_graph_plot when
making the change.
🧹 Nitpick comments (2)
apps/api/plane/bgtasks/analytic_plot_export.py (2)

23-23: Unused imports: VALID_ANALYTICS_FIELDS and VALID_YAXIS are not used in this file.

Only build_graph_plot is actually used in this file (line 359). The constants are imported but never referenced directly since the validation happens internally within build_graph_plot().

Proposed fix to remove unused imports
-from plane.utils.analytics_plot import build_graph_plot, VALID_ANALYTICS_FIELDS, VALID_YAXIS
+from plane.utils.analytics_plot import build_graph_plot
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/bgtasks/analytic_plot_export.py` at line 23, Remove the two
unused imports VALID_ANALYTICS_FIELDS and VALID_YAXIS and keep only
build_graph_plot imported from plane.utils.analytics_plot; locate the import
line that currently reads from plane.utils.analytics_plot import
build_graph_plot, VALID_ANALYTICS_FIELDS, VALID_YAXIS and delete the trailing
constants so the module only imports build_graph_plot (validation is handled
inside build_graph_plot).

349-406: Consider the impact of ValueError from build_graph_plot() on task behavior.

If build_graph_plot() raises a ValueError for invalid inputs, the broad Exception handler (line 404) catches it and logs the error, but the task silently returns without sending any notification to the user. The user would have received a success message from ExportAnalyticsEndpoint.post() saying the export will be emailed, but no email ever arrives.

Since inputs are already validated in ExportAnalyticsEndpoint.post() before dispatching the task, this is unlikely to happen in normal operation. However, for defense-in-depth, you may want to send a failure notification email or consider more specific exception handling.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/api/plane/app/views/analytic/base.py`:
- Around line 191-214: The stored x_axis and y_axis from AnalyticView.query_dict
are only checked for truthiness but not validated against allowed fields,
causing build_graph_plot to raise ValueError for legacy/invalid saved values;
update the get method to validate x_axis is in VALID_ANALYTICS_FIELDS and y_axis
is in VALID_YAXIS (or the appropriate allowlist constants) before calling
build_graph_plot, and if either is invalid return a Response with a 400 status
and a clear error message; keep existing segment validation that uses
VALID_ANALYTICS_FIELDS and ensure you reference AnalyticView.query_dict, the get
method, and build_graph_plot when making the change.

---

Nitpick comments:
In `@apps/api/plane/bgtasks/analytic_plot_export.py`:
- Line 23: Remove the two unused imports VALID_ANALYTICS_FIELDS and VALID_YAXIS
and keep only build_graph_plot imported from plane.utils.analytics_plot; locate
the import line that currently reads from plane.utils.analytics_plot import
build_graph_plot, VALID_ANALYTICS_FIELDS, VALID_YAXIS and delete the trailing
constants so the module only imports build_graph_plot (validation is handled
inside build_graph_plot).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36b9b0dd-a2d1-45e7-bee1-8ac9c011e573

📥 Commits

Reviewing files that changed from the base of the PR and between d1db13c and d987141.

📒 Files selected for processing (3)
  • apps/api/plane/app/views/analytic/base.py
  • apps/api/plane/bgtasks/analytic_plot_export.py
  • apps/api/plane/utils/analytics_plot.py

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 vulnerability in analytics plotting by preventing ORM field reference injection via unvalidated axis/segment inputs, and centralizes axis allowlists to a single source of truth.

Changes:

  • Introduces centralized allowlists (VALID_ANALYTICS_FIELDS, VALID_YAXIS) and uses them across analytics endpoints.
  • Adds defense-in-depth validation in extract_axis() / build_graph_plot() to block unsafe field references.
  • Updates analytics endpoints to use the centralized allowlists and adds missing segment validation for saved analytics.

Reviewed changes

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

File Description
apps/api/plane/utils/analytics_plot.py Adds centralized allowlists and validates x/y/segment inputs to prevent unsafe F() usage.
apps/api/plane/bgtasks/analytic_plot_export.py Updates imports to include centralized allowlists (currently unused).
apps/api/plane/app/views/analytic/base.py Replaces inline allowlists with centralized constants and adds segment validation for saved analytics.

Comment on lines 206 to +210
segment = request.GET.get("segment", False)

if segment and (segment not in VALID_ANALYTICS_FIELDS or x_axis == segment):
return Response(
{"error": "Both segment and x axis cannot be same and segment should be valid"},
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build_graph_plot() now raises ValueError for invalid x_axis/y_axis, but this endpoint takes those values from analytic_view.query_dict (not the request) and doesn’t validate/catch errors before calling build_graph_plot(). A malformed/legacy/malicious saved analytic could now produce a 500. Consider validating x_axis/y_axis against the allowlists here and/or catching ValueError and returning a 400 response.

Copilot uses AI. Check for mistakes.
Comment on lines 229 to 234
# Check for x-axis and y-axis as thery are required parameters
if not x_axis or not y_axis or x_axis not in valid_xaxis_segment or y_axis not in valid_yaxis:
if not x_axis or not y_axis or x_axis not in VALID_ANALYTICS_FIELDS or y_axis not in VALID_YAXIS:
return Response(
{"error": "x-axis and y-axis dimensions are required and the values should be valid"},
status=status.HTTP_400_BAD_REQUEST,
)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says ExportAnalyticsEndpoint allowlist was fixed to use estimate_point instead of estimate_point__value, but VALID_ANALYTICS_FIELDS (used here for validation) includes estimate_point__value (and not estimate_point). Please reconcile the PR description/test plan with the actual intended accepted field name to avoid confusion for reviewers and release notes.

Copilot uses AI. Check for mistakes.
…ery params

Remove unused VALID_ANALYTICS_FIELDS and VALID_YAXIS imports from
analytic_plot_export.py. Add x_axis/y_axis allowlist validation in
SavedAnalyticEndpoint for stored query_dict values to prevent 500
errors from malformed saved analytics.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/api/plane/app/views/analytic/base.py (1)

45-52: Extract duplicated axis/segment validation into one helper.

The same validation and error payload shape is repeated in three endpoints, which increases maintenance drift risk.

♻️ Proposed refactor
+def _validate_analytics_dimensions(x_axis, y_axis, segment):
+    if (
+        not x_axis
+        or not y_axis
+        or x_axis not in VALID_ANALYTICS_FIELDS
+        or y_axis not in VALID_YAXIS
+    ):
+        return Response(
+            {"error": "x-axis and y-axis dimensions are required and the values should be valid"},
+            status=status.HTTP_400_BAD_REQUEST,
+        )
+
+    if segment and (segment not in VALID_ANALYTICS_FIELDS or x_axis == segment):
+        return Response(
+            {"error": "Both segment and x axis cannot be same and segment should be valid"},
+            status=status.HTTP_400_BAD_REQUEST,
+        )
+    return None
+
 class AnalyticsEndpoint(BaseAPIView):
@@
-        if not x_axis or not y_axis or x_axis not in VALID_ANALYTICS_FIELDS or y_axis not in VALID_YAXIS:
-            return Response(
-                {"error": "x-axis and y-axis dimensions are required and the values should be valid"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
-
-        # If segment is present it cannot be same as x-axis
-        if segment and (segment not in VALID_ANALYTICS_FIELDS or x_axis == segment):
-            return Response(
-                {"error": "Both segment and x axis cannot be same and segment should be valid"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
+        validation_error = _validate_analytics_dimensions(x_axis, y_axis, segment)
+        if validation_error:
+            return validation_error

Apply the same helper in SavedAnalyticEndpoint.get and ExportAnalyticsEndpoint.post.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/app/views/analytic/base.py` around lines 45 - 52, Extract the
duplicated axis/segment validation into a single helper (e.g.,
validate_axes_and_segment) in apps/api/plane/app/views/analytic/base.py that
accepts x_axis, y_axis, segment and returns either None for success or the
Response instance (same payload and status) on error; replace the inline checks
currently using VALID_ANALYTICS_FIELDS and VALID_YAXIS in the current view and
update SavedAnalyticEndpoint.get and ExportAnalyticsEndpoint.post to call this
helper and early-return the Response when it indicates an error, preserving the
existing error message and HTTP_400_BAD_REQUEST status.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/plane/app/views/analytic/base.py`:
- Around line 45-52: Extract the duplicated axis/segment validation into a
single helper (e.g., validate_axes_and_segment) in
apps/api/plane/app/views/analytic/base.py that accepts x_axis, y_axis, segment
and returns either None for success or the Response instance (same payload and
status) on error; replace the inline checks currently using
VALID_ANALYTICS_FIELDS and VALID_YAXIS in the current view and update
SavedAnalyticEndpoint.get and ExportAnalyticsEndpoint.post to call this helper
and early-return the Response when it indicates an error, preserving the
existing error message and HTTP_400_BAD_REQUEST status.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c52d97a5-3308-4969-95aa-c5db5f7dd6ac

📥 Commits

Reviewing files that changed from the base of the PR and between d987141 and 1819834.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/analytic/base.py

@sriramveeraghanta sriramveeraghanta merged commit 8a2579c into preview Apr 7, 2026
13 checks passed
@sriramveeraghanta sriramveeraghanta deleted the fix/orm-field-injection-analytics-segment branch April 7, 2026 10:34
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.

3 participants