Skip to content

BUG:Wildcard CORS + credentials + no auth allows any webpage to read/delete the photo library #1360

Description

@akshajtiwari

Is there an existing issue for this?

  • I have searched the existing issues

What happened?

Summary

Both local FastAPI servers (backend on :52123 and sync-microservice on :52124) are configured with:

allow_origins=["*"]
allow_credentials=True
allow_methods=["*"]
allow_headers=["*"]

This combination means the browser will expose credentialed cross-origin responses to any origin. Since there's no authentication on top of it, any webpage the user has open in a normal browser tab — while PictoPy is running in the background — can talk directly to these servers.

Affected files

  • backend/main.py:124-130
  • sync-microservice/main.py:36-42

Impact

With the app simply running, a malicious or compromised webpage can, without any user interaction:

  • Enumerate the entire photo library via GET /images/, including file paths, GPS/EXIF data, and timestamps
  • Delete folders and cascade-delete images/faces/albums via DELETE /folders/delete-folders
  • Delete ML models via DELETE /models/facenet
  • Shut down the backend via POST /shutdown
  • Leak absolute local folder paths (OS username, directory layout) via the sync-microservice's status endpoints
  • Disrupt sync or kill the sync-microservice via unauthenticated POSTs (/watcher/stop, /shutdown)

This also compounds other findings — e.g. the unrestricted folder_path in POST /folders/add-folder becomes remotely triggerable once this is fixed to require auth, it's no longer exploitable from an arbitrary webpage.

Steps to reproduce

  1. Run PictoPy normally.
  2. In a separate, unrelated browser tab, open a page containing:
    fetch('http://localhost:52123/images/').then(r => r.json()).then(console.log)
  3. Observe the full image index (including GPS data) returned to a page with no relation to PictoPy.

Proposed fix

  1. Restrict CORS to the actual app origin(s) instead of *:
    allow_origins=["tauri://localhost", "http://localhost:1420"]
  2. Drop allow_credentials=True unless cookies/sessions are actually required — this alone prevents cross-origin scripts from reading responses even if an origin match occurs.
  3. Add a local shared-secret token, generated at process startup, written to a file only the Tauri process can read, and required on every request via a header (e.g. X-PictoPy-Token). This protects against DNS-rebinding-style attacks that could still spoof an allowed origin.
  4. Apply the same fix to sync-microservice/main.py.

Acceptance criteria

  • allow_origins set to an explicit list, not *
  • allow_credentials set to False unless a concrete need is documented
  • All state-changing and data-reading endpoints require a valid local auth token
  • Token is generated per-process-start and stored with restrictive file permissions (0600)
  • Fix applied to both backend and sync-microservice
  • Manual test: a fetch() from an arbitrary origin (not the app's own) is rejected with 401

Record

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions