Skip to content

Fix: Critical Security Vulnerability (IDOR) in evaluate-assessments Edge Function#174

Open
Varadraj75 wants to merge 3 commits intoAOSSIE-Org:mainfrom
Varadraj75:fix/idor-evaluate-assessments
Open

Fix: Critical Security Vulnerability (IDOR) in evaluate-assessments Edge Function#174
Varadraj75 wants to merge 3 commits intoAOSSIE-Org:mainfrom
Varadraj75:fix/idor-evaluate-assessments

Conversation

@Varadraj75
Copy link
Contributor

@Varadraj75 Varadraj75 commented Mar 2, 2026

Closes #173

📝 Description

This PR patches a critical IDOR (Insecure Direct Object Reference) security vulnerability in the evaluate-assessments Supabase Edge Function. The function previously allowed any unauthenticated user to forge autism assessment results for arbitrary patient IDs by blindly trusting the patient_id from the request body and using a global Supabase client initialized with the anonymous key — completely bypassing JWT authentication and Row Level Security (RLS).

🔧 Changes Made

  • Removed the global Supabase client that was initialized with SUPABASE_ANON_KEY, which bypassed RLS for all requests.
  • Added Authorization header extraction — requests without a valid Authorization header now return 401 Unauthorized.
  • Created a per-request Supabase client using the user's JWT token, ensuring all database operations respect Row Level Security policies.
  • Added JWT verification via supabase.auth.getUser() to validate the authenticated user's identity before processing.
  • Replaced untrusted patient_id from request body with user.id extracted from the verified JWT — users can now only submit assessment results for themselves.
  • Added error handling for the database insert operation — previously, insert failures were silently ignored.

File Modified: supabase/functions/evaluate-assessments/index.ts

📷 Screenshots or Visual Changes (if applicable)

N/A — This is a backend security fix with no UI changes.

🤝 Collaboration

Collaborated with: N/A

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • New Features

    • Added JWT-based authentication for secure assessment submissions.
    • Returns structured JSON responses with appropriate HTTP statuses.
  • Bug Fixes

    • Validates request payloads, required fields, duplicate questions, and answer IDs.
    • Improves scoring accuracy and determines result flag/message based on cutoff.
    • Persists assessment results and maps storage errors to proper responses.
    • Adds centralized handling for unexpected server errors.

- Create per-request Supabase client with user's JWT (enforces RLS)
- Validate user identity via supabase.auth.getUser()
- Use authenticated user.id instead of trusting patient_id from request body
- Add error handling for failed DB inserts

Fixes AOSSIE-Org#173
Copilot AI review requested due to automatic review settings March 2, 2026 13:10
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Warning

Rate limit exceeded

@Varadraj75 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 39 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between fc7fbed and 1f585e2.

📒 Files selected for processing (1)
  • supabase/functions/evaluate-assessments/index.ts
📝 Walkthrough

Walkthrough

Adds JWT authentication to the evaluate-assessments edge function, validates and extracts the authenticated user's ID, enforces request payload validation, verifies assessment and answer IDs, computes and persists assessment results, and centralizes error handling and standardized JSON responses.

Changes

Cohort / File(s) Summary
Edge Function (auth, validation, scoring, persistence)
supabase/functions/evaluate-assessments/index.ts
Implements Authorization header validation and per-request Supabase client with the token; verifies user via supabase.auth.getUser() and uses user.id as patient_id; validates JSON payload, required fields, duplicate question detection, question/answer ID validity; computes score and isAutistic; inserts into assessment_results and maps DB errors to appropriate HTTP responses; adds centralized 500 handling and JSON Content-Type responses.

Sequence Diagram

sequenceDiagram
    participant Client
    participant EdgeFunction as Edge Function
    participant SupabaseAuth as Supabase Auth
    participant SupabaseDB as Supabase DB

    Client->>EdgeFunction: POST /evaluate-assessments (Authorization header + JSON)
    EdgeFunction->>EdgeFunction: Validate Authorization header (401 if missing)
    EdgeFunction->>SupabaseAuth: supabase.auth.getUser()
    SupabaseAuth-->>EdgeFunction: user or error (401 on invalid)
    EdgeFunction->>SupabaseDB: SELECT assessment by assessment_id
    SupabaseDB-->>EdgeFunction: assessment or null (404 if not found)
    EdgeFunction->>EdgeFunction: Validate payload, questions, answers (400 on invalid)
    EdgeFunction->>EdgeFunction: Compute total score and isAutistic
    EdgeFunction->>SupabaseDB: INSERT assessment_results (scoped by auth/RLS)
    SupabaseDB-->>EdgeFunction: insert result or error (map errors to 400/403/500)
    EdgeFunction->>Client: 200 + result JSON or appropriate error status
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A token in paw, I hop to inspect,
No phantom patient can now interject.
I tally the answers, the score I confide,
The DB accepts only what truthified,
Hooray — secure hops, with data kept right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing a critical IDOR security vulnerability in the evaluate-assessments Edge Function.
Linked Issues check ✅ Passed All coding requirements from issue #173 are met: JWT authentication via Authorization header, per-request Supabase client creation, JWT validation, user ID extraction, RLS enforcement, and error handling for database inserts.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the IDOR vulnerability: authentication, validation, error handling, and response standardization align with issue #173 objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

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

Patches a critical IDOR vulnerability in the evaluate-assessments Supabase Edge Function by enforcing JWT-based authentication and binding writes to the authenticated user, preventing forged assessment submissions for arbitrary patient IDs.

Changes:

  • Require Authorization header and validate JWT via supabase.auth.getUser().
  • Create a per-request Supabase client using the request’s JWT to ensure RLS is applied.
  • Stop trusting patient_id from request body; use user.id and add insert error handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
supabase/functions/evaluate-assessments/index.ts (1)

160-164: Add structured error logging in the catch path.

Returning 500 without emitting context makes production debugging and incident triage harder. Log a sanitized error payload before responding.

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

In `@supabase/functions/evaluate-assessments/index.ts` around lines 160 - 164, In
the catch (error) block of this function add structured, sanitized error logging
before returning the 500 response: capture a minimal payload (e.g., { message:
String(error?.message), stack: String(error?.stack)?.split("\n")[0], requestId:
ctx?.requestId || null }) and emit it via the existing logger (e.g.,
processLogger.error or console.error) so the log includes context but no
sensitive data, then return the JSON 500 response as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/functions/evaluate-assessments/index.ts`:
- Around line 69-81: The current branch checks error before data and can return
500 even when the record is simply missing; invert and refine the checks in
evaluate-assessments/index.ts so you first detect missing results (e.g., if
(!data || data.length === 0) return new Response(JSON.stringify({ error:
"Assessment not found" }), { status: 404 });) and only afterwards handle genuine
query errors (if (error) return new Response(JSON.stringify({ error: "Internal
Server Error" }), { status: 500 });). Update the Response calls around the error
and data variables so not-found always yields 404 and real errors yield 500.
- Around line 109-125: The loop that currently uses continue to skip invalid or
unmatched answers (inside the for loop over answered_questions.questions where
matched_question and answer are resolved) should instead validate inputs: detect
duplicate question_id entries by tracking seen IDs (e.g., a Set of
question.question_id) and immediately return a 400 error if a duplicate is
found; likewise, if matched_question is not found or if answer is not found for
a given question.answer_id, return a 400 error indicating invalid question_id or
answer_id rather than silently continuing; update the handler that processes
answered_questions (the evaluate-assessments request handler) to perform these
checks before scoring so tampered submissions are rejected.
- Around line 59-89: Validate and type-check the incoming request body before
using it: wrap await req.json() in a try/catch to return a 400 on malformed
JSON, then verify that assessment_id is present and that questions is an array
whose elements have the expected shape (each contains question_id and answer_id)
before constructing answered_questions; if the shape is invalid return a 400
with a clear error message. Update the logic around req.json(), the extracted
assessment_id/questions, and the construction of AssessmentEvaluationRequestDTO
(and the use of AssessmentEvaluationQuestionDTO in questions.map) to perform
these checks and short-circuit with 400 for bad input.

---

Nitpick comments:
In `@supabase/functions/evaluate-assessments/index.ts`:
- Around line 160-164: In the catch (error) block of this function add
structured, sanitized error logging before returning the 500 response: capture a
minimal payload (e.g., { message: String(error?.message), stack:
String(error?.stack)?.split("\n")[0], requestId: ctx?.requestId || null }) and
emit it via the existing logger (e.g., processLogger.error or console.error) so
the log includes context but no sensitive data, then return the JSON 500
response as before.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 421e7c2 and 9d55ae5.

📒 Files selected for processing (1)
  • supabase/functions/evaluate-assessments/index.ts

- Add Content-Type: application/json header to all error responses
- Validate and type-check request payload before processing
- Map insert errors to proper status codes (403/400/500)
- Check not-found before generic error to ensure correct 404 responses
- Reject invalid or duplicate question_ids with 400 status
- Add structured error logging in catch block for debugging
Copy link

@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.

♻️ Duplicate comments (1)
supabase/functions/evaluate-assessments/index.ts (1)

71-88: ⚠️ Potential issue | 🟠 Major

Validate each questions[] item before mapping to avoid 500s on malformed payloads.

Top-level payload checks are good, but items inside questions are not shape-validated. Inputs like null or objects missing question_id/answer_id can throw during mapping and fall into generic 500 instead of returning 400.

Suggested fix
+    const rawQuestions = (body as { questions?: unknown }).questions;
+    const hasValidQuestionShape =
+      Array.isArray(rawQuestions) &&
+      rawQuestions.every(
+        (q): q is AssessmentEvaluationQuestionDTO =>
+          !!q &&
+          typeof q === "object" &&
+          typeof (q as { question_id?: unknown }).question_id === "string" &&
+          typeof (q as { answer_id?: unknown }).answer_id === "string"
+      );
+
     if (
       !body ||
       typeof body !== "object" ||
       typeof (body as { assessment_id?: unknown }).assessment_id !== "string" ||
-      !Array.isArray((body as { questions?: unknown }).questions)
+      !hasValidQuestionShape
     ) {
       return new Response(
         JSON.stringify({
-          error: "Invalid payload: assessment_id and questions are required",
+          error:
+            "Invalid payload: assessment_id must be string and each question must include string question_id and answer_id",
         }),
         { status: 400, headers: JSON_HEADERS }
       );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/evaluate-assessments/index.ts` around lines 71 - 88, The
payload validation currently checks only top-level body but not items inside
questions; update the code after extracting const { assessment_id, questions }
to iterate over questions and validate each item (ensure each item is a non-null
object and has string question_id and answer_id, or otherwise matches
AssessmentEvaluationQuestionDTO), and if any item fails return a 400 Response
with a clear error; implement this check before any mapping/processing so
functions that map questions won't throw on malformed entries (use the existing
JSON_HEADERS and the same error Response pattern).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@supabase/functions/evaluate-assessments/index.ts`:
- Around line 71-88: The payload validation currently checks only top-level body
but not items inside questions; update the code after extracting const {
assessment_id, questions } to iterate over questions and validate each item
(ensure each item is a non-null object and has string question_id and answer_id,
or otherwise matches AssessmentEvaluationQuestionDTO), and if any item fails
return a 400 Response with a clear error; implement this check before any
mapping/processing so functions that map questions won't throw on malformed
entries (use the existing JSON_HEADERS and the same error Response pattern).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d55ae5 and fc7fbed.

📒 Files selected for processing (1)
  • supabase/functions/evaluate-assessments/index.ts

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.

BUG: Critical Security flaw - Edge Function IDOR and lack of authentication allows forged assessment results

2 participants