mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
fix(api): echo only known control-socket reason codes as socket_error
The on-demand routes returned ControlError.reason verbatim. Every reason the client raises is a fixed code, but the display's error code arrives over the socket, so map the value onto the known set (transport reasons plus ErrorCode) and report anything else as "other". Resolves CodeQL py/stack-trace-exposure on the on-demand stop/start responses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,20 @@ class TestMailboxFallback:
|
||||
assert resp.get_json()["data"]["socket_error"] == "internal"
|
||||
assert len(_mailbox_writes(service["cache"])) == 1
|
||||
|
||||
def test_an_unknown_reason_is_reported_as_other(self, api_v3_client, service):
|
||||
# Only known codes are echoed back; anything else stays server-side.
|
||||
with patch(f"{CLIENT}.on_demand_start",
|
||||
side_effect=control_client.ControlError("/run/secret/path", "x")):
|
||||
data = api_v3_client.post(START_URL, json={"plugin_id": "weather"}).get_json()["data"]
|
||||
assert data["transport"] == "mailbox"
|
||||
assert data["socket_error"] == "other"
|
||||
assert len(_mailbox_writes(service["cache"])) == 1
|
||||
|
||||
def test_every_display_error_code_is_reportable(self):
|
||||
from web_interface.blueprints.api_v3 import display
|
||||
codes = {v for k, v in vars(c.ErrorCode).items() if not k.startswith("_")}
|
||||
assert codes <= set(display._REPORTABLE_SOCKET_REASONS)
|
||||
|
||||
def test_stop_falls_back(self, api_v3_client, service):
|
||||
with patch(f"{CLIENT}.on_demand_stop",
|
||||
side_effect=control_client.ControlError("timeout")):
|
||||
|
||||
@@ -34,6 +34,21 @@ def _cache_manager():
|
||||
#: older than the socket, Windows, or switched off. Not worth a log line.
|
||||
_QUIET_SOCKET_REASONS = frozenset({'no_socket', 'disabled', 'unsupported'})
|
||||
|
||||
#: Every reason code a response may echo as ``socket_error``: the client's
|
||||
#: transport reasons plus the display's ErrorCode values. Anything else is
|
||||
#: reported as ``other``, so no text taken from an exception reaches a reply.
|
||||
_REPORTABLE_SOCKET_REASONS = (
|
||||
'disabled', 'unsupported', 'no_socket', 'refused', 'timeout', 'closed',
|
||||
'bad_response', 'invalid_request',
|
||||
'bad_json', 'bad_request', 'message_too_large', 'unsupported_version',
|
||||
'unknown_command', 'invalid_args', 'busy', 'forbidden', 'internal',
|
||||
)
|
||||
|
||||
|
||||
def _socket_reason_code(reason):
|
||||
"""``reason`` as one of _REPORTABLE_SOCKET_REASONS, else ``'other'``."""
|
||||
return next((code for code in _REPORTABLE_SOCKET_REASONS if code == reason), 'other')
|
||||
|
||||
|
||||
def _deliver_on_demand(payload):
|
||||
"""Hand an on-demand request to the display: control socket, else mailbox.
|
||||
@@ -58,7 +73,7 @@ def _deliver_on_demand(payload):
|
||||
control_client.on_demand_stop(payload['request_id'])
|
||||
return 'socket', None
|
||||
except control_client.ControlError as e:
|
||||
reason = e.reason
|
||||
reason = _socket_reason_code(e.reason)
|
||||
if reason in _QUIET_SOCKET_REASONS:
|
||||
logger.debug("On-demand %s via the mailbox: %s", payload['action'], e)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user