Skip to content

fix(security): scope calendar export by user_id — close cross-user IDOR (#123)#224

Merged
Jose-Gael-Cruz-Lopez merged 1 commit into
mainfrom
security/123-calendar-export-idor
Jun 14, 2026
Merged

fix(security): scope calendar export by user_id — close cross-user IDOR (#123)#224
Jose-Gael-Cruz-Lopez merged 1 commit into
mainfrom
security/123-calendar-export-idor

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-Lopez Jose-Gael-Cruz-Lopez commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes #123. P0 / CRITICAL.

Vulnerability

export_to_google (routes/calendar.py) selected each requested assignment by id with no user_id scope — read+decrypt another user's private notes, push to the attacker's calendar, stamp google_event_id on the victim's row.

Fix

Per-site user_id scoping on the export select and write-back.

Note on sibling endpoints

update_assignment/delete_assignment/sync_to_google are not vulnerable — each does a user_id-scoped SELECT and returns 404 before its (currently id-only) write, so a non-owned id never reaches the mutation. Their write filters are scoped-by-read-guard, not scoped-by-filter; tightening those write filters as defense-in-depth is a tracked follow-up, not part of this PR.

Negative test (cross-user, fails pre-fix)

tests/test_calendar_export_idor.py: attacker authenticated as self targets victim's assignment id → exported_count==0, no decrypt/insert/update; control test confirms owner can still export. Fails pre-fix (exported_count==1).

⚠️ Auth-boundary change.

…DOR (#123)

export_to_google selected each assignment by id alone, so an authenticated
caller could pass another user's assignment UUIDs to read and decrypt their
private notes, push them into the caller's Google Calendar, and stamp
google_event_id onto the victim's row (data corruption). require_self only
validated the body's user_id, not the ids.

Scope both the select and the write-back update by user_id (matching the sync/
update/delete siblings): a non-owned id now returns no row and is skipped.

Test exercises the exact cross-user path — attacker authenticated as self,
targeting the victim's assignment id — and asserts nothing is decrypted,
exported, or stamped. Fails on pre-fix code (exported_count==1).
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR fixes a critical IDOR vulnerability in the calendar export endpoint by adding user_id scoping to both the assignment row lookup and the google_event_id write-back, complemented by regression tests simulating the attack scenario where an authenticated user attempts to export another user's assignment.

Changes

Calendar Export Cross-User IDOR Fix

Layer / File(s) Summary
Assignment selection and write-back user-id scoping
backend/routes/calendar.py
The /export endpoint adds user_id equality filters to both the assignment row selection (preventing access to another user's assignments) and the subsequent google_event_id update (preventing data stamping on rows not owned by the caller), with inline comments documenting the defense-in-depth approach.
IDOR regression test suite
backend/tests/test_calendar_export_idor.py
New test module provides regression coverage with a row-scoped mock select helper that simulates real database row-level access rules, an IDOR attack test that verifies cross-user export requests return zero exports with no side effects, and a control test confirming owner-scoped exports function correctly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A calendar breach, oh what a sight,
User IDs scoped—now the exports are tight!
Mock helpers mock, the tests stand tall,
No more notes leaked across the hall! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.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
Title check ✅ Passed The PR title clearly summarizes the main security fix: adding user_id scoping to close a cross-user IDOR vulnerability in calendar export.
Linked Issues check ✅ Passed The code changes fully satisfy issue #123: user_id scoping added to both select filter and update query in calendar.export, matching sibling endpoints and preventing cross-user access.
Out of Scope Changes check ✅ Passed All changes are directly in-scope: the fix targets the exact vulnerability in export_to_google, and the new test module provides regression coverage for the cross-user IDOR issue.
Description check ✅ Passed The PR description comprehensively covers the vulnerability, fix, testing approach, and auth-boundary implications with clear references to the code changes.

✏️ 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 security/123-calendar-export-idor

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.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
frontend 488a201 Commit Preview URL

Branch Preview URL
Jun 13 2026, 04:56 AM

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

Caution

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

⚠️ Outside diff range comments (1)
backend/tests/test_calendar_export_idor.py (1)

96-99: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Assert write-back remains user-scoped in the owner-path test.

This test validates export success but not the update(..., filters={id,user_id}) contract, so the write-back scoping could regress unnoticed.

Suggested test hardening
         assert r.status_code == 200
         assert r.json()["exported_count"] == 1
         service.events.return_value.insert.assert_called_once()
+        t.return_value.update.assert_called_once()
+        _, update_kwargs = t.return_value.update.call_args
+        assert update_kwargs["filters"] == {
+            "id": "eq.my_assignment",
+            "user_id": f"eq.{VICTIM}",
+        }
🤖 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/tests/test_calendar_export_idor.py` around lines 96 - 99, The test at
backend/tests/test_calendar_export_idor.py only asserts insert was called but
doesn't verify the write-back used user-scoped filters; add an assertion that
service.events.return_value.update was called once with filters including both
"id" and "user_id" (or that the call args/kwargs contain filters={"id":
<expected>, "user_id": <expected>}), alongside the existing insert assertion so
the test enforces the update(..., filters={id,user_id}) contract and prevents
regression of owner-scoping.
🤖 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.

Outside diff comments:
In `@backend/tests/test_calendar_export_idor.py`:
- Around line 96-99: The test at backend/tests/test_calendar_export_idor.py only
asserts insert was called but doesn't verify the write-back used user-scoped
filters; add an assertion that service.events.return_value.update was called
once with filters including both "id" and "user_id" (or that the call
args/kwargs contain filters={"id": <expected>, "user_id": <expected>}),
alongside the existing insert assertion so the test enforces the update(...,
filters={id,user_id}) contract and prevents regression of owner-scoping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ada72b7-83e3-4348-9906-49f997d6cfed

📥 Commits

Reviewing files that changed from the base of the PR and between a27b688 and 488a201.

📒 Files selected for processing (2)
  • backend/routes/calendar.py
  • backend/tests/test_calendar_export_idor.py

@Jose-Gael-Cruz-Lopez Jose-Gael-Cruz-Lopez merged commit 97b3aaf into main Jun 14, 2026
6 checks passed
@Jose-Gael-Cruz-Lopez Jose-Gael-Cruz-Lopez deleted the security/123-calendar-export-idor branch June 14, 2026 02:17
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.

[P0] calendar.export_to_google cross-user IDOR leaks decrypted private notes

1 participant