Uh oh!
There was an error while loading. Please reload this page.
feat(feedback): encrypt free-text input + admin read surface (#520) - #525
Conversation
This pull request has been ignored for the connected project Preview Branches by Supabase. |
📝 WalkthroughWalkthroughChangesEncrypted feedback administration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AdminUI
participant AdminAPI
participant AdminRoutes
participant FeedbackTables
AdminUI->>AdminAPI: Request feedback and issue reports
AdminAPI->>AdminRoutes: GET /api/admin/feedback and /api/admin/issue-reports
AdminRoutes->>FeedbackTables: Read bounded newest-first records
FeedbackTables-->>AdminRoutes: Feedback and issue-report rows
AdminRoutes-->>AdminAPI: Decrypted enriched records
AdminAPI-->>AdminUI: Renderable admin responses
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Deploying with |
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs | frontend-staging | 1e36394 | Commit Preview URL Branch Preview URL | Aug 06 2026, 01:24 AM |
AndresL230
commented
Aug 6, 2026
Code reviewFound 1 issue:
Sapling/frontend/src/components/screens/Admin.tsx Lines 1529 to 1531 in d9cce5e 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…520) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ption (#520) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
f.rating is an unbounded int; repeat() on the unclamped complement throws RangeError for rating > 5 and crashes the admin FeedbackTab. Clamp once per row and reuse for both the filled and empty glyphs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3052b3a to
1e36394CompareThere was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/routes/admin.py`:
- Around line 514-527: Update list_feedback and list_issue_reports in
backend/routes/admin.py to return responses with the Cache-Control: no-store
header while preserving their existing payloads. In
backend/tests/test_admin_routes.py, extend the regression coverage for both
endpoints to assert that header is present.
🪄 Autofix
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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: fbea67dd-ec82-42bd-95c4-a5b3a3a48885
📒 Files selected for processing (12)
CLAUDE.mdbackend/db/backfill_encryption.pybackend/db/seed_local_rich.pybackend/e2e_oracles/gather.pybackend/routes/admin.pybackend/routes/feedback.pybackend/tests/integration/test_encryption_roundtrip.pybackend/tests/test_admin_routes.pybackend/tests/test_feedback_routes.pydocs/frontend-testids.mdfrontend/src/components/screens/Admin.tsxfrontend/src/lib/api.ts
| @router.get("/feedback") | ||
| def list_feedback(request: Request, limit: int = 200): | ||
| require_admin(request) | ||
| rows = table("feedback").select( | ||
| "id,user_id,type,rating,selected_options,comment,session_id,topic,created_at", | ||
| order="created_at.desc", | ||
| limit=max(1, min(int(limit), 500)), | ||
| ) or [] | ||
| names = get_display_names([r["user_id"] for r in rows]) | ||
| for r in rows: | ||
| r["comment"] = decrypt_if_present(r.get("comment")) | ||
| r["topic"] = decrypt_if_present(r.get("topic")) | ||
| r["user_name"] = names.get(r["user_id"], "") | ||
| return {"feedback": rows} |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"== admin route relevant sections =="
sed -n '480,555p' backend/routes/admin.py
echoecho"== auth guard require_admin =="
sed -n '90,125p' backend/services/auth_guard.py
echoecho"== tests around feedback/reports =="
sed -n '640,725p' backend/tests/test_admin_routes.py
echoecho"== search for Cache-Control headers in admin/auth/test files =="
rg -n "Cache-Control|no-store|requires-cors-preflight|CORSMiddleware|response\\.headers|PlainResponse|JSONResponse|Response" backend/routes/admin.py backend/services/auth_guard.py backend/tests/test_admin_routes.py backend 2>/dev/null | head -200
echoecho"== app middleware / response headers snippets =="
rg -n "Cache-Control|no-store|requires-cors-preflight|CORSMiddleware|include_subschemas|response\\.headers|PlainResponse|JSONResponse|Response" backend -g '*.py'| head -200
echoecho"== route imports and router registration =="
rg -n "from routes|include_route|admin_router|auth_guard|require_admin|Cache-Control|Clear-Site" backend -g '*.py'Repository: SaplingLearn/Sapling
Length of output: 50376
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"== imports in backend/services/request_context.py =="
sed -n '1,80p' backend/services/request_context.py
echoecho"== request context around response handling =="
sed -n '50,175p' backend/services/request_context.py
echoecho"== main.py app and middleware =="
sed -n '130,235p' backend/main.py
echoecho"== focused tests/search for Cache-Control across python files =="
rg -n --glob '*.py''"Cache-Control":|Cache-Control|no-store|private' backend/services backend/tests backend/main.py backend/routes/admin.py backend/routes/admin_analytics.py
echoecho"== deterministic header behavior from router return values =="
python3 - <<'PY'# Read the relevant route bodies as text and perform a lightweight# control-flow check: does either GET handler pass a Response/JSONResponse,# or assign response.headers["Cache-Control"] before returning dict?from pathlib import Pathsrc = Path("backend/routes/admin.py").read_text()for name in ("list_feedback", "list_issue_reports"): idx = src.index(f"def {name}") next_def = src.find("\ndef ", idx + 1) body = src[idx:next_def] if next_def != -1 else src[idx:] print(f"--- {name} ---") print("returns dict/json:", "return {" in body or 'return {"' in body) print("has explicit Cache-Control assignment:", 'headers["Cache-Control"]' in body) print("passes Response/JSONResponse:", any(f"class.{kw}" in body or f"({kw}" in body for kw in ("Response", "JSONResponse")))PYRepository: SaplingLearn/Sapling
Length of output: 19598
Sensitive Data Exposure (CWE-525): Use of Web Browser Cache Containing Sensitive Information
Reachability: External
Prevent browser-cache reuse of decrypted admin responses.
require_admin() protects the request, but these decrypted responses do not disable local caching. list_feedback() and list_issue_reports() return plain dicts without setting Cache-Control: no-store, and the regression tests do not assert this header.
- Set
Cache-Control: no-storeon/api/admin/feedbackand/api/admin/issue-reports. - Add regression assertions for both endpoints in
backend/tests/test_admin_routes.py.
📍 Affects 2 files
backend/routes/admin.py#L514-L527(this comment)backend/routes/admin.py#L530-L543backend/tests/test_admin_routes.py#L658-L714
🤖 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/routes/admin.py` around lines 514 - 527, Update list_feedback and
list_issue_reports in backend/routes/admin.py to return responses with the
Cache-Control: no-store header while preserving their existing payloads. In
backend/tests/test_admin_routes.py, extend the regression coverage for both
endpoints to assert that header is present.
Closes#520.
feedback.comment/topic,issue_reports.topic/descriptionencrypted at writeStacked on #519's ADR PR.
🤖 Generated with Claude Code
Summary by CodeRabbit