Skip to content

fix(backend): prevent path traversal in face search endpoint - #1415

Open
AbiramiR-27 wants to merge 6 commits into
AOSSIE-Org:mainfrom
AbiramiR-27:bugfix/path-traversal-face-search
Open

fix(backend): prevent path traversal in face search endpoint#1415
AbiramiR-27 wants to merge 6 commits into
AOSSIE-Org:mainfrom
AbiramiR-27:bugfix/path-traversal-face-search

Conversation

@AbiramiR-27

@AbiramiR-27 AbiramiR-27 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Addressed Issues:

Fixes #1322

Screenshots/Recordings:

N/A (This is a backend security fix).

Additional Notes:

This PR resolves a path traversal vulnerability in the /face-search endpoint. Previously, if input_type was set to path, the endpoint would attempt to read and process any arbitrary file path provided by the user.

Changes made in this PR:

  • Added an is_safe_path() helper function in backend/app/routes/face_clusters.py that normalizes paths and validates that they are located inside one of the registered folders in the database or the temp_uploads folder using os.path.commonpath.
  • Checked is_safe_path before processing paths in /face-search, returning a 403 Forbidden error on violation.
  • Added test_face_search_path_traversal_blocked and test_face_search_safe_path_allowed unit tests in backend/tests/test_face_clusters.py to cover both cases.

AI Usage Disclosure:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: Antigravity coding assistant.

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • My changes generate no new warnings or errors
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

  • Bug Fixes
    • Improved security for face searches that accept file paths by rejecting unsafe or out-of-scope locations.
    • Now returns a clear “Access Denied” (HTTP 403) when a requested path is restricted.
    • Hardened path handling to safely open/read images and improved in-memory processing for base64 searches.
  • Tests
    • Added API tests covering path traversal, symlink-like escape attempts, successful safe-path requests, and base64 requests using in-memory image_bytes.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@AbiramiR-27, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 99ead611-1cc5-48f9-bd99-265a29cc908b

📥 Commits

Reviewing files that changed from the base of the PR and between b4bc922 and c5dd0db.

📒 Files selected for processing (3)
  • backend/app/models/FaceDetector.py
  • backend/app/routes/face_clusters.py
  • backend/tests/test_face_clusters.py

Walkthrough

The face-search endpoint restricts path inputs to allowed folders and temp_uploads, securely reads permitted files, and passes image bytes to face search. Base64 inputs also use in-memory bytes. Tests cover blocked paths, allowed paths, and base64 processing.

Changes

Face Search Security and Input Flow

Layer / File(s) Summary
Path authorization and secure file reading
backend/app/routes/face_clusters.py, backend/tests/test_face_clusters.py
Adds resolved-path authorization, secure file opening, 403 Access Denied responses, and tests for traversal, symlink escape, and permitted paths.
In-memory image search pipeline
backend/app/utils/faceSearch.py, backend/app/models/FaceDetector.py, backend/app/routes/face_clusters.py, backend/tests/test_face_clusters.py
Extends face search and detection to accept image bytes, decode them in memory, and validates base64 routing.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: Python

Suggested reviewers: rohan-pandeyy

Poem

A rabbit guards the image gate,
While risky paths must patiently wait.
Safe bytes hop through search with cheer,
And base64 paths disappear.
Tests nibble bugs till skies are clear.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: hardening face search against path traversal.
Linked Issues check ✅ Passed The endpoint now validates paths against allowed folders and blocks unsafe reads before processing, satisfying #1322.
Out of Scope Changes check ✅ Passed The added byte-based processing changes and tests directly support the path-traversal fix and stay within scope.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
backend/app/routes/face_clusters.py (1)

262-279: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Authorize before probing the filesystem.

Unauthorized existing paths return 403, while unauthorized nonexistent paths return 400, creating a filesystem-existence oracle. Call is_safe_path(local_file_path) before os.path.isfile() so all disallowed paths consistently return 403.

🤖 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/app/routes/face_clusters.py` around lines 262 - 279, Reorder the
validation in the face-cluster route so is_safe_path(local_file_path) executes
before os.path.isfile(local_file_path). Preserve the existing 403 response for
unsafe paths and the 400 response for safe paths that are not valid files,
preventing filesystem existence from being revealed for unauthorized paths.
🤖 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/app/routes/face_clusters.py`:
- Around line 45-58: Update the path containment logic in the folder-checking
function to use canonical paths: resolve both target_path and each allowed
folder, including temp_dir, with realpath() before commonpath comparison.
Preserve the existing allowed-folder iteration and return behavior while
ensuring symlink targets outside permitted roots are rejected.

In `@backend/tests/test_face_clusters.py`:
- Around line 420-434: Expand test_face_search_path_traversal_blocked to use an
actual parent-directory traversal such as /allowed/folder/../restricted/file.jpg
and assert it remains blocked with 403. Add a separate symlink-escape regression
test that mocks or creates a link inside the allowed folder pointing outside it,
then verifies canonicalized-path validation rejects the request.

---

Outside diff comments:
In `@backend/app/routes/face_clusters.py`:
- Around line 262-279: Reorder the validation in the face-cluster route so
is_safe_path(local_file_path) executes before os.path.isfile(local_file_path).
Preserve the existing 403 response for unsafe paths and the 400 response for
safe paths that are not valid files, preventing filesystem existence from being
revealed for unauthorized paths.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cdff1766-c663-410d-baf4-bf1bac2a023d

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2dc9b and d4b8a95.

📒 Files selected for processing (2)
  • backend/app/routes/face_clusters.py
  • backend/tests/test_face_clusters.py

Comment thread backend/app/routes/face_clusters.py Outdated
Comment thread backend/tests/test_face_clusters.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
backend/tests/test_face_clusters.py (2)

472-485: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the exact path passed to face search.

assert_called_once() does not verify that the authorized path is the one forwarded downstream.

-        mock_perform.assert_called_once()
+        mock_perform.assert_called_once_with("/allowed/folder/family.jpg")
🤖 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_face_clusters.py` around lines 472 - 485, Update
test_face_search_safe_path_allowed to assert the exact arguments passed to
mock_perform, including the authorized path /allowed/folder/family.jpg, rather
than only asserting it was called once. Preserve the existing success response
and folder authorization setup.

Source: Path instructions


472-485: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Cover the temp_uploads allowlist branch.

The test covers registered folders but not the second permitted root used by is_safe_path(). Add a permitted temp_uploads/... case so that exception cannot regress unnoticed.

🤖 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_face_clusters.py` around lines 472 - 485, Extend
test_face_search_safe_path_allowed to also exercise a path under the
temp_uploads permitted root used by is_safe_path(), while retaining the existing
registered-folder coverage and successful perform_face_search assertions. Ensure
the temp_uploads case verifies a request is accepted and mock_perform is
invoked.

Source: Path instructions

🤖 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/tests/test_face_clusters.py`:
- Around line 427-446: Harden both blocked-path tests, including the
symlink-escape test, by mocking os.path.isfile to return True for the supplied
escaped paths and asserting mock_perform was not called. Keep the existing
path-resolution mocks and 403 assertions, ensuring the tests model an existing
file rejected by authorization rather than by file absence.

---

Outside diff comments:
In `@backend/tests/test_face_clusters.py`:
- Around line 472-485: Update test_face_search_safe_path_allowed to assert the
exact arguments passed to mock_perform, including the authorized path
/allowed/folder/family.jpg, rather than only asserting it was called once.
Preserve the existing success response and folder authorization setup.
- Around line 472-485: Extend test_face_search_safe_path_allowed to also
exercise a path under the temp_uploads permitted root used by is_safe_path(),
while retaining the existing registered-folder coverage and successful
perform_face_search assertions. Ensure the temp_uploads case verifies a request
is accepted and mock_perform is invoked.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ce5d4d18-c5d8-4199-9f1e-1429bfa3deed

📥 Commits

Reviewing files that changed from the base of the PR and between d4b8a95 and 722a61b.

📒 Files selected for processing (2)
  • backend/app/routes/face_clusters.py
  • backend/tests/test_face_clusters.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/routes/face_clusters.py

Comment thread backend/tests/test_face_clusters.py
@AbiramiR-27

Copy link
Copy Markdown
Contributor Author

@rohan-pandeyy Could you please review this PR when you get a chance?

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ This PR has merge conflicts.

Please resolve the merge conflicts before review.

Your PR will only be reviewed by a maintainer after all conflicts have been resolved.

📺 Watch this video to understand why conflicts occur and how to resolve them:
https://www.youtube.com/watch?v=Sqsz1-o7nXk

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/app/routes/face_clusters.py (1)

46-65: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Eliminate the validation-to-use race.

real_target is validated here, but face_tagging() later checks and passes the original payload.path. A caller able to modify an allowed directory can replace that path with a symlink after validation and before perform_face_search() opens it. Open the image through trusted directory descriptors with no-follow semantics and make the search consume that opened handle/bytes, rather than reopening the user-controlled pathname.

🤖 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/app/routes/face_clusters.py` around lines 46 - 65, Update the
face_tagging/perform_face_search flow to eliminate the validation-to-use race:
after is_safe_path validation, open the image via trusted allowed-directory file
descriptors using no-follow semantics, then pass the opened handle or loaded
bytes to perform_face_search instead of the original payload.path. Preserve
rejection of paths outside registered folders and temp_uploads, and ensure the
image is not reopened through the user-controlled pathname.
🤖 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/app/routes/face_clusters.py`:
- Around line 46-65: Update the face_tagging/perform_face_search flow to
eliminate the validation-to-use race: after is_safe_path validation, open the
image via trusted allowed-directory file descriptors using no-follow semantics,
then pass the opened handle or loaded bytes to perform_face_search instead of
the original payload.path. Preserve rejection of paths outside registered
folders and temp_uploads, and ensure the image is not reopened through the
user-controlled pathname.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 25064d07-ed48-42ab-9e0e-ad2f7797ddda

📥 Commits

Reviewing files that changed from the base of the PR and between 722a61b and 7f50ea4.

📒 Files selected for processing (2)
  • backend/app/routes/face_clusters.py
  • backend/tests/test_face_clusters.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/test_face_clusters.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/models/FaceDetector.py (1)

34-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate the detect_faces return contract.

The method returns either None or a result dictionary, but exposes no return type. Declare this contract to keep the new byte-based call path type-safe.

Proposed fix
-from typing import Optional
+from typing import Any, Dict, Optional
...
     def detect_faces(
         self,
         image_id: str,
         image_path: Optional[str] = None,
         forSearch: bool = False,
         image_bytes: Optional[bytes] = None,
-    ):
+    ) -> Optional[Dict[str, Any]]:

As per path instructions, “Ensure proper use of type hints.”

🤖 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/app/models/FaceDetector.py` around lines 34 - 50, Update the
detect_faces method signature to declare its return type as an optional result
dictionary, preserving the existing None returns for missing or invalid images
and dictionary results for successful detection.

Source: Path instructions

🤖 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/app/routes/face_clusters.py`:
- Around line 323-329: Update the file-opening logic in the route handler around
local_file_path to eliminate the authorization-to-open race: open the verified
allowed root as a directory descriptor, resolve each relative path component
using dir_fd and O_NOFOLLOW, and open the final component without reopening the
original request path. Verify the resulting descriptor is a regular file before
reading it, while preserving the existing canonical-target authorization checks.

In `@backend/tests/test_face_clusters.py`:
- Around line 494-495: Strengthen the tests in
backend/tests/test_face_clusters.py at lines 494-495 by assert­ing
perform_face_search receives exactly b"fakeimagebytes" and verifying os.open
includes O_NOFOLLOW when supported; at lines 508-514, decode the fixture and
assert perform_face_search receives the exact decoded bytes.

---

Nitpick comments:
In `@backend/app/models/FaceDetector.py`:
- Around line 34-50: Update the detect_faces method signature to declare its
return type as an optional result dictionary, preserving the existing None
returns for missing or invalid images and dictionary results for successful
detection.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 32de94d5-a4d8-45eb-9cfc-17e9a1a6a1e1

📥 Commits

Reviewing files that changed from the base of the PR and between 7f50ea4 and b4bc922.

📒 Files selected for processing (4)
  • backend/app/models/FaceDetector.py
  • backend/app/routes/face_clusters.py
  • backend/app/utils/faceSearch.py
  • backend/tests/test_face_clusters.py

Comment thread backend/app/routes/face_clusters.py Outdated
Comment thread backend/tests/test_face_clusters.py
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: Path traversal vulnerability in face search endpoint allows reading files outside image directories

1 participant