* 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>