feat: support whitelist filtering in AuditLoggingMiddleware (#22515)

Add AUDIT_INCLUDED_PATHS env var for whitelist-based audit filtering.
When set, only matching paths are audited and AUDIT_EXCLUDED_PATHS is
ignored. Auth endpoints (signin/signout/signup) are always logged
regardless of filtering mode.
This commit is contained in:
Timothy Jaeryang Baek
2026-03-11 15:41:42 -05:00
parent 1b1abdd30c
commit afa0609ece
3 changed files with 27 additions and 2 deletions

View File

@@ -964,6 +964,12 @@ AUDIT_EXCLUDED_PATHS = os.getenv("AUDIT_EXCLUDED_PATHS", "/chats,/chat,/folders"
AUDIT_EXCLUDED_PATHS = [path.strip() for path in AUDIT_EXCLUDED_PATHS]
AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
# Comma separated list of urls to include in audit (whitelist mode)
# When set, only these paths are audited and AUDIT_EXCLUDED_PATHS is ignored
AUDIT_INCLUDED_PATHS = os.getenv("AUDIT_INCLUDED_PATHS", "").split(",")
AUDIT_INCLUDED_PATHS = [path.strip() for path in AUDIT_INCLUDED_PATHS]
AUDIT_INCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_INCLUDED_PATHS if path]
####################################
# OPENTELEMETRY

View File

@@ -467,6 +467,7 @@ from open_webui.env import (
ENABLE_CUSTOM_MODEL_FALLBACK,
LICENSE_KEY,
AUDIT_EXCLUDED_PATHS,
AUDIT_INCLUDED_PATHS,
AUDIT_LOG_LEVEL,
CHANGELOG,
REDIS_URL,
@@ -1581,6 +1582,7 @@ if audit_level != AuditLevel.NONE:
AuditLoggingMiddleware,
audit_level=audit_level,
excluded_paths=AUDIT_EXCLUDED_PATHS,
included_paths=AUDIT_INCLUDED_PATHS,
max_body_size=MAX_BODY_LOG_SIZE,
)
##################################

View File

@@ -24,7 +24,7 @@ from asgiref.typing import (
from loguru import logger
from starlette.requests import Request
from open_webui.env import AUDIT_LOG_LEVEL, MAX_BODY_LOG_SIZE
from open_webui.env import AUDIT_LOG_LEVEL, AUDIT_INCLUDED_PATHS, MAX_BODY_LOG_SIZE
from open_webui.utils.auth import get_current_user, get_http_authorization_cred
from open_webui.models.users import UserModel
@@ -129,15 +129,23 @@ class AuditLoggingMiddleware:
app: ASGI3Application,
*,
excluded_paths: Optional[list[str]] = None,
included_paths: Optional[list[str]] = None,
max_body_size: int = MAX_BODY_LOG_SIZE,
audit_level: AuditLevel = AuditLevel.NONE,
) -> None:
self.app = app
self.audit_logger = AuditLogger(logger)
self.excluded_paths = excluded_paths or []
self.included_paths = included_paths or []
self.max_body_size = max_body_size
self.audit_level = audit_level
if self.included_paths and self.excluded_paths:
logger.warning(
"Both AUDIT_INCLUDED_PATHS and AUDIT_EXCLUDED_PATHS are set. "
"AUDIT_INCLUDED_PATHS (whitelist) takes precedence."
)
async def __call__(
self,
scope: ASGIScope,
@@ -226,7 +234,16 @@ class AuditLoggingMiddleware:
):
return True
# match either /api/<resource>/...(for the endpoint /api/chat case) or /api/v1/<resource>/...
# Whitelist mode: only log paths that match included_paths
if self.included_paths:
pattern = re.compile(
r"^/api(?:/v1)?/(" + "|".join(self.included_paths) + r")\b"
)
if not pattern.match(request.url.path):
return True # Skip: path not in whitelist
return False # Do NOT skip: path is in whitelist
# Blacklist mode: skip paths that match excluded_paths
pattern = re.compile(
r"^/api(?:/v1)?/(" + "|".join(self.excluded_paths) + r")\b"
)