Compare commits

...
4 Commits
Author SHA1 Message Date
Blake Blackshear c3cf6cf3cb plus docs update 2026-10-16 09:02:26 -05:00
Josh HawkinsandGitHub aa42b17911 don't log an nginx warning for every authenticated request (#24682)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
Since 0.18.2, every request that went through /auth logged an uninitialized "go2rtc_stream_proxy" variable warning, because only the go2rtc stream locations set that variable. The warning is now turned off in the /auth location, and the header value sent to /auth is unchanged.
2026-10-16 07:44:39 -05:00
Josh HawkinsandGitHub 698b1ce628 bump version in docs (#24673) 2026-10-15 18:52:01 -04:00
Josh HawkinsandGitHub cfa08c56b2 Normalize request paths in media auth (#24662)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* merge repeated slashes in the media auth path check

X-Original-URL carries only the raw request path since #24602, and extract_path parsed it with urlparse, which reads a path starting with // as a host. A restricted role could request another camera's clips or recordings with a leading // and the media check saw a non-media path and allowed it, while nginx merged the slashes and served the file. extract_path now splits the raw URI the same way the go2rtc stream check does and merges repeated slashes as nginx does before matching locations. The split helper moves from auth.py into media_auth.py so both checks share it.

* check the nginx-normalized path in the media auth dot-segment fallback

The dot-segment fallback only checked the raw first segment, so a role restricted to one camera could request `/x/../clips/back_door-1.jpg` and nginx served back_door's clip. The fallback now resolves the path the way nginx does before checking for a media root, and the URI split ends at a literal `#` since nginx ignores everything after it.
2026-10-15 07:02:08 -05:00
12 changed files with 134 additions and 64 deletions
@@ -5,6 +5,8 @@ location /auth {
## Essential Proxy Configuration ## Essential Proxy Configuration
internal; internal;
proxy_pass $upstream_auth; proxy_pass $upstream_auth;
# $go2rtc_stream_proxy is only set by the go2rtc stream locations
uninitialized_variable_warn off;
## Headers ## Headers
+1 -1
View File
@@ -87,7 +87,7 @@ Along with picking a detector for your hardware, you will choose a model's **inp
:::tip :::tip
The best detection accuracy comes from a model trained on images that look like what Frigate actually sees: security camera footage cropped to regions of interest. You can train or fine-tune your own model on images like this and run it as a custom model (see the per-detector sections below), but [Frigate+](/plus) makes this much easier by handling the training for you on images submitted from your own cameras. For YOLOv9, the `s` (small) variant at `320x320` resolution is a good place to start. The best detection accuracy comes from a model trained on images that look like what Frigate actually sees: security camera footage cropped to regions of interest. You can train or fine-tune your own model on images like this and run it as a custom model (see the per-detector sections below), but [Frigate+](/plus) makes this much easier by handling the training for you on images submitted from your own cameras. For YOLOv9, the `s` (small) variant at `320x320` resolution is a good place to start, and the larger `c` variant is available for more powerful hardware.
::: :::
+7 -7
View File
@@ -5,7 +5,7 @@ title: Updating
# Updating Frigate # Updating Frigate
The current stable version of Frigate is **0.18.1**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.18.1). The current stable version of Frigate is **0.18.2**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.18.2).
Keeping Frigate up to date ensures you benefit from the latest features, performance improvements, and bug fixes. The update process varies slightly depending on your installation method (Docker, Home Assistant App, etc.). Below are instructions for the most common setups. Keeping Frigate up to date ensures you benefit from the latest features, performance improvements, and bug fixes. The update process varies slightly depending on your installation method (Docker, Home Assistant App, etc.). Below are instructions for the most common setups.
@@ -31,21 +31,21 @@ If you’re running Frigate via Docker (recommended method), follow these steps:
2. **Update and Pull the Latest Image**: 2. **Update and Pull the Latest Image**:
- If using Docker Compose: - If using Docker Compose:
- Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.18.1` instead of `0.17.1`). For example: - Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.18.2` instead of `0.17.1`). For example:
```yaml ```yaml
services: services:
frigate: frigate:
image: ghcr.io/blakeblackshear/frigate:0.18.1 image: ghcr.io/blakeblackshear/frigate:0.18.2
``` ```
- Then pull the image: - Then pull the image:
```bash ```bash
docker pull ghcr.io/blakeblackshear/frigate:0.18.1 docker pull ghcr.io/blakeblackshear/frigate:0.18.2
``` ```
- **Note for `stable` Tag Users**: If your `docker-compose.yml` uses the `stable` tag (e.g., `ghcr.io/blakeblackshear/frigate:stable`), you don’t need to update the tag manually. The `stable` tag always points to the latest stable release after pulling. - **Note for `stable` Tag Users**: If your `docker-compose.yml` uses the `stable` tag (e.g., `ghcr.io/blakeblackshear/frigate:stable`), you don’t need to update the tag manually. The `stable` tag always points to the latest stable release after pulling.
- If using `docker run`: - If using `docker run`:
- Pull the image with the appropriate tag (e.g., `0.18.1`, `0.18.1-tensorrt`, or `stable`): - Pull the image with the appropriate tag (e.g., `0.18.2`, `0.18.2-tensorrt`, or `stable`):
```bash ```bash
docker pull ghcr.io/blakeblackshear/frigate:0.18.1 docker pull ghcr.io/blakeblackshear/frigate:0.18.2
``` ```
3. **Start the Container**: 3. **Start the Container**:
@@ -100,7 +100,7 @@ If an update causes issues:
1. Stop Frigate. 1. Stop Frigate.
2. Restore your backed-up config file and database. 2. Restore your backed-up config file and database.
3. Revert to the previous image version: 3. Revert to the previous image version:
- For Docker: Specify an older tag (e.g., `ghcr.io/blakeblackshear/frigate:0.18.0`) in your `docker run` command. - For Docker: Specify an older tag (e.g., `ghcr.io/blakeblackshear/frigate:0.18.1`) in your `docker run` command.
- For Docker Compose: Edit your `docker-compose.yml`, specify the older version tag (e.g., `ghcr.io/blakeblackshear/frigate:0.16.4`), and re-run `docker compose up -d`. - For Docker Compose: Edit your `docker-compose.yml`, specify the older version tag (e.g., `ghcr.io/blakeblackshear/frigate:0.16.4`), and re-run `docker compose up -d`.
- For Home Assistant: Restore from the app/addon backup you took before you updated. - For Home Assistant: Restore from the app/addon backup you took before you updated.
4. Verify the old version is running again. 4. Verify the old version is running again.
+1 -1
View File
@@ -59,7 +59,7 @@ You can view all of your submitted images at [https://plus.frigate.video](https:
Once you have [requested your first model](../plus/first_model.md) and gotten your own model ID, it can be used with a special model path. No other information needs to be configured for Frigate+ models because it fetches the remaining config from Frigate+ automatically. Once you have [requested your first model](../plus/first_model.md) and gotten your own model ID, it can be used with a special model path. No other information needs to be configured for Frigate+ models because it fetches the remaining config from Frigate+ automatically.
You can either choose the new model from the <NavPath path="Settings > System > Detectors and model" /> pane in the Frigate UI (the **Frigate+ Model** tab), or manually set the model at the root level in your config: You can either choose the new model from the <NavPath path="Settings > System > Detectors and model" /> pane in the Frigate UI (the **Frigate+** tab), or manually set the model at the root level in your config:
```yaml ```yaml
detectors: ... detectors: ...
+15
View File
@@ -25,6 +25,12 @@ If you have an active Frigate+ subscription, new uploads will be scanned for the
Suggestions are converted to labels when saving, so you should remove any errant suggestions. There is already some logic designed to avoid duplicate labels, but you may still occasionally see some duplicate suggestions. You should keep the most accurate bounding box and delete any duplicates so that you have just one label per object remaining. Suggestions are converted to labels when saving, so you should remove any errant suggestions. There is already some logic designed to avoid duplicate labels, but you may still occasionally see some duplicate suggestions. You should keep the most accurate bounding box and delete any duplicates so that you have just one label per object remaining.
## Reviewing annotations with Patches
The Patches tab on the Images page shows a cropped view of every annotation for a label in a grid. Scanning the crops makes it much easier to spot a mislabeled object than paging through full images one at a time. Pick a label to get started, and optionally narrow the results to a single camera. Clicking a patch opens that image in the editor so you can fix the annotation.
Patches only include annotations from images you have already verified.
## False positive labels ## False positive labels
False positives will be shown with a red box and the label will have a strike through. These can't be adjusted, but they can be deleted if you accidentally submit a true positive as a false positive from Frigate. False positives will be shown with a red box and the label will have a strike through. These can't be adjusted, but they can be deleted if you accidentally submit a true positive as a false positive from Frigate.
@@ -51,3 +57,12 @@ Misidentified objects should have a correct label added. For example, if a perso
| `scrollwheel` | Zoom in/out | | `scrollwheel` | Zoom in/out |
| `f` | Hide/show all but current box | | `f` | Hide/show all but current box |
| `spacebar` | Verify and save | | `spacebar` | Verify and save |
## Deleting images
In most cases, you should keep your existing images. Removing training data usually makes accuracy worse, not better, so you shouldn't delete images just because you think you made some annotation mistakes. Deleting images makes sense when you move to a new location, or significantly upgrade or reposition your cameras.
- To delete all images for a camera, use the **Delete Images** button for that camera on the Cameras page.
- To delete all images in your account, use **Delete All Images** on the Settings page.
Deletions run in the background, and their progress is shown in the bulk operations panel. Deleted images cannot be recovered and are no longer used to train your models.
+2
View File
@@ -26,7 +26,9 @@ Yes. Subscriptions to Frigate+ provide access to the infrastructure used to trai
### Can I use Frigate+ models commercially? ### Can I use Frigate+ models commercially?
A standard subscription covers use on camera systems you own or operate, including for your business. A shop, restaurant, warehouse, or office running Frigate+ at its own locations (including multiple locations) is exactly the kind of use the subscription is for. A standard subscription covers use on camera systems you own or operate, including for your business. A shop, restaurant, warehouse, or office running Frigate+ at its own locations (including multiple locations) is exactly the kind of use the subscription is for.
What the standard subscription does not cover is using Frigate+ models to provide a product or service to others. If you're deploying models at your customers' sites, bundling them with hardware you sell, or running them as part of a hosted or managed service, even if your customers never receive the model files themselves, you'll need a commercial license. What the standard subscription does not cover is using Frigate+ models to provide a product or service to others. If you're deploying models at your customers' sites, bundling them with hardware you sell, or running them as part of a hosted or managed service, even if your customers never receive the model files themselves, you'll need a commercial license.
Note that professional installers are fine under standard subscriptions when each customer holds their own Frigate+ subscription. The commercial license is for cases where your license powers your customers' sites. Note that professional installers are fine under standard subscriptions when each customer holds their own Frigate+ subscription. The commercial license is for cases where your license powers your customers' sites.
### Why can't I submit images to Frigate+? ### Why can't I submit images to Frigate+?
+1 -1
View File
@@ -17,7 +17,7 @@ For more detailed recommendations, you can refer to the docs on [annotating](./a
## Step 2: Submit a model request ## Step 2: Submit a model request
Once you have an initial set of verified images, you can request a model on the Models page. For guidance on choosing a model type, refer to [this part of the documentation](./index.md#available-model-types). If you are unsure which type to request, you can test the base model for each version from the "Base Models" tab. Each model request requires 1 of the 12 trainings that you receive with your annual subscription. This model will support all [label types available](./index.md#available-label-types) even if you do not submit any examples for those labels. Model creation can take up to 36 hours. Once you have an initial set of verified images, you can request a model on the Models page. For guidance on choosing a model type, refer to [this part of the documentation](./index.md#available-model-types). If you are unsure which type to request, you can test the base model for each version from the "Base Models" tab. For most hardware, `yolov9` is recommended; `mobiledet` is a legacy model type and will be removed in 2027. Each model request requires 1 of the 12 trainings that you receive with your annual subscription. This model will support all [label types available](./index.md#available-label-types) even if you do not submit any examples for those labels. Model creation can take up to 36 hours.
![Plus Models Page](/img/plus/plus-models.jpg) ![Plus Models Page](/img/plus/plus-models.jpg)
## Step 3: Set your model ## Step 3: Set your model
+15 -9
View File
@@ -5,25 +5,31 @@ title: Models
<a href="https://frigate.video/plus" target="_blank" rel="nofollow">Frigate+</a> offers models trained on images submitted by Frigate+ users from their security cameras and is specifically designed for the way Frigate NVR analyzes video footage. These models offer higher accuracy with less resources. The images you upload are used to fine tune a base model trained from images uploaded by all Frigate+ users. This fine tuning process results in a model that is optimized for accuracy in your specific conditions. <a href="https://frigate.video/plus" target="_blank" rel="nofollow">Frigate+</a> offers models trained on images submitted by Frigate+ users from their security cameras and is specifically designed for the way Frigate NVR analyzes video footage. These models offer higher accuracy with less resources. The images you upload are used to fine tune a base model trained from images uploaded by all Frigate+ users. This fine tuning process results in a model that is optimized for accuracy in your specific conditions.
With a subscription, 12 model trainings to fine tune your model per year are included. In addition, you will have access to any base models published while your subscription is active. If you cancel your subscription, you will retain access to any trained and base models in your account. An active subscription is required to submit model requests or purchase additional trainings. New base models are published quarterly with target dates of January 15th, April 15th, July 15th, and October 15th. With a subscription, 12 model trainings to fine tune your model per year are included. In addition, you will have access to any base models published while your subscription is active. If you cancel your subscription, you will retain access to any trained and base models in your account. An active subscription is required to submit model requests or purchase additional trainings. New base models are published twice per year.
Information on how to integrate Frigate+ with Frigate can be found in the [integration docs](../integrations/plus.md). Information on how to integrate Frigate+ with Frigate can be found in the [integration docs](../integrations/plus.md).
## Available model types ## Available model types
There are three model types offered in Frigate+, `mobiledet`, `yolonas`, and `yolov9`. All of these models are object detection models and are trained to detect the same set of labels [listed below](#available-label-types). There are three model types offered in Frigate+, `yolov9`, `yolonas`, and `mobiledet` (legacy). All of these models are object detection models and are trained to detect the same set of labels [listed below](#available-label-types).
Not all model types are supported by all detectors, so it's important to choose a model type to match your detector as shown in the table under [supported detector types](#supported-detector-types). You can test model types for compatibility and speed on your hardware by using the base models. Not all model types are supported by all detectors, so it's important to choose a model type to match your detector as shown in the table under [supported detector types](#supported-detector-types). You can test model types for compatibility and speed on your hardware by using the base models.
| Model Type | Description | | Model Type | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mobiledet` | Based on the same architecture as the default model included with Frigate. Runs on Google Coral devices and CPUs. |
| `yolonas` | A newer architecture that offers slightly higher accuracy and improved detection of small objects. Runs on Intel, NVidia GPUs, and AMD GPUs. |
| `yolov9` | A leading SOTA (state of the art) object detection model with similar performance to yolonas, but on a wider range of hardware options. Runs on most hardware. | | `yolov9` | A leading SOTA (state of the art) object detection model with similar performance to yolonas, but on a wider range of hardware options. Runs on most hardware. |
| `yolonas` | A newer architecture that offers slightly higher accuracy and improved detection of small objects. Runs on Intel, NVidia GPUs, and AMD GPUs. |
| `mobiledet` | Legacy. Based on the same architecture as the default model included with Frigate. Runs on Google Coral devices and CPUs. |
:::warning
`mobiledet` is a legacy model type. Base model 2026.3 is the last base model update for `mobiledet`, and it is planned to be removed from Frigate+ in 2027. If you are using `mobiledet` with a Google Coral, request a `yolov9` model with the `edgetpu` variant instead.
:::
### YOLOv9 Details ### YOLOv9 Details
YOLOv9 models are available in `s`, `t`, `edgetpu` variants. When requesting a `yolov9` model, you will be prompted to choose a variant. If you want the model to be compatible with a Google Coral, you will need to choose the `edgetpu` variant. If you are unsure what variant to choose, you should perform some tests with the base models to find the performance level that suits you. The `s` size is most similar to the current `yolonas` models in terms of inference times and accuracy, and a good place to start is the `320x320` resolution model for `yolov9s`. YOLOv9 models are available in `t`, `s`, `c`, and `edgetpu` variants, and an `m` size is coming soon. When requesting a `yolov9` model, you will be prompted to choose a variant. If you want the model to be compatible with a Google Coral, you will need to choose the `edgetpu` variant, which requires Frigate 0.17 or later. If you are unsure what variant to choose, you should perform some tests with the base models to find the performance level that suits you. The `s` size is most similar to the current `yolonas` models in terms of inference times and accuracy, and a good place to start is the `320x320` resolution model for `yolov9s`. The `c` size is larger and offers higher accuracy at the cost of slower inference, so it is best suited to more powerful hardware such as GPUs.
:::info :::info
@@ -33,7 +39,7 @@ When switching to YOLOv9, you may need to adjust your thresholds for some object
#### Hailo Support #### Hailo Support
If you have a Hailo device, you will need to specify the hardware you have when submitting a model request because they are not cross compatible. Please test using the available base models before submitting your model request. If you have a Hailo device, you will need to specify the hardware you have when submitting a model request because they are not cross compatible. Hailo models are available for the `t`, `s`, and `c` variants on Hailo8, Hailo8L, and Hailo8R devices. Please test using the available base models before submitting your model request.
#### Rockchip (RKNN) Support #### Rockchip (RKNN) Support
@@ -45,7 +51,7 @@ Currently, Frigate+ models support CPU (`cpu`), Google Coral (`edgetpu`), OpenVi
| Hardware | Recommended Detector Type | Recommended Model Type | | Hardware | Recommended Detector Type | Recommended Model Type |
| -------------------------------------------------------------------------------- | ------------------------- | ---------------------- | | -------------------------------------------------------------------------------- | ------------------------- | ---------------------- |
| [CPU](/configuration/object_detectors.md#cpu-detector-not-recommended) | `cpu` | `mobiledet` | | [CPU](/configuration/object_detectors.md#cpu-detector-not-recommended) | `cpu` | `yolov9` |
| [Coral (all form factors)](/configuration/object_detectors.md#edge-tpu-detector) | `edgetpu` | `yolov9` | | [Coral (all form factors)](/configuration/object_detectors.md#edge-tpu-detector) | `edgetpu` | `yolov9` |
| [Intel](/configuration/object_detectors.md#openvino-detector) | `openvino` | `yolov9` | | [Intel](/configuration/object_detectors.md#openvino-detector) | `openvino` | `yolov9` |
| [NVidia GPU](/configuration/object_detectors#onnx) | `onnx` | `yolov9` | | [NVidia GPU](/configuration/object_detectors#onnx) | `onnx` | `yolov9` |
@@ -66,7 +72,7 @@ Some users may find that Frigate+ models result in more false positives initiall
Frigate+ models support a more relevant set of objects for security cameras. The labels for annotation in Frigate+ are configurable by editing the camera in the Cameras section of Frigate+. Currently, the following objects are supported: Frigate+ models support a more relevant set of objects for security cameras. The labels for annotation in Frigate+ are configurable by editing the camera in the Cameras section of Frigate+. Currently, the following objects are supported:
- **People**: `person`, `face`, `baby` - **People**: `person`, `face`, `baby`
- **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `school_bus`, `garbage truck`, `license_plate` - **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `school_bus`, `garbage_truck`, `license_plate`
- **Delivery Logos**: `amazon`, `usps`, `ups`, `fedex`, `dhl`, `an_post`, `purolator`, `postnl`, `nzpost`, `postnord`, `gls`, `dpd`, `canada_post`, `royal_mail` - **Delivery Logos**: `amazon`, `usps`, `ups`, `fedex`, `dhl`, `an_post`, `purolator`, `postnl`, `nzpost`, `postnord`, `gls`, `dpd`, `canada_post`, `royal_mail`
- **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`, `skunk`, `kangaroo`, `possum`, `rodent` - **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`, `skunk`, `kangaroo`, `possum`, `rodent`
- **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`, `baby_stroller` - **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`, `baby_stroller`
@@ -81,7 +87,7 @@ Where possible, these labels are mapped to existing labels during training. For
- **Vehicles**: `tractor`, `golf_cart`, `bus`, `airplane`, `helicopter`, `rickshaw`, `scooter` - **Vehicles**: `tractor`, `golf_cart`, `bus`, `airplane`, `helicopter`, `rickshaw`, `scooter`
- **Delivery Logos**: `bpost`, `auspost`, `aramex`, `transoflex`, `parcelforce`, `hermes`, `cargus`, `fan_courier`, `sameday`, `la_poste` - **Delivery Logos**: `bpost`, `auspost`, `aramex`, `transoflex`, `parcelforce`, `hermes`, `cargus`, `fan_courier`, `sameday`, `la_poste`
- **Animals**: `badger`, `chicken`, `duck`, `turkey`, `groundhog`, `boar`, `hedgehog`, `wombat`, `bobcat`, `mustelid`, `mountain_lion`, `crocodile`, `monkey`, `coyote`, `porcupine`, `sheep`, `snake`, `lizard`, `heron`, `elk`, `moose`, `pig`, `donkey`, `civet` - **Animals**: `badger`, `chicken`, `duck`, `turkey`, `groundhog`, `boar`, `hedgehog`, `wombat`, `bobcat`, `mustelid`, `mountain_lion`, `crocodile`, `monkey`, `coyote`, `porcupine`, `sheep`, `snake`, `lizard`, `heron`, `elk`, `moose`, `pig`, `donkey`, `civet`, `chipmunk`
- **Other**: `sports_ball`, `drone`, `lawnmower` - **Other**: `sports_ball`, `drone`, `lawnmower`
Candidate labels are not available for automatic suggestions. Candidate labels are not available for automatic suggestions.
+1 -1
View File
@@ -162,7 +162,7 @@ Open one of the tracked objects in Explore → **Tracking Details**. If the dete
What helps: What helps:
- **Improve the view**: even a small angle change that gets more of the car visible could lift scores enough to stabilize tracking. - **Improve the view**: even a small angle change that gets more of the car visible could lift scores enough to stabilize tracking.
- **Use a more accurate model**: switching from `mobiledet` to `yolov9`, or stepping up to a larger variant like `yolov9-s` over `yolov9-t`, can help (at the cost of inference time, and still on the COCO dataset). The biggest gains usually come from fine-tuning a model on images from your own cameras so it learns your specific scene. [Frigate+](https://frigate.video/plus) is a paid option that does this - models are trained on security-camera footage and can be fine-tuned on images you submit from your own setup. - **Use a more accurate model**: switching from `mobiledet` to `yolov9`, or stepping up to a larger variant like `yolov9-s` over `yolov9-t` (or `yolov9-c` on more powerful hardware), can help (at the cost of inference time, and still on the COCO dataset). The biggest gains usually come from fine-tuning a model on images from your own cameras so it learns your specific scene. [Frigate+](https://frigate.video/plus) is a paid option that does this - models are trained on security-camera footage and can be fine-tuned on images you submit from your own setup.
- **Don't set `detect -> stationary -> max_frames` for `car`**: it artificially ends tracking and forces re-detection as a new object. See [Stationary Objects](../configuration/stationary_objects.md). - **Don't set `detect -> stationary -> max_frames` for `car`**: it artificially ends tracking and forces re-detection as a new object. See [Stationary Objects](../configuration/stationary_objects.md).
- **Restrict alerts to the areas you care about** with `required_zones`. See [Zones](../configuration/zones.md#restricting-alerts-and-detections-to-specific-zones). Make sure those zones use the default `loitering_time: 0` unless you specifically want the review item to stay open until the car leaves. - **Restrict alerts to the areas you care about** with `required_zones`. See [Zones](../configuration/zones.md#restricting-alerts-and-detections-to-specific-zones). Make sure those zones use the default `loitering_time: 0` unless you specifically want the review item to stay open until the car leaves.
- **Filter impossible locations** with [object filter masks](../configuration/masks.md#object-filter-masks) if cars are being detected on rooftops, treetops, etc. - **Filter impossible locations** with [object filter masks](../configuration/masks.md#object-filter-masks) if cars are being detected on rooftops, treetops, etc.
+5 -34
View File
@@ -11,7 +11,7 @@ import secrets
import time import time
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from urllib.parse import parse_qs, unquote from urllib.parse import parse_qs
import requests import requests
from fastapi import APIRouter, Depends, HTTPException, Request, Response from fastapi import APIRouter, Depends, HTTPException, Request, Response
@@ -31,6 +31,8 @@ from frigate.api.media_auth import (
check_camera_access, check_camera_access,
deny_response_for_media_uri, deny_response_for_media_uri,
is_role_restricted, is_role_restricted,
normalize_request_path,
split_request_uri,
) )
from frigate.config import AuthConfig, ProxyConfig from frigate.config import AuthConfig, ProxyConfig
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
@@ -1185,37 +1187,6 @@ GO2RTC_STREAM_PROXY_PATHS = frozenset(
} }
) )
_URI_SCHEME_AND_AUTHORITY = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://[^/?]*")
def _split_request_uri(request_uri: str) -> tuple[str, str]:
"""Split a raw request URI into its path and query string.
urlparse is not used since it reads a path starting with `//` as a host.
"""
uri = _URI_SCHEME_AND_AUTHORITY.sub("", request_uri, count=1)
path, _, query = uri.partition("?")
return path, query
def _normalize_request_path(path: str) -> str:
"""Normalize a raw request path the way nginx does before matching locations.
nginx percent-decodes the path, merges repeated slashes and resolves dot
segments, while `X-Original-URL` carries the raw `$request_uri`.
"""
segments: list[str] = []
for segment in unquote(path).split("/"):
if segment in ("", "."):
continue
if segment == "..":
if segments:
segments.pop()
continue
segments.append(segment)
return "/" + "/".join(segments)
def _is_go2rtc_stream_proxy_request(path: str, request: Request) -> bool: def _is_go2rtc_stream_proxy_request(path: str, request: Request) -> bool:
# nginx marks requests routed through the go2rtc stream locations, and the # nginx marks requests routed through the go2rtc stream locations, and the
@@ -1223,7 +1194,7 @@ def _is_go2rtc_stream_proxy_request(path: str, request: Request) -> bool:
if request.headers.get("x-go2rtc-stream-proxy") == "1": if request.headers.get("x-go2rtc-stream-proxy") == "1":
return True return True
normalized = _normalize_request_path(path) normalized = normalize_request_path(path)
return any(normalized.startswith(p) for p in GO2RTC_STREAM_PROXY_PATHS) return any(normalized.startswith(p) for p in GO2RTC_STREAM_PROXY_PATHS)
@@ -1242,7 +1213,7 @@ def deny_response_for_go2rtc_stream(
if not original_url: if not original_url:
return None return None
path, query = _split_request_uri(original_url) path, query = split_request_uri(original_url)
if not _is_go2rtc_stream_proxy_request(path, request): if not _is_go2rtc_stream_proxy_request(path, request):
return None return None
+42 -10
View File
@@ -13,8 +13,9 @@ from __future__ import annotations
import logging import logging
import os import os
import re
from enum import Enum from enum import Enum
from urllib.parse import unquote, urlparse from urllib.parse import unquote
from peewee import DoesNotExist from peewee import DoesNotExist
@@ -24,6 +25,8 @@ from frigate.models import Export, User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_URI_SCHEME_AND_AUTHORITY = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://[^/?]*")
class MediaAuthResolution(str, Enum): class MediaAuthResolution(str, Enum):
"""Classification of an `X-Original-URL` path for media-auth purposes.""" """Classification of an `X-Original-URL` path for media-auth purposes."""
@@ -41,6 +44,36 @@ class MediaAuthResolution(str, Enum):
UNKNOWN = "unknown" UNKNOWN = "unknown"
def split_request_uri(request_uri: str) -> tuple[str, str]:
"""Split a raw request URI into its path and query string.
urlparse is not used since it reads a path starting with `//` as a host.
nginx ends the URI at a literal `#`, so anything after it is dropped.
"""
uri = _URI_SCHEME_AND_AUTHORITY.sub("", request_uri, count=1)
path, _, query = uri.partition("#")[0].partition("?")
return path, query
def normalize_request_path(path: str) -> str:
"""Normalize a raw request path the way nginx does before matching locations.
nginx percent-decodes the path, merges repeated slashes and resolves dot
segments, while `X-Original-URL` carries the raw `$request_uri`.
"""
segments: list[str] = []
for segment in unquote(path).split("/"):
if segment in ("", "."):
continue
if segment == "..":
if segments:
segments.pop()
continue
segments.append(segment)
return "/" + "/".join(segments)
def extract_path(original_url: str | None) -> str | None: def extract_path(original_url: str | None) -> str | None:
"""Return the decoded path component of nginx's `X-Original-URL` header. """Return the decoded path component of nginx's `X-Original-URL` header.
@@ -49,14 +82,14 @@ def extract_path(original_url: str | None) -> str | None:
request like `/recordings/.../allowed_cam/../forbidden_cam/file.mp4` request like `/recordings/.../allowed_cam/../forbidden_cam/file.mp4`
would (1) parse as the allowed camera in our auth check, (2) be served would (1) parse as the allowed camera in our auth check, (2) be served
as the forbidden camera by nginx. To close the bypass we reject any URI as the forbidden camera by nginx. To close the bypass we reject any URI
whose path contains `.` or `..` segments outright. whose path contains `.` or `..` segments outright. Repeated slashes are
merged, as nginx does before matching locations.
""" """
if not original_url: if not original_url:
return None return None
parsed = urlparse(original_url) path, _ = split_request_uri(original_url)
raw_path = parsed.path or original_url decoded = re.sub(r"/{2,}", "/", unquote(path))
decoded = unquote(raw_path)
if not decoded: if not decoded:
return None return None
@@ -252,12 +285,11 @@ def deny_response_for_media_uri(
path = extract_path(original_url) path = extract_path(original_url)
# `extract_path` returns None for URIs containing `.` or `..` segments. # `extract_path` returns None for URIs containing `.` or `..` segments.
# For media-root URIs that's a traversal attempt — deny outright. For # Deny them when nginx would resolve them under a media root, and pass
# non-media URIs, pass through (nginx / the backend handle them). # the rest through to nginx and the backend.
if path is None: if path is None:
raw = urlparse(original_url).path or original_url normalized = normalize_request_path(split_request_uri(original_url)[0])
decoded = unquote(raw) first = normalized.lstrip("/").split("/", 1)[0]
first = decoded.lstrip("/").split("/", 1)[0] if decoded else ""
if first in ("clips", "recordings", "exports"): if first in ("clips", "recordings", "exports"):
return 403 return 403
return None return None
+42
View File
@@ -76,6 +76,20 @@ class TestExtractPath(unittest.TestCase):
self.assertIsNone(extract_path(None)) self.assertIsNone(extract_path(None))
self.assertIsNone(extract_path("")) self.assertIsNone(extract_path(""))
def test_leading_double_slash_is_path(self):
self.assertEqual(
extract_path("//clips/front_door-1.jpg"), "/clips/front_door-1.jpg"
)
def test_repeated_slashes_merged(self):
self.assertEqual(
extract_path("/recordings//2026-05-11/14//front_door/00.00.mp4"),
"/recordings/2026-05-11/14/front_door/00.00.mp4",
)
self.assertEqual(
extract_path("/%2Fclips/front_door-1.jpg"), "/clips/front_door-1.jpg"
)
class TestResolveMediaUri(unittest.TestCase): class TestResolveMediaUri(unittest.TestCase):
def setUp(self): def setUp(self):
@@ -376,6 +390,34 @@ class TestDenyResponseForMediaUri(unittest.TestCase):
self.assertIsNone(self._deny("/clips/nonexistent-1.jpg", "admin")) self.assertIsNone(self._deny("/clips/nonexistent-1.jpg", "admin"))
self.assertIsNone(self._deny("/clips/nonexistent-1.jpg", "viewer")) self.assertIsNone(self._deny("/clips/nonexistent-1.jpg", "viewer"))
def test_repeated_slashes_resolve_to_owning_camera(self):
# nginx merges repeated slashes before matching the media locations
for url in (
"//clips/back_door-1.jpg",
"/clips//back_door-1.jpg",
"//recordings/2026-05-11/14/back_door/00.00.mp4",
"/recordings//2026-05-11/14/back_door/00.00.mp4",
):
self.assertEqual(self._deny(url, "limited_user"), 403, url)
self.assertIsNone(self._deny("//clips/front_door-1.jpg", "limited_user"))
def test_traversal_with_leading_double_slash_denied(self):
self.assertEqual(
self._deny("//clips/front_door/../back_door-1.jpg", "admin"), 403
)
def test_traversal_from_non_media_prefix_denied(self):
# nginx resolves these into a media root before serving
for url in (
"/x/../clips/back_door-1.jpg",
"/x/%2E%2E/clips/back_door-1.jpg",
"/./clips/front_door/../back_door-1.jpg",
"/api/../recordings/2026-05-11/14/back_door/00.00.mp4",
"/clips/back_door-1.jpg#/../../x",
):
self.assertEqual(self._deny(url, "limited_user"), 403, url)
self.assertIsNone(self._deny("/x/../api/events", "limited_user"))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()