Is there an existing issue for this?
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
- Run PictoPy normally.
- In a separate, unrelated browser tab, open a page containing:
fetch('http://localhost:52123/images/').then(r => r.json()).then(console.log)
- Observe the full image index (including GPS data) returned to a page with no relation to PictoPy.
Proposed fix
- Restrict CORS to the actual app origin(s) instead of
*:
allow_origins=["tauri://localhost", "http://localhost:1420"]
- 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.
- 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.
- Apply the same fix to
sync-microservice/main.py.
Acceptance criteria
Record
Is there an existing issue for this?
What happened?
Summary
Both local FastAPI servers (
backendon:52123andsync-microserviceon:52124) are configured with: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-130sync-microservice/main.py:36-42Impact
With the app simply running, a malicious or compromised webpage can, without any user interaction:
GET /images/, including file paths, GPS/EXIF data, and timestampsDELETE /folders/delete-foldersDELETE /models/facenetPOST /shutdown/watcher/stop,/shutdown)This also compounds other findings — e.g. the unrestricted
folder_pathinPOST /folders/add-folderbecomes remotely triggerable once this is fixed to require auth, it's no longer exploitable from an arbitrary webpage.Steps to reproduce
Proposed fix
*:allow_credentials=Trueunless cookies/sessions are actually required — this alone prevents cross-origin scripts from reading responses even if an origin match occurs.X-PictoPy-Token). This protects against DNS-rebinding-style attacks that could still spoof an allowed origin.sync-microservice/main.py.Acceptance criteria
allow_originsset to an explicit list, not*allow_credentialsset toFalseunless a concrete need is documented0600)backendandsync-microservicefetch()from an arbitrary origin (not the app's own) is rejected with401Record