Repository navigation
fix: match command group names as whole words - #10372
Conversation
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.
There was a problem hiding this comment.
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
kilisamemarisaaa
left a comment
There was a problem hiding this comment.
Independent, AI-assisted verification of head ccb8ab6753088964a7ab17c87eece63e7a354fd2 on Windows / Python 3.12.6:
- Rebuilt the two-file patch on its merge base
ac4df996f6ff217838293628272b573f3c45d4f6after Git transport failures. The resulting complete Git tree isb5b1731519fca1a18e664fadc6376e2099a56cd9, matching the head tree returned by GitHub's API. - The new regression module against unchanged upstream
42972e932produced 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 -qreturned 84 passed, 1 warning (existingaudioopdeprecation). - Ruff check of the changed production module, Ruff format check of both changed files, and
git diff --cached --checkpassed.
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.

Fixes #10371.
CommandGroupFilter.startswith()uses a plain prefix check, so a group namedmathalso matchesmathematicsormath123. 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/mathematicscommand from running when an admin-onlymathgroup is registered.Modifications / 改动点
Update
astrbot/core/star/filter/command_group.pyto use the same matching rule asCommandFilter: 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 inWakingCheckStage.This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
Verification steps:
天气andmath, plus a publicmathematicscommand.天气真好啊in a private chat and/mathematicsin a group./math add 1 2from 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.
天气真好啊/mathematicsmathematics: ok/math add 1 2No 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_messageand the LLM handling stage:Targeted tests were rechecked on Windows with Python 3.13.13:
The related command filtering, permission, plugin activation, and waking-stage tests also passed:
71 passed. Before theequals()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 themember_executionandadmin_removalcases oftests/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 / 检查清单
docs/zhordocs/enare needed.Summary by Sourcery
Match command group names as complete words so unrelated messages and commands are no longer intercepted by restricted groups.
Bug Fixes:
Tests: