Skip to content

fix(filters): reject invalid base64 deep-link payloads in CommandStart - #1963

Merged
JrooTJunior merged 2 commits into
aiogram:dev-3.xfrom
Enesuygurs:fix/command-start-invalid-base64-deep-link
Oct 30, 2026
Merged

JrooTJunior merged 2 commits into
aiogram:dev-3.xfrom
Enesuygurs:fix/command-start-invalid-base64-deep-link

Conversation

@Enesuygurs

Copy link
Copy Markdown
Contributor

Description

CommandStart(deep_link=True, deep_link_encoded=True) only catches UnicodeDecodeError when it decodes the payload. If the payload is not valid base64url, urlsafe_b64decode raises binascii.Error instead, and that error escapes the filter. This happens, for example, when the payload length is 4k+1: /start a, /start abcde, or a referral link that was cut short when copied.

Because the filter raised, the observer stops at that handler and the update never reaches the handlers registered after it, such as a plain CommandStart() fallback. The user gets no reply and the dispatcher logs "Cause exception while process update". Anyone can trigger this by typing /start a.

@router.message(CommandStart(deep_link=True, deep_link_encoded=True))
async def referral(message: Message): ...

@router.message(CommandStart())
async def plain_start(message: Message): ...
  • /start dGVzdA -> referral (as expected)
  • /start a -> binascii.Error: Invalid base64-encoded string: number of data characters (1) cannot be 1 more than a multiple of 4, and plain_start is never called

The fix also catches binascii.Error and turns it into CommandException, the same way an undecodable UTF-8 payload is already handled. The filter now returns False and propagation continues.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Added /start a and /start abcde cases to TestCommandFilter.test_parse_command. Both fail with binascii.Error without the fix and pass with it.
  • pytest tests: 2327 passed, 56 skipped (redis/mongo not configured)
  • ruff format --check and ruff check --preview on the changed files, and mypy aiogram

Test Configuration:

  • Operating System: Windows 10
  • Python version: 3.11.9

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@github-actions github-actions Bot added the 3.x Issue or PR for stable 3.x version label Oct 23, 2026
@JrooTJunior

Copy link
Copy Markdown
Member

@claude, review it

@claude

claude Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Claude finished @JrooTJunior's task in 15s —— View job


Review of #1963

Verdict: looks good to merge. I read the diff and the surrounding code. I did not run the test suite or linters in this job.

  • Fix (aiogram/filters/command.py:300): decode_payload calls _decode_b64, which uses urlsafe_b64decode. That function raises binascii.Error for malformed input, such as a payload whose length is 4k+1. The old code only caught UnicodeDecodeError, so the error escaped the filter. The new except (binascii.Error, UnicodeDecodeError) turns it into CommandException. Command.__call__ already catches that exception (line 120) and returns False, so propagation continues to later handlers. The change is minimal and matches the existing handling.
  • Exception coverage: binascii.Error subclasses ValueError, and so does UnicodeDecodeError. Catching the two explicitly is clearer than catching ValueError, so I'd keep it as is.
  • Tests: /start a and /start abcde both have length 4k+1, which is the failing case. They sit in the existing parametrized test_parse_command, which fits the suite's style. If you want a bit more coverage, a payload with characters outside the base64url alphabet could be added. I'm not sure whether _decode_b64 rejects those, so check before adding a case. This is optional.
  • Changelog: CHANGES/1963.bugfix.rst has the right category and the PR number. It describes user-visible behavior.
  • Scope: the diff is focused, with no unrelated changes.

Nits: none blocking.

@github-actions

Copy link
Copy Markdown

✔️ Changelog found.

Thank you for adding a description of the changes

@codecov

codecov Bot commented Oct 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (c5ead5b) to head (1fb28f2).
⚠️ Report is 4 commits behind head on dev-3.x.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           dev-3.x     #1963   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          751       751           
  Lines        16802     16803    +1     
=========================================
+ Hits         16802     16803    +1     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
aiogram/filters/command.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@JrooTJunior
JrooTJunior merged commit 1fc1c97 into aiogram:dev-3.x Oct 30, 2026
24 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.x Issue or PR for stable 3.x version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants