Skip to content

Commit 56530fb

Browse files
Prevents information exposure through exceptions
1 parent 04ee627 commit 56530fb

2 files changed

Lines changed: 124 additions & 7 deletions

File tree

backend/services/middleware/tactic/src/off_key_tactic_middleware/api/v1/admin_models.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,28 @@ async def test_model_instantiation(
235235
"validated_parameters": validated_params,
236236
}
237237

238-
except ValueError as e:
239-
return {"success": False, "error": "validation_error", "message": str(e)}
240-
except ImportError as e:
238+
except ValueError:
239+
logger.warning(
240+
"Model validation failed for '%s'",
241+
model_type,
242+
exc_info=True,
243+
)
244+
return {
245+
"success": False,
246+
"error": "validation_error",
247+
"message": "Model validation failed. Check model type and parameters.",
248+
}
249+
except ImportError:
250+
logger.exception("Model dependency import failed for '%s'", model_type)
241251
return {
242252
"success": False,
243253
"error": "import_error",
244-
"message": f"Cannot import model dependencies: {e}",
254+
"message": "Model dependencies are not available.",
255+
}
256+
except Exception:
257+
logger.exception("Model test failed for '%s'", model_type)
258+
return {
259+
"success": False,
260+
"error": "instantiation_error",
261+
"message": "Model instantiation failed due to an internal error",
245262
}
246-
except Exception as e:
247-
logger.error(f"Model test failed for '{model_type}': {e}")
248-
return {"success": False, "error": "instantiation_error", "message": str(e)}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""Security regressions for admin model registry endpoints."""
2+
3+
from unittest.mock import MagicMock
4+
5+
import pytest
6+
7+
from off_key_tactic_middleware.api.v1.admin_models import (
8+
test_model_instantiation as call_test_model_instantiation,
9+
)
10+
11+
12+
def _assert_message_does_not_expose(response: dict, *sensitive_values: str) -> None:
13+
message = response["message"]
14+
for sensitive_value in sensitive_values:
15+
assert sensitive_value not in message
16+
17+
18+
@pytest.mark.asyncio
19+
async def test_model_instantiation_validation_error_hides_exception_detail():
20+
registry = MagicMock()
21+
registry.validate_model_params.side_effect = ValueError(
22+
"secret validation detail /tmp/internal"
23+
)
24+
25+
response = await call_test_model_instantiation(
26+
model_type="isolation_forest",
27+
test_parameters={"n_estimators": 100},
28+
model_registry=registry,
29+
)
30+
31+
assert response == {
32+
"success": False,
33+
"error": "validation_error",
34+
"message": "Model validation failed. Check model type and parameters.",
35+
}
36+
_assert_message_does_not_expose(
37+
response,
38+
"secret validation detail",
39+
"/tmp/internal",
40+
)
41+
registry.create_model_instance.assert_not_called()
42+
43+
44+
@pytest.mark.asyncio
45+
async def test_model_instantiation_import_error_hides_exception_detail():
46+
registry = MagicMock()
47+
registry.validate_model_params.return_value = {"n_estimators": 100}
48+
registry.create_model_instance.side_effect = ImportError("secret.module.path")
49+
50+
response = await call_test_model_instantiation(
51+
model_type="isolation_forest",
52+
test_parameters={"n_estimators": 100},
53+
model_registry=registry,
54+
)
55+
56+
assert response == {
57+
"success": False,
58+
"error": "import_error",
59+
"message": "Model dependencies are not available.",
60+
}
61+
_assert_message_does_not_expose(response, "secret.module.path")
62+
63+
64+
@pytest.mark.asyncio
65+
async def test_model_instantiation_internal_error_hides_exception_detail():
66+
registry = MagicMock()
67+
registry.validate_model_params.return_value = {"n_estimators": 100}
68+
registry.create_model_instance.side_effect = RuntimeError("stack trace secret")
69+
70+
response = await call_test_model_instantiation(
71+
model_type="isolation_forest",
72+
test_parameters={"n_estimators": 100},
73+
model_registry=registry,
74+
)
75+
76+
assert response == {
77+
"success": False,
78+
"error": "instantiation_error",
79+
"message": "Model instantiation failed due to an internal error",
80+
}
81+
_assert_message_does_not_expose(response, "stack trace secret")
82+
83+
84+
@pytest.mark.asyncio
85+
async def test_model_instantiation_success_response_is_unchanged():
86+
registry = MagicMock()
87+
registry.validate_model_params.return_value = {"n_estimators": 100}
88+
89+
response = await call_test_model_instantiation(
90+
model_type="isolation_forest",
91+
test_parameters={"n_estimators": 100},
92+
model_registry=registry,
93+
)
94+
95+
assert response == {
96+
"success": True,
97+
"message": "Model 'isolation_forest' instantiated successfully",
98+
"validated_parameters": {"n_estimators": 100},
99+
}
100+
registry.create_model_instance.assert_called_once_with(
101+
"isolation_forest",
102+
{"n_estimators": 100},
103+
)

0 commit comments

Comments
 (0)