Skip to content

fix(security): fail closed on telemetry ownership check and protect user activity log - #19

Open
parthasdey2304 wants to merge 1 commit into
mainfrom
fix/security-telemetry-fail-closed-and-log-auth
Open

parthasdey2304 wants to merge 1 commit into
mainfrom
fix/security-telemetry-fail-closed-and-log-auth

Conversation

@parthasdey2304

Copy link
Copy Markdown
Member

Security Fixes (Strix Review Remediation)

  • CWE-754 Fail-Closed Ownership Verification:
    • Replaced silent pass with fail-closed logging and HTTP 500 in \�pp/routers/ai.py\ during Firestore read errors on conversation telemetry ownership checks.
    • Added fail-closed ownership check in \�pp/routers/practice.py\ when \payload.id\ is supplied.
    • Permitted \HTTPException\ to bubble up properly in AI telemetry sync without being caught and disguised as a 200 response.
  • Activity Log Authorization & Spoofing Elimination:
    • Enforced \Depends(get_current_user)\ and ownership / admin authorization on \GET /api/v1/activity/log/{uid}.
    • Enforced JWT identity in \POST /api/v1/activity/log\ preventing non-admin clients from impersonating other user accounts.
  • Automated Tests:
    • Added unit test coverage for activity log authorization, spoofing prevention, practice attempt ownership validation, and AI conversation ownership validation in \ ests/test_activity_and_practice.py.

… activity log, and prevent practice attempt spoofing
@strix-security

strix-security Bot commented Sep 10, 2026

Copy link
Copy Markdown

Strix Security Review

1 open security finding on this PR:

Review summary

Reviewed PR #19, a security remediation covering fail-closed ownership verification and activity-log authorization. The fail-closed changes to the AI conversation telemetry sync and practice attempt submission endpoints correctly propagate HTTPException and return HTTP 500 on Firestore read failures rather than failing open, and the new authorization check on the per-user activity-log endpoint correctly limits non-admin callers to their own records. One gap remains in the remediation: the anti-spoofing control added to the activity-log ingestion endpoint is bypassed by unauthenticated requests, leaving identity spoofing possible — see "Activity log identity spoofing still possible for unauthenticated requests in POST /api/v1/activity/log".

Fixed the findings? re-run the review, or tag @strix-security in a PR comment to run a fresh review.

Updated for c4ed593.


Reviewed by Strix
Re-run review · Configure security review settings

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

Strix flagged a new security finding below. See the pinned summary comment for the full PR status.

Comment thread app/routers/activity.py
Comment on lines +65 to 72
# Prevent spoofing of another user's identity if authenticated
if user_uid:
if not is_admin or not entry.get("uid"):
entry["uid"] = user_uid
if user_name and (not is_admin or not entry.get("student_name")):
entry["student_name"] = user_name
if user_email and not entry.get("student_email"):
if user_email and (not is_admin or not entry.get("student_email")):
entry["student_email"] = user_email

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Activity log identity spoofing still possible for unauthenticated requests in POST /api/v1/activity/log

Severity: MEDIUM · CWE-863

The anti-spoofing block added in ingest_log only executes when an authenticated user context is present. Because the endpoint resolves the caller via get_current_user_optional, a request with no Authorization header produces current_user == None, so user_uid, user_name, and user_email are all None and the new block is a no-op. Client-supplied uid, student_name, and student_email values are then persisted verbatim by db.save_activity_log. As a result, the PR's stated goal of preventing non-admin clients from impersonating other accounts is not achieved for unauthenticated callers: an anonymous caller who supplies valid HMAC headers can still inject a forged activity entry (e.g. a CHEAT_ATTEMPT) attributed to an arbitrary victim uid. The shared static HMAC secret is transport-level integrity, not per-user authentication, so it does not block this spoofing.

Suggested change
# Prevent spoofing of another user's identity if authenticated
if user_uid:
if not is_admin or not entry.get("uid"):
entry["uid"] = user_uid
if user_name and (not is_admin or not entry.get("student_name")):
entry["student_name"] = user_name
if user_email and not entry.get("student_email"):
if user_email and (not is_admin or not entry.get("student_email")):
entry["student_email"] = user_email
# Prevent spoofing of another user's identity
if not user_uid:
entry.pop("uid", None)
entry.pop("student_name", None)
entry.pop("student_email", None)
if user_uid and (not is_admin or not entry.get("uid")):
entry["uid"] = user_uid
if user_name and (not is_admin or not entry.get("student_name")):
entry["student_name"] = user_name
if user_email and (not is_admin or not entry.get("student_email")):
entry["student_email"] = user_email
Prompt to fix with AI
This is a security vulnerability found during a code review.

Vulnerability: Activity log identity spoofing still possible for unauthenticated requests in POST /api/v1/activity/log
Severity: MEDIUM
CWE: CWE-863

The anti-spoofing block added in `ingest_log` only executes when an authenticated user context is present. Because the endpoint resolves the caller via `get_current_user_optional`, a request with no `Authorization` header produces `current_user == None`, so `user_uid`, `user_name`, and `user_email` are all `None` and the new block is a no-op. Client-supplied `uid`, `student_name`, and `student_email` values are then persisted verbatim by `db.save_activity_log`. As a result, the PR's stated goal of preventing non-admin clients from impersonating other accounts is not achieved for unauthenticated callers: an anonymous caller who supplies valid HMAC headers can still inject a forged activity entry (e.g. a `CHEAT_ATTEMPT`) attributed to an arbitrary victim `uid`. The shared static HMAC secret is transport-level integrity, not per-user authentication, so it does not block this spoofing.

Location: app/routers/activity.py:65-72
Context: Neutralize identity for anonymous callers
```
// Before:
        # Prevent spoofing of another user's identity if authenticated
        if user_uid:
            if not is_admin or not entry.get("uid"):
                entry["uid"] = user_uid
        if user_name and (not is_admin or not entry.get("student_name")):
            entry["student_name"] = user_name
        if user_email and (not is_admin or not entry.get("student_email")):
            entry["student_email"] = user_email
// After:
        # Prevent spoofing of another user's identity
        if not user_uid:
            entry.pop("uid", None)
            entry.pop("student_name", None)
            entry.pop("student_email", None)
        if user_uid and (not is_admin or not entry.get("uid")):
            entry["uid"] = user_uid
        if user_name and (not is_admin or not entry.get("student_name")):
            entry["student_name"] = user_name
        if user_email and (not is_admin or not entry.get("student_email")):
            entry["student_email"] = user_email
```

How to fix:
Require authentication on this endpoint by switching from `get_current_user_optional` to `get_current_user` so anonymous requests are rejected. Alternatively, if anonymous telemetry must remain allowed, strip or reject client-supplied identity fields (`uid`, `student_name`, `student_email`) whenever no authenticated user is present, so spoofed attribution cannot be persisted. Treat the shared static HMAC secret as integrity protection only and never as a basis for attributing activity to a specific user account.

Please fix this vulnerability. If you propose a fix, make it concise and minimal.

React 👍 / 👎 to tune Strix for this repo. A repo collaborator (or the PR author) can resolve this thread to dismiss the finding.

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.

1 participant