mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
fix(security): refuse unsafe plugin ids, keep secrets private, validate request bodies (#643)
* fix(security): refuse unsafe plugin ids, keep secrets private, validate bodies - install_from_url and the registry install's manifest rename refuse a plugin id that is not a single safe name (no ../ out of plugins_dir). - Uninstall and config reset refuse core config sections and ids with path parts; uninstall of a plugin whose directory is gone still works. - separate_secrets checks a field's own x-secret marker before recursing, so object/array secrets no longer land in config.json. - Backup restore creates missing secrets/wifi/ytm files with mode 640; export skips non-object manifests and no longer collides on same-second exports. - SYSTEM_FONTS includes every bundled font from BUNDLED_FONTS. - Raw config/secrets saves and validate_request_json require a JSON object. - A blank max_dynamic_duration_seconds keeps the stored value; other values are validated to 30-1800 instead of raising a 500. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(security): validate the id before install_plugin moves anything; claim backup names atomically - install_plugin set aside plugins_dir / plugin_id before any id check, so "../x" moved a directory outside the plugins dir (the rollback moved it back, but only if the install path got that far) - two exports finishing in the same second could both see a free name and the later os.replace destroyed the first archive; the name is now claimed with O_EXCL before the archive is swapped in Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -414,3 +414,28 @@ def test_pi5_form_warns_about_a_stored_unsupported_row_address_type(display_page
|
||||
board(PI5_MODEL)
|
||||
body = display_page(_config_with(hardware={'row_address_type': 5}))
|
||||
assert "Your saved row address type (5) can't be used on this Raspberry Pi 5" in body
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value,stored', [(180, 180), ('600', 600), (30, 30), ('1800', 1800)])
|
||||
def test_max_dynamic_duration_in_range_is_saved(api_v3_client, saved, value, stored):
|
||||
response = _post(api_v3_client, {'max_dynamic_duration_seconds': value})
|
||||
assert response.status_code == 200, response.get_data(as_text=True)[:200]
|
||||
assert saved['config']['display']['dynamic_duration']['max_duration_seconds'] == stored
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value', ['', ' ', None])
|
||||
def test_a_blank_max_dynamic_duration_keeps_the_stored_cap(api_v3_client, api_v3_module, saved, value):
|
||||
"""A cleared box posts "": int("") was a 500 that lost the whole Display save."""
|
||||
api_v3_module.api_v3.config_manager.load_config.return_value = {
|
||||
'display': {'dynamic_duration': {'max_duration_seconds': 240}}}
|
||||
response = _post(api_v3_client, {'max_dynamic_duration_seconds': value, 'brightness': 50})
|
||||
assert response.status_code == 200, response.get_data(as_text=True)[:200]
|
||||
assert saved['config']['display']['dynamic_duration']['max_duration_seconds'] == 240
|
||||
assert saved['config']['display']['hardware']['brightness'] == 50
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value', ['abc', 29, 1801, '12.5', True])
|
||||
def test_an_invalid_max_dynamic_duration_is_a_400(api_v3_client, saved, value):
|
||||
response = _post(api_v3_client, {'max_dynamic_duration_seconds': value})
|
||||
assert response.status_code == 400
|
||||
assert 'config' not in saved
|
||||
|
||||
Reference in New Issue
Block a user