diff --git a/UNDF-REGISTRY.json b/UNDF-REGISTRY.json index 27f77dc3e..2632ca7e2 100644 --- a/UNDF-REGISTRY.json +++ b/UNDF-REGISTRY.json @@ -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" } diff --git a/defects/open-webui-0001/patch/open-webui-0001.patch b/defects/open-webui-0001/patch/open-webui-0001.patch new file mode 100644 index 000000000..2c5c73063 --- /dev/null +++ b/defects/open-webui-0001/patch/open-webui-0001.patch @@ -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 diff --git a/defects/open-webui-0001/test/open-webui-0001-test.py b/defects/open-webui-0001/test/open-webui-0001-test.py new file mode 100644 index 000000000..79fa942ce --- /dev/null +++ b/defects/open-webui-0001/test/open-webui-0001-test.py @@ -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()