backend/app/services/plugin_service.py:
- Zip Slip defense: new _safe_extract_path() validates every archive
member against the destination root (rejects absolute paths, ../
segments, and entries that resolve outside backend/frontend dest).
All zip extract paths route through it.
- Plugin requirements.txt no longer auto-installs. Running pip on an
untrusted plugin gives setup.py hooks code-exec at the backend's
privilege level. Operators must opt in with SERVERKIT_ALLOW_PLUGIN_PIP=1;
otherwise the file is saved to the plugin dir for manual review.
- New _attach_status_guard() registers a per-blueprint before_request
that returns 503 if the plugin's DB status is not 'active'. Flask
can't unregister blueprints from a running app, so the in-DB status
is now authoritative at request time — disabling a plugin actually
stops serving its routes.
- New _update_plugin_metadata() syncs all mutable manifest fields
(display_name, description, author, homepage, repository, license,
category) on reinstall. Previously these went stale after the first
install.
backend/app/api/plugins.py:
- /install endpoints no longer echo raw exception strings to clients
(could leak paths, lib versions, SQL fragments). Log full traceback
server-side with a short correlation ref returned to the client.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>