mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-06 07:15:09 +00:00
fix(web): keep exception messages out of API responses (py/stack-trace-exposure)
CodeQL had ~40 open py/stack-trace-exposure alerts on main. Almost all
flowed through describe_exception(), which returned "TypeName: message"
(redacted, capped); the rest through _run_systemctl_command's str(err),
WiFiManager's `return False, str(e)`, unit_refresh's f-strings and two
str(e)/f"{err}" messages in api_v3/__init__.py.
describe_exception() now returns a reason code -- the type, plus the
errno symbol for an OSError ("OSError:EIO", "PermissionError:EACCES") --
and logs the redacted message itself. That keeps what #538 wanted (a
failing disk still says EIO in the response) without quoting paths,
URLs or library internals, and fixes every call site at once; the
test_no_api_v3_handler_discards_its_exception policy still holds.
Service results: _get_display_service_status returns active/returncode
only, and the on-demand start/stop `service` result keeps
returncode/active/started/status but drops systemctl stdout/stderr
(logged on failure). Nothing in web_interface/static, the templates or
the MQTT bridge reads those fields. The Starlark SIGKILL-restart error
no longer returns systemctl stderr as `details`.
WiFi, unit-refresh, config-save and plugin-removal failures now say
what failed with the reason code and point at the log. display.py is
untouched (draft #773 edits it).
Tests: test_api_v3_no_exception_text.py drives one route per affected
file with a marker in the exception message and asserts it never
reaches the body; all 13 fail on origin/main, and targeted mutations
(drop the service filter, put stderr back, str(e) in WiFiManager,
{e} in unit_refresh, {install_err} in system.py, message back in
describe_exception) each fail at least one. Tests that asserted the old
message-in-details contract now assert the reason code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,7 +86,7 @@ def refresh_after_update(run=None, systemd_dir=None, helper_source=None, helper_
|
||||
except Exception as e: # a broken template must not fail the update itself
|
||||
if type(e).__name__ != 'UnitsUnreadable':
|
||||
logger.warning("Could not compare the installed systemd units with the new templates: %s", e)
|
||||
return _result(FAILED, f'The service settings could not be checked: {e}.')
|
||||
return _result(FAILED, 'The service settings could not be checked; see logs for details.')
|
||||
# Units installed mode 0600 (install_service.sh run on its own, before
|
||||
# it set 0644): only root can compare them, so let the helper decide.
|
||||
stale = []
|
||||
@@ -110,7 +110,7 @@ def refresh_after_update(run=None, systemd_dir=None, helper_source=None, helper_
|
||||
timeout=TIMEOUT_SECONDS)
|
||||
except (subprocess.SubprocessError, OSError) as e:
|
||||
logger.warning("Refreshing the systemd units failed: %s", e)
|
||||
return _result(FAILED, f'Updating the service settings ({names}) failed: {e}.', stale)
|
||||
return _result(FAILED, f'Updating the service settings ({names}) failed; see logs for details.', stale)
|
||||
if result.returncode == 0:
|
||||
# The helper says what it did: "units refreshed: a b" or "units: up to date".
|
||||
done = next((line.split(':', 1)[1].split() for line in (result.stdout or '').splitlines()
|
||||
|
||||
Reference in New Issue
Block a user