From 3ef0d1543368c8b0280f501a2dc3cb5e0b9c3785 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Thu, 1 Oct 2026 05:21:58 +0200 Subject: [PATCH] fix: audit log shows passwords that contain a double quote (#31659) * fix: audit log shows passwords that contain a double quote With AUDIT_LOG_LEVEL set to REQUEST or REQUEST_RESPONSE, a password containing a double quote was only masked up to that quote, so a new password like Q"secret was logged as "********"secret. A request with whitespace before the colon, such as "new_password" : "secret", was not masked at all. Any field whose name ends in "password" is now masked through its closing quote in both cases. * fix: audit log records passwords sent back in responses With AUDIT_LOG_LEVEL set to REQUEST_RESPONSE, fields whose name ends in "password" were masked in request bodies, but response bodies were logged unmasked. Saving or opening the LDAP server settings therefore wrote the Application DN Password to the audit log in plain text, because the settings come back in the response, and the Jupyter passwords in the code execution settings leaked the same way. Responses now get the same masking as requests. --- backend/open_webui/utils/audit.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/open_webui/utils/audit.py b/backend/open_webui/utils/audit.py index 177036acb6..9c680a7f43 100644 --- a/backend/open_webui/utils/audit.py +++ b/backend/open_webui/utils/audit.py @@ -113,6 +113,17 @@ class AuditContext: self.response_body.extend(chunk[: self.max_body_size - len(self.response_body)]) +def redact_passwords(body: str) -> str: + if 'password' not in body.lower(): + return body + return re.sub( + r'"(\w*password)"\s*:\s*"(?:[^"\\]|\\.)*"', + r'"\1": "********"', + body, + flags=re.IGNORECASE, + ) + + class AuditLoggingMiddleware: """ ASGI middleware that intercepts HTTP requests and responses to perform audit logging. It captures request/response bodies (depending on audit level), headers, HTTP methods, and user information, then logs a structured audit entry at the end of the request cycle. @@ -282,13 +293,8 @@ class AuditLoggingMiddleware: response_body = context.response_body.decode('utf-8', errors='replace') # Redact sensitive information - if 'password' in request_body.lower(): - request_body = re.sub( - r'"(\w*password)":\s*".*?"', - r'"\1": "********"', - request_body, - flags=re.IGNORECASE, - ) + request_body = redact_passwords(request_body) + response_body = redact_passwords(response_body) entry = AuditLogEntry( id=str(uuid.uuid4()),