Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion astrbot/dashboard/services/config_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,21 @@ async def save_plugin_configs(
if errors:
raise ValueError(f"格式校验未通过: {errors}")
metadata.config.save_config(post_configs)
await self.core_lifecycle.plugin_manager.reload(plugin_name)
try:
success, error_message = await self.core_lifecycle.plugin_manager.reload(
plugin_name
)
except Exception as exc:
logger.exception(
f"Failed to reload plugin {plugin_name} after saving its configuration."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Plugin errors expose secrets

When a caller with plugin-scoped API access submits configuration that causes an installed plugin to raise an error containing a secret or other sensitive internal detail, save_plugin_configs puts the reload result or exception text into a ValueError, and the API returns that message to the caller; secrets included in plugin-generated errors are exposed.

Return a generic reload-failure message to API callers and keep detailed exception information in protected server logs.

Also at astrbot/dashboard/services/config_service.py:1146.

Prompt for AI agents
In `astrbot/dashboard/services/config_service.py` at line 1144:

**Plugin errors expose secrets**

When a caller with plugin-scoped API access submits configuration that causes an installed plugin to raise an error containing a secret or other sensitive internal detail, `save_plugin_configs` puts the reload result or exception text into a `ValueError`, and the API returns that message to the caller; secrets included in plugin-generated errors are exposed.

Return a generic reload-failure message to API callers and keep detailed exception information in protected server logs.

Also at `astrbot/dashboard/services/config_service.py:1146`.

raise ValueError(
f"插件 {plugin_name} 配置已保存,但重载失败: {str(exc) or '未知错误,请查看日志'}"
) from exc
if not success:
raise ValueError(
f"插件 {plugin_name} 配置已保存,但重载失败: {error_message or '未知错误,请查看日志'}"
)

async def save_plugin_configs_from_dashboard_payload(
self,
Expand Down
64 changes: 64 additions & 0 deletions tests/test_fastapi_v1_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -3543,6 +3543,70 @@ async def form(self):
assert not files


@pytest.mark.asyncio
@pytest.mark.parametrize(
("reload_result", "expected_status", "expected_detail"),
[
((True, None), 200, None),
((False, "Invalid endpoint"), 400, "Invalid endpoint"),
((False, None), 400, "未知错误"),
(RuntimeError("Initialization failed"), 400, "Initialization failed"),
(TimeoutError(), 400, "未知错误,请查看日志"),
],
)
async def test_v1_plugin_config_save_reports_reload_failure(
asgi_client,
fake_core_lifecycle,
monkeypatch,
tmp_path,
reload_result,
expected_status,
expected_detail,
):
"""Report reload failures without claiming the saved configuration was reverted."""
config_path = tmp_path / "plugin.json"
plugin_config = config_service.AstrBotConfig(
str(config_path),
schema={"endpoint": {"type": "string", "default": "old"}},
)
monkeypatch.setattr(
config_service,
"star_registry",
[SimpleNamespace(name="demo", config=plugin_config)],
)

async def reload_plugin(plugin_name):
assert plugin_name == "demo"
assert json.loads(config_path.read_text(encoding="utf-8-sig")) == {
"endpoint": "new"
}
if isinstance(reload_result, Exception):
raise reload_result
return reload_result

monkeypatch.setattr(fake_core_lifecycle.plugin_manager, "reload", reload_plugin)
response = await asgi_client.put(
"/api/v1/plugins/demo/config",
json={"config": {"endpoint": "new"}},
headers=_jwt_headers(),
)

assert response.status_code == expected_status
payload = response.json()
assert json.loads(config_path.read_text(encoding="utf-8-sig")) == {
"endpoint": "new"
}
if expected_status == 200:
assert payload["status"] == "ok"
else:
assert payload["status"] == "error"
assert "demo" in payload["message"]
assert "配置已保存" in payload["message"]
assert "重载失败" in payload["message"]
if expected_detail:
assert expected_detail in payload["message"]


@pytest.mark.asyncio
async def test_v1_plugin_config_file_routes_reach_service_layer(
asgi_client: httpx.AsyncClient,
Expand Down
Loading