mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-01 21:04:52 +03:00
Miscellaneous improvements (#22714)
* scrub genai API keys and onvif credentials from config endpoint
* enforce camera access in thumbnail tracked-object fallback
The /events/{id}/thumbnail endpoint called require_camera_access when
loading persisted events but skipped the check in the tracked-object
fallback path for in-progress events. A restricted viewer could
retrieve thumbnails from cameras they should not have access to.
* block filter and attach flags in custom ffmpeg export args
The ffmpeg argument blocklist missed -filter_complex, -lavfi, -vf,
-af, -filter, and -attach. These flags can read arbitrary files via
source filters like movie= and amovie=, bypassing the existing -i
block. A user with camera access could exploit this through the
custom export endpoint.
* enforce camera access on VLM monitor endpoint
POST /vlm/monitor allowed any authenticated user to start VLM
monitoring on any camera without checking camera access. A viewer
restricted to specific cameras could monitor cameras they should
not have access to.
* enforce camera access in chat start_camera_watch tool
The start_camera_watch tool called via POST /chat/completion did not
validate camera access, allowing a restricted viewer to start VLM
monitoring on cameras outside their allowed set through the chat
interface.
* restrict review summary endpoint to admin role
* fix require_role call passing string instead of list
* fix section config uiSchema merge replacing base entries
mergeSectionConfig was replacing the entire base uiSchema when a
level override (global/camera) also defined one, causing base-level
ui:after/ui:before directives to be silently dropped. This broke
the SemanticSearchReindex button which was defined in base uiSchema.
This commit is contained in:
parent
4695e10341
commit
b821420dee
@ -142,9 +142,20 @@ def config(request: Request):
|
|||||||
# remove the proxy secret
|
# remove the proxy secret
|
||||||
config["proxy"].pop("auth_secret", None)
|
config["proxy"].pop("auth_secret", None)
|
||||||
|
|
||||||
|
# remove genai api keys
|
||||||
|
for genai_name, genai_cfg in config.get("genai", {}).items():
|
||||||
|
if isinstance(genai_cfg, dict):
|
||||||
|
genai_cfg.pop("api_key", None)
|
||||||
|
|
||||||
for camera_name, camera in request.app.frigate_config.cameras.items():
|
for camera_name, camera in request.app.frigate_config.cameras.items():
|
||||||
camera_dict = config["cameras"][camera_name]
|
camera_dict = config["cameras"][camera_name]
|
||||||
|
|
||||||
|
# remove onvif credentials
|
||||||
|
onvif_dict = camera_dict.get("onvif", {})
|
||||||
|
if onvif_dict:
|
||||||
|
onvif_dict.pop("user", None)
|
||||||
|
onvif_dict.pop("password", None)
|
||||||
|
|
||||||
# clean paths
|
# clean paths
|
||||||
for input in camera_dict.get("ffmpeg", {}).get("inputs", []):
|
for input in camera_dict.get("ffmpeg", {}).get("inputs", []):
|
||||||
input["path"] = clean_camera_user_pass(input["path"])
|
input["path"] = clean_camera_user_pass(input["path"])
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from pydantic import BaseModel
|
|||||||
from frigate.api.auth import (
|
from frigate.api.auth import (
|
||||||
allow_any_authenticated,
|
allow_any_authenticated,
|
||||||
get_allowed_cameras_for_filter,
|
get_allowed_cameras_for_filter,
|
||||||
|
require_camera_access,
|
||||||
)
|
)
|
||||||
from frigate.api.defs.query.events_query_parameters import EventsQueryParams
|
from frigate.api.defs.query.events_query_parameters import EventsQueryParams
|
||||||
from frigate.api.defs.request.chat_body import ChatCompletionRequest
|
from frigate.api.defs.request.chat_body import ChatCompletionRequest
|
||||||
@ -672,6 +673,8 @@ async def _execute_start_camera_watch(
|
|||||||
if camera not in config.cameras:
|
if camera not in config.cameras:
|
||||||
return {"error": f"Camera '{camera}' not found."}
|
return {"error": f"Camera '{camera}' not found."}
|
||||||
|
|
||||||
|
await require_camera_access(camera, request=request)
|
||||||
|
|
||||||
genai_manager = request.app.genai_manager
|
genai_manager = request.app.genai_manager
|
||||||
vision_client = genai_manager.vision_client or genai_manager.tool_client
|
vision_client = genai_manager.vision_client or genai_manager.tool_client
|
||||||
if vision_client is None:
|
if vision_client is None:
|
||||||
@ -1156,6 +1159,8 @@ async def start_vlm_monitor(
|
|||||||
status_code=404,
|
status_code=404,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await require_camera_access(body.camera, request=request)
|
||||||
|
|
||||||
vision_client = genai_manager.vision_client or genai_manager.tool_client
|
vision_client = genai_manager.vision_client or genai_manager.tool_client
|
||||||
if vision_client is None:
|
if vision_client is None:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
|
|||||||
@ -896,6 +896,7 @@ async def event_thumbnail(
|
|||||||
if event_id in camera_state.tracked_objects:
|
if event_id in camera_state.tracked_objects:
|
||||||
tracked_obj = camera_state.tracked_objects.get(event_id)
|
tracked_obj = camera_state.tracked_objects.get(event_id)
|
||||||
if tracked_obj is not None:
|
if tracked_obj is not None:
|
||||||
|
await require_camera_access(camera_state.name, request=request)
|
||||||
thumbnail_bytes = tracked_obj.get_thumbnail(extension.value)
|
thumbnail_bytes = tracked_obj.get_thumbnail(extension.value)
|
||||||
except Exception:
|
except Exception:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@ -1066,7 +1067,7 @@ def grid_snapshot(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/{camera_name}/region_grid", dependencies=[Depends(require_role("admin"))]
|
"/{camera_name}/region_grid", dependencies=[Depends(require_role(["admin"]))]
|
||||||
)
|
)
|
||||||
def clear_region_grid(request: Request, camera_name: str):
|
def clear_region_grid(request: Request, camera_name: str):
|
||||||
"""Clear the region grid for a camera."""
|
"""Clear the region grid for a camera."""
|
||||||
|
|||||||
@ -742,7 +742,7 @@ async def set_not_reviewed(
|
|||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/review/summarize/start/{start_ts}/end/{end_ts}",
|
"/review/summarize/start/{start_ts}/end/{end_ts}",
|
||||||
dependencies=[Depends(allow_any_authenticated())],
|
dependencies=[Depends(require_role(["admin"]))],
|
||||||
description="Use GenAI to summarize review items over a period of time.",
|
description="Use GenAI to summarize review items over a period of time.",
|
||||||
)
|
)
|
||||||
def generate_review_summary(request: Request, start_ts: float, end_ts: float):
|
def generate_review_summary(request: Request, start_ts: float, end_ts: float):
|
||||||
|
|||||||
@ -36,7 +36,9 @@ logger = logging.getLogger(__name__)
|
|||||||
DEFAULT_TIME_LAPSE_FFMPEG_ARGS = "-vf setpts=0.04*PTS -r 30"
|
DEFAULT_TIME_LAPSE_FFMPEG_ARGS = "-vf setpts=0.04*PTS -r 30"
|
||||||
TIMELAPSE_DATA_INPUT_ARGS = "-an -skip_frame nokey"
|
TIMELAPSE_DATA_INPUT_ARGS = "-an -skip_frame nokey"
|
||||||
|
|
||||||
# ffmpeg flags that can read from or write to arbitrary files
|
# ffmpeg flags that can read from or write to arbitrary files.
|
||||||
|
# filter flags are blocked because source filters like movie= and
|
||||||
|
# amovie= can read arbitrary files from the filesystem.
|
||||||
BLOCKED_FFMPEG_ARGS = frozenset(
|
BLOCKED_FFMPEG_ARGS = frozenset(
|
||||||
{
|
{
|
||||||
"-i",
|
"-i",
|
||||||
@ -45,6 +47,12 @@ BLOCKED_FFMPEG_ARGS = frozenset(
|
|||||||
"-passlogfile",
|
"-passlogfile",
|
||||||
"-sdp_file",
|
"-sdp_file",
|
||||||
"-dump_attachment",
|
"-dump_attachment",
|
||||||
|
"-filter_complex",
|
||||||
|
"-lavfi",
|
||||||
|
"-vf",
|
||||||
|
"-af",
|
||||||
|
"-filter",
|
||||||
|
"-attach",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -649,8 +649,11 @@ const mergeSectionConfig = (
|
|||||||
return srcValue ?? objValue;
|
return srcValue ?? objValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "uiSchema" && srcValue !== undefined) {
|
if (key === "uiSchema") {
|
||||||
return srcValue;
|
if (objValue && srcValue) {
|
||||||
|
return merge({}, objValue, srcValue);
|
||||||
|
}
|
||||||
|
return srcValue ?? objValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user