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
4 changes: 4 additions & 0 deletions astrbot/api/event/filter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
)
from astrbot.core.star.register import register_on_platform_loaded as on_platform_loaded
from astrbot.core.star.register import register_on_plugin_error as on_plugin_error
from astrbot.core.star.register import register_on_plugin_loaded as on_plugin_loaded
from astrbot.core.star.register import register_on_plugin_unloaded as on_plugin_unloaded
from astrbot.core.star.register import register_on_using_llm_tool as on_using_llm_tool
from astrbot.core.star.register import (
register_on_waiting_llm_request as on_waiting_llm_request,
Expand Down Expand Up @@ -54,6 +56,8 @@
"on_llm_request",
"on_llm_response",
"on_plugin_error",
"on_plugin_loaded",
"on_plugin_unloaded",
"on_platform_loaded",
"on_waiting_llm_request",
"permission_type",
Expand Down
4 changes: 4 additions & 0 deletions astrbot/core/star/register/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
register_on_llm_tool_respond,
register_on_platform_loaded,
register_on_plugin_error,
register_on_plugin_loaded,
register_on_plugin_unloaded,
register_on_using_llm_tool,
register_on_waiting_llm_request,
register_permission_type,
Expand All @@ -34,6 +36,8 @@
"register_on_llm_request",
"register_on_llm_response",
"register_on_plugin_error",
"register_on_plugin_loaded",
"register_on_plugin_unloaded",
"register_on_platform_loaded",
"register_on_waiting_llm_request",
"register_permission_type",
Expand Down
34 changes: 34 additions & 0 deletions astrbot/core/star/register/star_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,40 @@ def decorator(awaitable):
return decorator


def register_on_plugin_loaded(**kwargs):
"""当有插件加载完成时

Hook 参数:
metadata

说明:
当有插件加载完成时,触发该事件并获取到该插件的元数据
"""

def decorator(awaitable):
_ = get_handler_or_create(awaitable, EventType.OnPluginLoadedEvent, **kwargs)
return awaitable

return decorator


def register_on_plugin_unloaded(**kwargs):
"""当有插件卸载完成时

Hook 参数:
metadata

说明:
当有插件卸载完成时,触发该事件并获取到该插件的元数据
"""

def decorator(awaitable):
_ = get_handler_or_create(awaitable, EventType.OnPluginUnloadedEvent, **kwargs)
return awaitable

return decorator


def register_on_waiting_llm_request(**kwargs):
"""当等待调用 LLM 时的通知事件(在获取锁之前)

Expand Down
4 changes: 4 additions & 0 deletions astrbot/core/star/star_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ def get_handlers_by_event_type(
not in (
EventType.OnAstrBotLoadedEvent,
EventType.OnPlatformLoadedEvent,
EventType.OnPluginLoadedEvent,
EventType.OnPluginUnloadedEvent,
)
and not plugin.reserved
):
Expand Down Expand Up @@ -201,6 +203,8 @@ class EventType(enum.Enum):
OnLLMToolRespondEvent = enum.auto() # 调用函数工具后
OnAfterMessageSentEvent = enum.auto() # 发送消息后
OnPluginErrorEvent = enum.auto() # 插件处理消息异常时
OnPluginLoadedEvent = enum.auto() # 插件加载完成
OnPluginUnloadedEvent = enum.auto() # 插件卸载完成


H = TypeVar("H", bound=Callable[..., Any])
Expand Down
28 changes: 27 additions & 1 deletion astrbot/core/star/star_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .context import Context
from .filter.permission import PermissionType, PermissionTypeFilter
from .star import star_map, star_registry
from .star_handler import star_handlers_registry
from .star_handler import EventType, star_handlers_registry
from .updator import PluginUpdator

try:
Expand Down Expand Up @@ -772,6 +772,19 @@ async def load(
if hasattr(metadata.star_cls, "initialize") and metadata.star_cls:
await metadata.star_cls.initialize()

# 触发插件加载事件
handlers = star_handlers_registry.get_handlers_by_event_type(
EventType.OnPluginLoadedEvent,
)
for handler in handlers:
try:
logger.info(
f"hook(on_plugin_loaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
)
await handler.handler(metadata)
except Exception:
logger.error(traceback.format_exc())
Comment on lines +779 to +786
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The newly introduced plugin lifecycle hooks are executed sequentially and awaited while holding the global plugin management lock (_pm_lock). This creates a Denial of Service (DoS) vulnerability, as a malicious or poorly implemented plugin could indefinitely block the entire plugin system by providing a hook that never returns. Additionally, when triggering plugin load hooks, it's recommended to include the plugin's name (metadata.name) in the logs and explicitly state which plugin's hook failed during exception handling to improve observability and troubleshooting.

                        logger.info(
                            f"hook(on_plugin_loaded): plugin {metadata.name} loaded -> handled by {star_map[handler.handler_module_path].name}.{handler.handler_name}",
                        )
                        await handler.handler(metadata)
                    except Exception as e:
                        logger.error(f"Error in on_plugin_loaded hook of plugin {star_map[handler.handler_module_path].name}: {e}")
                        logger.error(traceback.format_exc())


except BaseException as e:
logger.error(f"----- 插件 {root_dir_name} 载入失败 -----")
errors = traceback.format_exc()
Expand Down Expand Up @@ -1159,6 +1172,19 @@ async def _terminate_plugin(star_metadata: StarMetadata) -> None:
elif "terminate" in star_metadata.star_cls_type.__dict__:
await star_metadata.star_cls.terminate()

# 触发插件卸载事件
handlers = star_handlers_registry.get_handlers_by_event_type(
EventType.OnPluginUnloadedEvent,
)
for handler in handlers:
try:
logger.info(
f"hook(on_plugin_unloaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
)
await handler.handler(star_metadata)
except Exception:
logger.error(traceback.format_exc())
Comment on lines +1179 to +1186
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

In the _terminate_plugin method, the hook execution is wrapped in a try...except Exception block, which does not catch BaseException types like SystemExit. A malicious plugin could register an on_plugin_unloaded hook that raises SystemExit, causing the entire bot process to terminate during a plugin reload or uninstallation. It is recommended to catch BaseException here or execute hooks in a way that they cannot crash the main process.


async def turn_on_plugin(self, plugin_name: str) -> None:
plugin = self.context.get_registered_star(plugin_name)
if plugin is None:
Expand Down