mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 22:35: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:
@@ -95,6 +95,31 @@ class TestMissingBodyGivesTheDeclaredError:
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
class TestNonObjectBodyIsRefused:
|
||||
"""A JSON array parses and is truthy, so it got past "No data provided".
|
||||
|
||||
The raw editors then wrote it over config.json / config_secrets.json, and
|
||||
validate_request_json checked ``field in data`` against a list -- so
|
||||
``["plugin_id"]`` passed and the handler raised TypeError.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize("url", [
|
||||
"/api/v3/config/raw/main",
|
||||
"/api/v3/config/raw/secrets",
|
||||
])
|
||||
def test_raw_config_saves_refuse_an_array(self, api_v3_client, api_v3_module, url):
|
||||
response = api_v3_client.post(url, json=["display", "schedule"])
|
||||
|
||||
assert response.status_code == 400
|
||||
api_v3_module.api_v3.config_manager.save_raw_file_content.assert_not_called()
|
||||
|
||||
def test_validate_request_json_refuses_an_array(self, api_v3_client, api_v3_module):
|
||||
response = api_v3_client.post("/api/v3/plugins/uninstall", json=["plugin_id"])
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "object" in response.get_json()["message"]
|
||||
|
||||
|
||||
class TestNoBodyReadContradictsItsOwnGuard:
|
||||
PKG = Path(__file__).parent.parent / "web_interface/blueprints/api_v3"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user