open-webui-0001: CWE-312 logged secret in PersistentConfig Redis sync

AppConfig.__getattr__ logs raw decoded values when config keys change
via Redis, including OPENAI_API_KEYS, GOOGLE_CLIENT_SECRET, and 20+
other API key/token/password PersistentConfig entries. Fix: redact
values for keys matching SECRET/KEY/TOKEN/PASSWORD/CREDENTIAL denylist.

13/13 PASS
This commit is contained in:
russell@unturf.com 2026-03-31 11:43:57 -04:00
parent 5a8e0836c3
commit f85ccf9b99
3 changed files with 116 additions and 1 deletions

View file

@ -945,5 +945,9 @@
"supertuxkart-0001-0001": "UNDF-2026-000000944",
"supertuxkart-0002-0002": "UNDF-2026-000000945",
"minetest-0004-0004": "UNDF-2026-000000946",
"minetest-0005-0005": "UNDF-2026-000000947"
"minetest-0005-0005": "UNDF-2026-000000947",
"open-webui-0001-0001": "UNDF-2026-000000948",
"tiled-0001-0001": "UNDF-2026-000000949",
"tiled-0002-0002": "UNDF-2026-000000950",
"tiled-0003-0003": "UNDF-2026-000000951"
}

View file

@ -0,0 +1,24 @@
# UNDF: UNDF-2026-000000948
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -266,10 +266,17 @@
decoded_value = json.loads(redis_value)
# Update the in-memory value if different
if self._state[key].value != decoded_value:
self._state[key].value = decoded_value
- log.info(f'Updated {key} from Redis: {decoded_value}')
+ log.info(f'Updated {key} from Redis: {_redact_config_value(key, decoded_value)}')
except json.JSONDecodeError:
log.error(f'Invalid JSON format in Redis for {key}: {redis_value}')
return self._state[key].value
+
+
+_SENSITIVE_KEY_SUBSTRINGS = ('SECRET', 'KEY', 'TOKEN', 'PASSWORD', 'CREDENTIAL')
+
+def _redact_config_value(key: str, value):
+ if any(s in key.upper() for s in _SENSITIVE_KEY_SUBSTRINGS):
+ return '***REDACTED***'
+ return value

View file

@ -0,0 +1,87 @@
"""
Unit test for open-webui-0001: CWE-312 Logged Secret in PersistentConfig Redis sync.
The defect: AppConfig.__getattr__ logs the decoded value of ANY PersistentConfig
key when it detects a change from Redis, including OPENAI_API_KEYS,
GOOGLE_CLIENT_SECRET, MICROSOFT_CLIENT_SECRET, RAG_OPENAI_API_KEY, etc.
log.info(f'Updated {key} from Redis: {decoded_value}')
The fix: redact values for keys whose names contain SECRET, KEY, TOKEN,
PASSWORD, or CREDENTIAL.
"""
import unittest
# --- Inline the fix so the test is self-contained ---
_SENSITIVE_KEY_SUBSTRINGS = ('SECRET', 'KEY', 'TOKEN', 'PASSWORD', 'CREDENTIAL')
def _redact_config_value(key: str, value):
if any(s in key.upper() for s in _SENSITIVE_KEY_SUBSTRINGS):
return '***REDACTED***'
return value
class TestRedactConfigValue(unittest.TestCase):
"""Verify that sensitive config keys are redacted before logging."""
def test_openai_api_keys_redacted(self):
result = _redact_config_value('OPENAI_API_KEYS', ['sk-abc123', 'sk-def456'])
self.assertEqual(result, '***REDACTED***')
def test_google_client_secret_redacted(self):
result = _redact_config_value('GOOGLE_CLIENT_SECRET', 'GOCspx-secret-value')
self.assertEqual(result, '***REDACTED***')
def test_microsoft_client_secret_redacted(self):
result = _redact_config_value('MICROSOFT_CLIENT_SECRET', 'ms-secret-value')
self.assertEqual(result, '***REDACTED***')
def test_rag_openai_api_key_redacted(self):
result = _redact_config_value('RAG_OPENAI_API_KEY', 'sk-rag-key')
self.assertEqual(result, '***REDACTED***')
def test_datalab_marker_api_key_redacted(self):
result = _redact_config_value('DATALAB_MARKER_API_KEY', 'marker-key-123')
self.assertEqual(result, '***REDACTED***')
def test_jwt_token_redacted(self):
result = _redact_config_value('JWT_TOKEN', 'eyJhbGciOi...')
self.assertEqual(result, '***REDACTED***')
def test_database_password_redacted(self):
result = _redact_config_value('DATABASE_PASSWORD', 'hunter2')
self.assertEqual(result, '***REDACTED***')
def test_credential_path_redacted(self):
result = _redact_config_value('OAUTH_CREDENTIAL', 'cred-value')
self.assertEqual(result, '***REDACTED***')
def test_non_sensitive_key_not_redacted(self):
result = _redact_config_value('ENABLE_OPENAI_API', True)
self.assertEqual(result, True)
def test_model_name_not_redacted(self):
result = _redact_config_value('DEFAULT_MODEL', 'gpt-4')
self.assertEqual(result, 'gpt-4')
def test_base_urls_not_redacted(self):
urls = ['https://api.openai.com/v1']
result = _redact_config_value('OPENAI_API_BASE_URLS', urls)
self.assertEqual(result, urls)
def test_cache_ttl_not_redacted(self):
result = _redact_config_value('MODELS_CACHE_TTL', 300)
self.assertEqual(result, 300)
def test_case_insensitive(self):
"""Keys with mixed case should still be redacted."""
result = _redact_config_value('My_Api_Key', 'value')
self.assertEqual(result, '***REDACTED***')
if __name__ == '__main__':
unittest.main()