Compare commits

...
Author SHA1 Message Date
Vito Sansevero bebf4c2986 fix(security): send a generic LoRA scan error to the browser
An unexpected exception's text could reach the page in the LoRA scan's
error frame, including a path or a model name. Those failures now send a
fixed message and keep the detail in the server log. Expected refusals keep
their wording, and an OSError still shows its reason and the file's basename.

Refs #552
2026-10-30 23:04:52 -07:00
2 changed files with 31 additions and 2 deletions
+14 -2
View File
@@ -44,6 +44,7 @@ _FILES_CHANGED = (
"The LoRA metadata files changed during the scan; the previous imports "
"were kept. Run the scan again."
)
_LORA_SCAN_FAILED = "The LoRA import failed. Check the server log for details."
_IMPORTS_CHANGED = (
"The imported LoRA prompts changed during the scan; the previous imports "
"were kept. Run the scan again."
@@ -407,10 +408,21 @@ class LoraIntegrationMixin:
return
if isinstance(error, PlanChanged):
message = _IMPORTS_CHANGED
elif isinstance(error, (LoraFilesChanged, LoraSettingsUnusable)):
elif isinstance(
error,
(
LoraFilesChanged,
LoraSettingsUnusable,
EmptyReplacement,
PairDependencyError,
),
):
message = str(error)
else:
elif isinstance(error, OSError):
# Reduced to its reason and the file's basename.
message = self._public_error(error)
else:
message = _LORA_SCAN_FAILED
if isinstance(
error,
(
+17
View File
@@ -840,6 +840,23 @@ class TestLoraScanRoute(LoraAPITestCase):
("error", error["message"], 0),
)
async def test_unexpected_scan_error_is_generic_in_the_stream(self):
self._enable()
secret = str(self.tmp_root / "x.db")
err = RuntimeError(f"could not open {secret}")
with patch.object(self.api.db, "replace_category_prompts", side_effect=err):
with self.assertLogs("prompt_manager.api", level="ERROR") as logs:
resp = await self.client.request("POST", "/prompt_manager/lora/scan")
body = await resp.text() # the stream ends inside the patch
self.assertNotIn("x.db", body)
error = self._sse_events(body)[-1]
self.assertEqual(
(error["type"], error["status"], error["progress"]),
("error", error["message"], 0),
)
self.assertIn("server log", error["message"])
self.assertIn(secret, "\n".join(logs.output))
@staticmethod
def _sse_events(text):
return [