Skip to content

filters.command(): command names interpolated into regex without escaping - false matches and re.error crashes #363

Description

@Meet6338-X

Summary

filters.command() interpolates the user-supplied command name directly into two regular expressions without re.escape(). Any command containing a regex metacharacter (. + ? ( [ etc.) either silently matches messages it must not match, or raises re.error during update dispatch.

The same applies to the follow-up re.sub() that strips the command from the text to build message.command, so arguments are also extracted incorrectly for such commands.

Note: this is a static analysis finding based on reading the dev branch; no runtime execution was performed.

Location

  • File: pyrogram/filters.py
  • Function: command() (inner func)
  • Relevant code path:
for cmd in flt.commands:
    if not re.match(rf"^(?:{cmd}(?:@?{username})?)(?:\s|$)", without_prefix,
                    flags=re.IGNORECASE if not flt.case_sensitive else 0):
        continue

    without_command = re.sub(rf"{cmd}(?:@?{username})?\s?", "", without_prefix, count=1, ...)

Problem

The documented parameter is a plain command name (commands (str | list): The command or list of commands as string), but the string is embedded in a pattern as raw regex source:

  1. Metacharacters alter matching semantics. For filters.command("buy.now"), the compiled intent is literal buy.now, but the executed pattern ^(?:buy.now(?:@?mybot)?)(?:\s|$) matches /buyXnow, /buy-now, /buynow2… because . matches any character.
  2. Quantifier-bearing commands produce broader false matches. For "c++", the effective pattern means "one-or-more of one-or-more c", so /cc and /cccc trigger the c++ handler.
  3. Some inputs make the pattern invalid. For "what?", compiling ^(?:what?(?:@?user)?)(?:\s|$) succeeds (t? quantifies the t) and then matches /wha, /what, /whatt...; for a command ending in an orphan quantifier such as "+" or "*" (e.g. command("+")), re.match raises re.error: nothing to repeat at position ... for every incoming message that starts with the configured prefix, turning the filter into a dispatch-time crash.

Because the second re.sub() reuses the same raw interpolation, even the false-positive cases strip the wrong span from without_prefix, producing wrong message.command argument lists.

Trigger / Reproduction

@app.on_message(filters.command("buy.now"))
async def handler(client, message):
    ...

Sending any of /buyXnow, /buy-now (with any configured prefix) invokes this handler although the user typed a different command.

@app.on_message(filters.command("+"))

Every incoming message beginning with / makes func raise re.error inside the dispatcher instead of returning a boolean.

Expected Behavior

Command names should be matched literally regardless of their characters, e.g. by escaping before interpolation:

escaped = re.escape(cmd)
re.match(rf"^(?:{escaped}(?:@?{re.escape(username)})?)(?:\s|$)", ...)

Actual Behavior

Command names act as regex patterns; metacharacter-containing commands misfire or raise re.error.

Impact

  • Silent routing bugs for legitimate command naming styles (start.v2, buy-now, localized prefixes like !?, technical commands like c++).
  • A single bad command can spam exceptions on every prefixed message, flooding logs and preventing the affected handlers from ever running deterministically.
  • Wrong message.command argument extraction follows from the same root cause.

Suggested Direction

Apply re.escape() to cmd (and defensively to username) at the point of interpolation into both the re.match and re.sub patterns. Optionally precompile per-command patterns once at filter creation instead of per-message.

Evidence

  • pyrogram/filters.py, command() inner function on the current dev branch: both f-string interpolations shown above contain no re.escape.
  • Docstring contract ("The command or list of commands as string") implies literal matching, not pattern semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions