fix: match command group names as whole words by niansia · Pull Request #10372 · AstrBotDevs/AstrBot · GitHub
Skip to content

fix: match command group names as whole words - #10372

Merged
Soulter merged 2 commits into
AstrBotDevs:masterfrom
niansia:fix/10371
Oct 6, 2026
Merged

Soulter merged 2 commits into
AstrBotDevs:masterfrom
niansia:fix/10371

Conversation

@niansia

@niansia niansia commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10371.

CommandGroupFilter.startswith() uses a plain prefix check, so a group named math also matches mathematics or math123. When the group is restricted to admins, these unrelated messages receive the permission-denied reply and are stopped.

This blocks ordinary private messages such as 天气真好啊 when an admin-only group is named 天气, and prevents another plugin's public /mathematics command from running when an admin-only math group is registered.

Modifications / 改动点

  • Update astrbot/core/star/filter/command_group.py to use the same matching rule as CommandFilter: normalize whitespace, then require an exact group name or the name followed by a space. Real sub-commands continue to match, including messages with repeated spaces or newlines.

  • Apply the same whitespace normalization in equals() so nested group names with repeated spaces, tabs, or newlines still show the missing-subcommand help.

  • Add thirteen regression cases in tests/unit/test_command_group_filter.py, covering prefix-only messages, aliases, nested groups, whitespace, missing-subcommand help, and permission handling in WakingCheckStage.

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

Verification steps:

  1. Start AstrBot with an aiocqhttp (OneBot v11) platform and two reproduction plugins: admin-only groups 天气 and math, plus a public mathematics command.
  2. From a non-admin account, send 天气真好啊 in a private chat and /mathematics in a group.
  3. Send /math add 1 2 from the same account to verify that the real sub-command remains restricted.

I ran these steps against a running AstrBot instance using a small OneBot v11 client in place of NapCat. The reproduction plugins, client, minimal script, and before-fix logs are in A5_repro.zip, attached to #10371.

Message Before, master @ ac4df99 After, 8d50de2
Private: 天气真好啊 Permission denied; event stopped Reaches LLM handling
Group: /mathematics Permission denied; event stopped Replies mathematics: ok
Group: /math add 1 2 Permission denied Permission denied

No LLM provider was configured on the test instance, so the private message returned the missing-provider error after the fix. The log confirms that it reached on_message and the LLM handling stage:

[11:00:31.149] [Core] [DBUG] [method.star_request:46]: plugin -> astrbot - on_message
[11:00:31.150] [Core] [DBUG] [agent_sub_stages.internal:201]: ready to request llm provider
[11:00:35.154] [Core] [DBUG] [method.star_request:46]: plugin -> astrbot_plugin_mathematics_demo - mathematics
[11:00:39.153] [Core] [INFO] [waking_check.stage:223]: User ID 10001 lacks permission to trigger astrbot_plugin_admin_demo.

Targeted tests were rechecked on Windows with Python 3.13.13:

python -m pytest tests/unit/test_command_group_filter.py -q
13 passed

ruff format .
514 files left unchanged

ruff check .
All checks passed!

The related command filtering, permission, plugin activation, and waking-stage tests also passed: 71 passed. Before the equals() fix, the new repeated-space, tab, and newline cases failed because no missing-subcommand help was raised; all four new cases pass after the fix.

The earlier full-suite run on Python 3.13 reported 3602 passed, 82 skipped, 2 failed. Both failures are the member_execution and admin_removal cases of tests/test_fastapi_v1_dashboard.py::test_config_update_revokes_only_affected_shell_sessions. They were also reproduced on unchanged master @ ac4df99.

No screenshot is applicable to this backend matching fix.


Checklist / 检查清单

Summary by Sourcery

Match command group names as complete words so unrelated messages and commands are no longer intercepted by restricted groups.

Bug Fixes:

  • Prevent command groups from matching unrelated messages that merely share the group name as a prefix.
  • Preserve matching for actual subcommands, aliases, nested groups, and flexible whitespace while enforcing admin permissions correctly.

Tests:

  • Add regression coverage for whole-word matching, aliases, nested groups, whitespace normalization, and permission handling.

CommandGroupFilter matched messages with a plain str.startswith, so a
group named "math" also matched "mathematics". Once the group was
restricted to admins, non-admins got the permission-denied reply for any
message that merely started with the group name, such as a normal
private chat message or another plugin's command.

Match group names the same way CommandFilter does: collapse whitespace,
then require an exact match or the name followed by a space.

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/star/filter/command_group.py" line_range="122-123" />
<code_context>
-        return message_str.startswith(tuple(self.get_complete_command_names()))
+        # Match whole words only, like CommandFilter, so that "mathematics"
+        # does not match the "math" group.
+        message_str = re.sub(r"\s+", " ", message_str.strip())
+        return any(
+            message_str == name or message_str.startswith(f"{name} ")
+            for name in self.get_complete_command_names()
+        )

</code_context>
<issue_to_address>
**issue (bug_risk):** Whitespace-normalized input for an exact nested group such as `tool   config` passes `startswith`, but `filter()` checks the unnormalized string in `equals()` first, so it does not raise the group's missing-subcommand help response. The event is treated as a matched group without activating a handler, and normal downstream handling proceeds instead.

**Triggers:** When a user invokes a nested command group using repeated internal whitespace or newlines without supplying a subcommand.

**Suggested fix:** Normalize the message before the `equals()` check as well, or make `equals()` use the same whitespace normalization as `startswith()`.

```suggestion
    def equals(self, message_str: str) -> bool:
        message_str = re.sub(r"\s+", " ", message_str.strip())
        return message_str in self.get_complete_command_names()
```
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: astrbot/core/star/filter/command_group.py:123


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread astrbot/core/star/filter/command_group.py
@he-yufeng

Copy link
Copy Markdown
Contributor

@kilisamemarisaaa kilisamemarisaaa left a comment

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.

Independent, AI-assisted verification of head ccb8ab6753088964a7ab17c87eece63e7a354fd2 on Windows / Python 3.12.6:

  • Rebuilt the two-file patch on its merge base ac4df996f6ff217838293628272b573f3c45d4f6 after Git transport failures. The resulting complete Git tree is b5b1731519fca1a18e664fadc6376e2099a56cd9, matching the head tree returned by GitHub's API.
  • The new regression module against unchanged upstream 42972e932 produced 8 failed, 5 passed: prefix collisions, nested-group help with repeated whitespace, and the private-chat permission-denied path. With the patch applied to that upstream base, the related tests passed.
  • On the exact head tree, python -m pytest tests/unit/test_command_group_filter.py tests/test_command_filter.py tests/unit/test_command_permissions.py tests/unit/test_command_plugin_activation.py tests/unit/test_waking_check_api_key_admin.py tests/unit/test_waking_check_umo_alias.py -q returned 84 passed, 1 warning (existing audioop deprecation).
  • Ruff check of the changed production module, Ruff format check of both changed files, and git diff --cached --check passed.

The matching now follows the leaf CommandFilter boundary rule. Normalizing equals() as well as startswith() is important: an exact nested group with repeated spaces, a tab, or a newline must still produce missing-subcommand help. In WakingCheckStage, a failed group match skips the deferred permission-denied branch, while an actual group subcommand remains restricted. I found no blocker in this scoped change.

Limits: reused the existing development interpreter; no local full-suite run or live OneBot/LLM end-to-end run. The currently reported 23 upstream checks are successful, but those are separate CI evidence, not local test results.

@Soulter
Soulter merged commit 900a700 into AstrBotDevs:master Oct 6, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 管理员指令组误匹配同前缀消息,导致普通聊天和其他指令被拦截

4 participants