Skip to content

Enforce Row Level Security (RLS) by Scoping Supabase Clients with User JWTs - #2189

Merged
riteshbonthalakoti merged 1 commit into
riteshbonthalakoti:gssocfrom
ArshVermaGit:main_1
Jun 7, 2026
Merged

Enforce Row Level Security (RLS) by Scoping Supabase Clients with User JWTs#2189
riteshbonthalakoti merged 1 commit into
riteshbonthalakoti:gssocfrom
ArshVermaGit:main_1

Conversation

@ArshVermaGit

@ArshVermaGit ArshVermaGit commented Jun 7, 2026

Copy link
Copy Markdown

Description

This PR resolves a critical security vulnerability where the backend bypassed Row Level Security (RLS) by connecting to Supabase using the global SUPABASE_SERVICE_ROLE_KEY.

We have refactored the database connection architecture to use a scoped Supabase client dependency.

Changes Made:

  • Extracted SUPABASE_ANON_KEY from environment variables.
  • Created a FastAPI dependency get_user_supabase(request: Request) that dynamically extracts the Bearer token from the incoming request.
  • Initialized a user-specific Supabase client that passes the JWT token natively in the global headers.
  • Replaced the global supabase object with the dynamically injected user_client across all core ticket endpoints (get_tickets, save_ticket, create_ticket, update_ticket, get_ticket_by_id).

This guarantees that every database operation performed by a user automatically adheres to the strict RLS policies configured in the database layer, eliminating manual application-level tenant isolation risks.

Resolved Issue

Resolves #2188

Verification

  • Verified that API calls missing an authorization header correctly throw 401 Unauthorized.
  • Verified that the user_client successfully scopes data according to the RLS policy of the authenticated user.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added rate limiting to ticket management endpoints to protect API resources.
    • Implemented database-level security controls restricting data access by user.
  • Bug Fixes

    • Storage failures now properly report errors instead of failing silently.
    • AI ticket prediction errors now provide clearer failure messages indicating communication issues.
  • Chores

    • Backend URL configuration now requires explicit environment setup; automatic fallback detection removed.

@vercel

vercel Bot commented Jun 7, 2026

Copy link
Copy Markdown

@ArshVermaGit is attempting to deploy a commit to the ritesh Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the backend from a global, service-role-key Supabase client to per-user, RLS-enforced clients; adds comprehensive database-level Row Level Security policies; hardens frontend configuration validation; and offloads blocking AI inference calls to async worker threads. The changes directly address the security bypass documented in issue #2188 by ensuring all database access is subject to RLS policies derived from user JWTs.

Changes

Supabase RLS Security Refactor and Backend Robustness

Layer / File(s) Summary
Configuration and Supabase Initialization
backend/.env.example, backend/main.py
Environment example swaps SUPABASE_SERVICE_KEY for SUPABASE_ANON_KEY; backend loads .env and initializes Supabase with service-role credentials for admin setup, disabling Supabase when config is incomplete.
Per-User Supabase Client Dependency
backend/main.py
New get_user_supabase(request) dependency extracts JWT from Authorization: Bearer header and creates a scoped Supabase client using the anon key, ensuring RLS policies are evaluated for every request.
Database Row Level Security Enforcement
supabase/migrations/20260607_enable_rls.sql
Enables RLS on tickets, profiles, ticket_messages, and system_settings; defines policies: users access only their own rows by user_id/id, admins/master_admins bypass RLS via profiles.role, messages restricted to owning ticket's user.
Ticket Persistence and CRUD Refactor
backend/main.py
Removes in-memory TICKETS_DB; tightens TicketRequest validation with max_length constraints; refactors GET /tickets, POST /tickets/save, GET /tickets/{ticket_id}, POST /tickets, and PATCH /tickets/{ticket_id} to use scoped Supabase client, validate JWT, prevent user-ID spoofing, resolve tenant linkage, and enforce rate limiting.
Frontend Configuration Strictness
Frontend/src/config.js
Enforces VITE_BACKEND_URL presence; removes hostname-based fallback logic; logs error and returns empty string if env var is missing, requiring explicit configuration.
Frontend API Client Error Handling
Frontend/src/services/api.js
Removes artificial delay helper; makes localStorage write failures fatal; eliminates mock delays and hardcoded fallback predictions; propagates backend errors instead of silently falling back.
Backend Async and Concurrency Safety
backend/main.py
Adds asyncio.Lock to serialize corrections_log.json updates; offloads blocking AI calls (Gemini vision, classifier v3/v1 fallback, NER, duplicate detection, RAG search, classifier_v2.predict) to worker threads in /ai/analyze, /ai/analyze_stream, and analyze_ticket_v2; tightens CORS middleware to explicit method/header allowlists including Authorization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #2158: The migration adds RLS policies to core tables and the backend refactor uses per-user scoped clients, directly addressing data isolation and enforcement at the database layer.
  • #2165: Both changes modify Frontend/src/config.js getBackendUrl to enforce VITE_BACKEND_URL and remove hostname-based fallback logic.

Possibly related PRs

  • ritesh-1918/HELPDESK.AI#1802: Both PRs add auth dependencies to secure ticket/AI endpoints in backend/main.py (retrieved uses Depends(get_current_user), this PR introduces get_user_supabase with JWT extraction).
  • ritesh-1918/HELPDESK.AI#2037: Both PRs modify backend/main.py and backend/.env.example Supabase initialization to replace service role key usage with anon key for RLS-backed access.
  • ritesh-1918/HELPDESK.AI#2048: Both PRs remove in-memory TICKETS_DB and refactor GET/POST/PATCH /tickets* endpoints to be Supabase-backed with per-request auth and corresponding env example updates.

Suggested labels

type:security, level:critical, quality:exceptional

Suggested reviewers

  • ritesh-1918

Poem

🐰 A token in each request flies,
RLS policies guard the skies,
No service keys to break the laws,
Per-user clients without flaws!
Threads now dance, no blocking calls—
Security and speed grace these halls.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Enforce Row Level Security (RLS) by Scoping Supabase Clients with User JWTs' directly and accurately describes the main change—migrating from a global service-role key to per-request scoped clients with user JWTs to enforce RLS.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #2188's requirements: replaces service-role key with SUPABASE_ANON_KEY [config.js], adds per-request JWT extraction [main.py], creates scoped user_client dependency [main.py], refactors ticket endpoints to use scoped client [main.py], enables RLS policies [migrations], and implements proper error handling for missing auth tokens.
Out of Scope Changes check ✅ Passed All changes directly support the RLS security enforcement objective: environment config updates, frontend URL validation, API error handling, per-request Supabase initialization, ticket endpoint refactoring, RLS policy definitions, and threading optimizations for async operations are all in scope.

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

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

@ArshVermaGit ArshVermaGit left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi @ritesh-1918 ! The issue has been resolved. Please review the PR and merge it under GSSoC. Thanks!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
Frontend/src/services/api.js (1)

24-32: ⚡ Quick win

"Safe helper" comment is inconsistent with throwing behavior.

The comment at line 24 describes setStorage as a "Safe helper," but the function now throws an error on line 30 when localStorage quota is exceeded. This breaking throw can propagate from line 14 inside getStorage (which is also documented as "Safe" at line 9), causing getStorage to fail during default-data initialization.

While throwing on quota exceeded is a reasonable design choice for stricter error handling (aligning with the PR's goal of removing silent fallbacks), the "Safe helper" documentation is now misleading.

📝 Proposed fix to update the misleading comment
-// Safe helper to set data and handle QuotaExceeded
+// Helper to set data in localStorage; throws if quota is exceeded
 const setStorage = (key, data) => {

Also consider updating the getStorage comment at line 9 to clarify that it may throw if default-data initialization fails:

-// Safe helper to get data from storage or default
+// Helper to get data from storage or initialize with default; may throw on storage quota errors
 const getStorage = (key, defaultData) => {
🤖 Prompt for 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.

In `@Frontend/src/services/api.js` around lines 24 - 32, Update the misleading
"Safe helper" comments: change the comment for setStorage to indicate it may
throw an error when localStorage quota is exceeded (so callers should handle or
propagate that error), and update the getStorage comment to note that it can
throw if default-data initialization triggers setStorage's quota error;
reference the setStorage and getStorage functions so readers know the documented
behavior matches the implementation.
backend/main.py (1)

38-51: ⚖️ Poor tradeoff

Service-role client remains in use, limiting RLS enforcement scope.

The global supabase client (line 48) using SUPABASE_SERVICE_ROLE_KEY still bypasses RLS for several operations in this file (get_system_settings, get_current_user, auth endpoints) and in other modules per the provided context snippets:

  • backend/services/notification_routing.py (lines 52-90)
  • backend/services/auto_close_service.py (lines 77-136)

This is acceptable for privileged backend-only operations, but consider updating the comment on line 38 to clarify when service-role vs user-scoped clients should be used, and ensure a follow-up tracks migrating other services where user-context is available.

🤖 Prompt for 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.

In `@backend/main.py` around lines 38 - 51, The comment and code create a global
service-role Supabase client (supabase via create_client using
SUPABASE_SERVICE_ROLE_KEY) which bypasses RLS; update the inline comment to
explicitly state that this global client is only for privileged, backend-only
operations and that any endpoints or functions that act on behalf of a user
(e.g., get_system_settings, get_current_user and services referenced like
notification_routing.py and auto_close_service.py) should instead create/request
a user-scoped client using the user's access token; add a TODO to track
migrating those services where user-context is available and ensure code paths
that should respect RLS do not use the global supabase client.
supabase/migrations/20260607_enable_rls.sql (1)

91-94: Prefer TO authenticated over auth.role() for the system_settings SELECT RLS policy

Supabase’s RLS guidance recommends targeting Postgres roles via the policy TO clause (e.g., TO authenticated) rather than using auth.role() in USING. While auth.role() typically reflects the JWT role claim (so your current auth.role() = 'authenticated' should gate correctly), switching the policy to TO authenticated avoids relying on auth.role() as the primary routing mechanism.
supabase/migrations/20260607_enable_rls.sql (lines 91-94)

🤖 Prompt for 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.

In `@supabase/migrations/20260607_enable_rls.sql` around lines 91 - 94, The SELECT
RLS policy on system_settings ("Authenticated users can view system settings")
should use the TO clause rather than relying on auth.role() in USING; update the
CREATE POLICY statement for system_settings to target the Postgres role by
adding TO authenticated and remove or replace the current USING (auth.role() =
'authenticated') check so the policy uses role-level routing instead of the
auth.role() expression.
🤖 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 `@backend/.env.example`:
- Around line 1-3: The example .env is missing SUPABASE_SERVICE_ROLE_KEY which
causes the supabase client initialization (the global supabase used in backend
main.py when initializing admin operations) to fail; update the
backend/.env.example to include
SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key> alongside SUPABASE_URL and
SUPABASE_ANON_KEY so developers have the admin key required for functions like
get_system_settings and get_current_user and for auth-related endpoints to avoid
the “[ERROR] SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY not set” failure during
startup.

In `@backend/main.py`:
- Around line 54-72: In get_user_supabase, ensure the extracted token from the
Authorization header is non-empty (reject cases like "Bearer " or empty string)
before calling create_client; if token is empty, raise HTTPException 401 with a
clear "Missing or invalid token" message. Also stop returning internal exception
text to clients: catch exceptions around create_client, log the full exception
internally (e.g., to your logger) and raise a generic HTTPException 500 with a
non-sensitive message like "Failed to initialize user client" instead of
including str(e). Ensure these checks apply to the Authorization parsing and the
create_client call so downstream errors are avoided.
- Around line 742-753: The update_ticket endpoint accepts an arbitrary updates
dict which can overwrite sensitive columns; fix by validating and filtering
fields before calling user_client.table("tickets").update: define a whitelist of
allowed updatable fields (e.g., status, viewed_at, assigned_to, priority) and
explicitly remove/ignore any keys like id, user_id, company_id, created_at,
created_by, or other sensitive columns, return a 400 if updates becomes empty or
contains disallowed keys, and then pass the sanitized updates to the existing
update call in update_ticket.
- Around line 727-739: The create_ticket endpoint inserts an unvalidated dict
and lacks the auth/profile/tenant checks implemented in save_ticket; update
create_ticket to either be deprecated or to replicate save_ticket's behavior by
(1) requiring and validating a typed TicketSaveRequest payload instead of raw
dict, (2) authenticating the caller via user_client.auth.get_user(token) (or
reuse get_user_supabase flow) to prevent user_id spoofing, (3) resolving
tenant_id from the user's profile (profiles table) and enforcing it on the
inserted record, (4) validating required fields and schema before calling
user_client.table("tickets").insert(...). Ensure responses and error handling
match save_ticket (HTTPException for missing auth or failed insert) and remove
direct print statements in favor of logging.

In `@Frontend/src/config.js`:
- Around line 7-9: The current check that logs and returns an empty string when
VITE_BACKEND_URL is missing (the envUrl check in Frontend/src/config.js) allows
the app to boot into a broken state; change this to fail-fast by throwing an
Error (or otherwise stopping initialization) when envUrl is falsy so the app
fails immediately with a clear message referencing VITE_BACKEND_URL; update the
envUrl/null-handling branch (the if (!envUrl) block) to throw a descriptive
Error (or call a build-time validation helper using import.meta.env) instead of
returning '' so downstream calls (e.g., axios requests) can't silently use
relative URLs.

---

Nitpick comments:
In `@backend/main.py`:
- Around line 38-51: The comment and code create a global service-role Supabase
client (supabase via create_client using SUPABASE_SERVICE_ROLE_KEY) which
bypasses RLS; update the inline comment to explicitly state that this global
client is only for privileged, backend-only operations and that any endpoints or
functions that act on behalf of a user (e.g., get_system_settings,
get_current_user and services referenced like notification_routing.py and
auto_close_service.py) should instead create/request a user-scoped client using
the user's access token; add a TODO to track migrating those services where
user-context is available and ensure code paths that should respect RLS do not
use the global supabase client.

In `@Frontend/src/services/api.js`:
- Around line 24-32: Update the misleading "Safe helper" comments: change the
comment for setStorage to indicate it may throw an error when localStorage quota
is exceeded (so callers should handle or propagate that error), and update the
getStorage comment to note that it can throw if default-data initialization
triggers setStorage's quota error; reference the setStorage and getStorage
functions so readers know the documented behavior matches the implementation.

In `@supabase/migrations/20260607_enable_rls.sql`:
- Around line 91-94: The SELECT RLS policy on system_settings ("Authenticated
users can view system settings") should use the TO clause rather than relying on
auth.role() in USING; update the CREATE POLICY statement for system_settings to
target the Postgres role by adding TO authenticated and remove or replace the
current USING (auth.role() = 'authenticated') check so the policy uses
role-level routing instead of the auth.role() expression.
🪄 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: c7b6568e-5b21-4e0b-aa0a-183618dc8442

📥 Commits

Reviewing files that changed from the base of the PR and between da8faf2 and 3833249.

📒 Files selected for processing (5)
  • Frontend/src/config.js
  • Frontend/src/services/api.js
  • backend/.env.example
  • backend/main.py
  • supabase/migrations/20260607_enable_rls.sql

Comment thread backend/.env.example
Comment on lines 1 to +3
# Supabase Configuration
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=your-service-key
SUPABASE_ANON_KEY=your-anon-key

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing SUPABASE_SERVICE_ROLE_KEY in example file.

The example file removes SUPABASE_SERVICE_KEY but backend/main.py (lines 42-48) still initializes the global supabase client using SUPABASE_SERVICE_ROLE_KEY for admin operations (e.g., get_system_settings, get_current_user, auth endpoints). The example should document both keys:

 # Supabase Configuration
 SUPABASE_URL=https://your-project.supabase.co
 SUPABASE_ANON_KEY=your-anon-key
+SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

Without this, developers will see the error on line 45: [ERROR] SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY not set.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 3-3: [UnorderedKey] The SUPABASE_ANON_KEY key should go before the SUPABASE_URL key

(UnorderedKey)

🤖 Prompt for 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.

In `@backend/.env.example` around lines 1 - 3, The example .env is missing
SUPABASE_SERVICE_ROLE_KEY which causes the supabase client initialization (the
global supabase used in backend main.py when initializing admin operations) to
fail; update the backend/.env.example to include
SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key> alongside SUPABASE_URL and
SUPABASE_ANON_KEY so developers have the admin key required for functions like
get_system_settings and get_current_user and for auth-related endpoints to avoid
the “[ERROR] SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY not set” failure during
startup.

Comment thread backend/main.py
Comment on lines +54 to +72
def get_user_supabase(request: Request):
"""Dependency to create a scoped Supabase client using the user's JWT token."""
if not url or not anon_key:
raise HTTPException(status_code=500, detail="Supabase Anon Key or URL missing in backend/.env")

auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")

token = auth_header.split(" ")[1]

try:
user_client = create_client(url, anon_key, options={
"global": {"headers": {"Authorization": f"Bearer {token}"}}
})
return user_client
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to initialize user client: {str(e)}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate token is non-empty and avoid leaking internal error details.

Two issues in the dependency:

  1. Empty token accepted: If header is "Bearer " (trailing space, no token), split(" ")[1] returns "", which passes to create_client and may cause confusing downstream errors.

  2. Information leakage: Line 71 exposes internal exception details (str(e)) which may reveal sensitive configuration or stack info to clients.

🛡️ Proposed fix
     token = auth_header.split(" ")[1]
+    if not token:
+        raise HTTPException(status_code=401, detail="Empty Bearer token")
     
     try:
         user_client = create_client(url, anon_key, options={
             "global": {"headers": {"Authorization": f"Bearer {token}"}}
         })
         return user_client
     except Exception as e:
-        raise HTTPException(status_code=500, detail=f"Failed to initialize user client: {str(e)}")
+        raise HTTPException(status_code=500, detail="Failed to initialize user client") from e
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 70-70: Do not catch blind exception: Exception

(BLE001)


[warning] 71-71: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 71-71: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for 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.

In `@backend/main.py` around lines 54 - 72, In get_user_supabase, ensure the
extracted token from the Authorization header is non-empty (reject cases like
"Bearer " or empty string) before calling create_client; if token is empty,
raise HTTPException 401 with a clear "Missing or invalid token" message. Also
stop returning internal exception text to clients: catch exceptions around
create_client, log the full exception internally (e.g., to your logger) and
raise a generic HTTPException 500 with a non-sensitive message like "Failed to
initialize user client" instead of including str(e). Ensure these checks apply
to the Authorization parsing and the create_client call so downstream errors are
avoided.

Comment thread backend/main.py
Comment on lines +727 to +739
@app.post("/tickets")
@limiter.limit("30/minute")
async def create_ticket(ticket: dict, request: Request, user_client = Depends(get_user_supabase)):
"""Save a new ticket into the system (persisted to Supabase)."""
if not user_client:
raise HTTPException(status_code=500, detail="Database connection not initialized")

res = user_client.table("tickets").insert(ticket).execute()
if not res.data:
raise HTTPException(status_code=400, detail="Failed to create ticket")

print(f"[DB] Ticket created: {res.data[0].get('id')}")
return res.data[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Endpoint lacks input validation and auth checks present in /tickets/save.

This endpoint accepts an unvalidated dict and inserts it directly, unlike save_ticket (lines 598-618) which:

  • Validates the user via user_client.auth.get_user(token)
  • Prevents user_id spoofing
  • Resolves tenant linkage from profiles
  • Uses a typed TicketSaveRequest model

While the RLS INSERT policy (auth.uid() = user_id) provides some protection, this endpoint:

  1. Allows arbitrary fields that may not match the schema
  2. Doesn't enforce tenant linkage or profile validation
  3. Could fail silently if user_id is missing from the dict

Consider deprecating this endpoint in favor of /tickets/save, or applying the same validation:

-async def create_ticket(ticket: dict, request: Request, user_client = Depends(get_user_supabase)):
+async def create_ticket(ticket: TicketSaveRequest, request: Request, user_client = Depends(get_user_supabase)):
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 729-729: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for 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.

In `@backend/main.py` around lines 727 - 739, The create_ticket endpoint inserts
an unvalidated dict and lacks the auth/profile/tenant checks implemented in
save_ticket; update create_ticket to either be deprecated or to replicate
save_ticket's behavior by (1) requiring and validating a typed TicketSaveRequest
payload instead of raw dict, (2) authenticating the caller via
user_client.auth.get_user(token) (or reuse get_user_supabase flow) to prevent
user_id spoofing, (3) resolving tenant_id from the user's profile (profiles
table) and enforcing it on the inserted record, (4) validating required fields
and schema before calling user_client.table("tickets").insert(...). Ensure
responses and error handling match save_ticket (HTTPException for missing auth
or failed insert) and remove direct print statements in favor of logging.

Comment thread backend/main.py
Comment on lines +742 to +753
@app.patch("/tickets/{ticket_id}")
@limiter.limit("60/minute")
async def update_ticket(ticket_id: str, updates: dict, request: Request, user_client = Depends(get_user_supabase)):
"""Partially update a ticket's fields (e.g., status, viewed_at) via Supabase."""
if not user_client:
raise HTTPException(status_code=500, detail="Database connection not initialized")

raise HTTPException(status_code=404, detail="Ticket not found")
res = user_client.table("tickets").update(updates).eq("id", ticket_id).execute()
if not res.data:
raise HTTPException(status_code=404, detail="Ticket not found")

return res.data[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Arbitrary updates dict allows overwriting sensitive fields.

The endpoint accepts any fields in the updates dict, potentially allowing clients to modify sensitive columns like user_id, company_id, or created_at. While RLS prevents updating other users' tickets, a malicious user could corrupt their own ticket data.

Consider validating allowed update fields:

🛡️ Proposed fix
+ALLOWED_UPDATE_FIELDS = {"status", "priority", "assigned_team", "last_user_viewed_at", "updated_at"}
+
 `@app.patch`("/tickets/{ticket_id}")
 `@limiter.limit`("60/minute")
 async def update_ticket(ticket_id: str, updates: dict, request: Request, user_client = Depends(get_user_supabase)):
     """Partially update a ticket's fields (e.g., status, viewed_at) via Supabase."""
     if not user_client:
         raise HTTPException(status_code=500, detail="Database connection not initialized")
     
+    # Filter to allowed fields only
+    safe_updates = {k: v for k, v in updates.items() if k in ALLOWED_UPDATE_FIELDS}
+    if not safe_updates:
+        raise HTTPException(status_code=400, detail="No valid update fields provided")
+    
-    res = user_client.table("tickets").update(updates).eq("id", ticket_id).execute()
+    res = user_client.table("tickets").update(safe_updates).eq("id", ticket_id).execute()
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 744-744: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for 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.

In `@backend/main.py` around lines 742 - 753, The update_ticket endpoint accepts
an arbitrary updates dict which can overwrite sensitive columns; fix by
validating and filtering fields before calling
user_client.table("tickets").update: define a whitelist of allowed updatable
fields (e.g., status, viewed_at, assigned_to, priority) and explicitly
remove/ignore any keys like id, user_id, company_id, created_at, created_by, or
other sensitive columns, return a 400 if updates becomes empty or contains
disallowed keys, and then pass the sanitized updates to the existing update call
in update_ticket.

Comment thread Frontend/src/config.js
Comment on lines +7 to +9
if (!envUrl) {
console.error("CRITICAL: VITE_BACKEND_URL environment variable is missing. The frontend may not be able to communicate with the backend.");
return '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Returning empty string allows the app to start in a broken state.

When VITE_BACKEND_URL is missing, returning an empty string permits the application to initialize but causes all API calls to fail with confusing relative-URL errors. For example, from the relevant snippet in TicketTracking.jsx, the request becomes axios.post("/tickets/save", ...) instead of a fully-qualified backend URL, which will likely route to the wrong endpoint or fail entirely.

Consider throwing an error here to fail-fast during initialization, providing immediate feedback that the configuration is incomplete. Alternatively, leverage Vite's build-time environment variable validation to catch this before runtime.

🛡️ Proposed fix to fail-fast on missing configuration
 const getBackendUrl = () => {
     const envUrl = import.meta.env.VITE_BACKEND_URL;
     if (!envUrl) {
         console.error("CRITICAL: VITE_BACKEND_URL environment variable is missing. The frontend may not be able to communicate with the backend.");
-        return '';
+        throw new Error("CRITICAL: VITE_BACKEND_URL environment variable is required. Please configure it in your .env file.");
     }
     return envUrl.trim().replace(/\/$/, '');
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!envUrl) {
console.error("CRITICAL: VITE_BACKEND_URL environment variable is missing. The frontend may not be able to communicate with the backend.");
return '';
const getBackendUrl = () => {
const envUrl = import.meta.env.VITE_BACKEND_URL;
if (!envUrl) {
console.error("CRITICAL: VITE_BACKEND_URL environment variable is missing. The frontend may not be able to communicate with the backend.");
throw new Error("CRITICAL: VITE_BACKEND_URL environment variable is required. Please configure it in your .env file.");
}
return envUrl.trim().replace(/\/$/, '');
};
🤖 Prompt for 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.

In `@Frontend/src/config.js` around lines 7 - 9, The current check that logs and
returns an empty string when VITE_BACKEND_URL is missing (the envUrl check in
Frontend/src/config.js) allows the app to boot into a broken state; change this
to fail-fast by throwing an Error (or otherwise stopping initialization) when
envUrl is falsy so the app fails immediately with a clear message referencing
VITE_BACKEND_URL; update the envUrl/null-handling branch (the if (!envUrl)
block) to throw a descriptive Error (or call a build-time validation helper
using import.meta.env) instead of returning '' so downstream calls (e.g., axios
requests) can't silently use relative URLs.

@riteshbonthalakoti
riteshbonthalakoti changed the base branch from main to gssoc June 7, 2026 17:42
@riteshbonthalakoti riteshbonthalakoti added gssoc GirlScript Summer of Code gssoc:approved GSSoC Approved PR level:intermediate Intermediate level difficulty quality:clean Clean code quality type:security Security fix or improvement labels Jun 7, 2026
@riteshbonthalakoti

Copy link
Copy Markdown
Owner

Hi @ArshVermaGit! Thanks for the contribution. I have triaged your PR and set it to merge into the gssoc branch.

⚠️ MANDATORY GSSOC ONBOARDING STEPS:
Before your PR points are finalized on the leaderboard, you MUST complete these required steps:

  1. Star this repository: https://github.com/ritesh-1918/HELPDESK.AI (Mandatory)
  2. 👤 Follow the Project Admin: https://github.com/ritesh-1918 (Mandatory)
  3. 💼 Connect on LinkedIn: https://www.linkedin.com/in/ritesh1908/ (Mandatory)

Welcome to the HELPDESK.AI developer family! 🚀💻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC Approved PR gssoc GirlScript Summer of Code level:intermediate Intermediate level difficulty quality:clean Clean code quality type:security Security fix or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Bypassing Row Level Security (RLS) via Service Role Key

2 participants