VYPR
Medium severity4.6NVD Advisory· Published Mar 10, 2026· Updated Apr 29, 2026

CVE-2026-30913

CVE-2026-30913

Description

Flarum is open-source forum software. When the flarum/nicknames extension is enabled, a registered user can set their nickname to a string that email clients interpret as a hyperlink. The nickname is inserted verbatim into plain-text notification emails, and recipients may be misled into visiting attacker-controlled domains.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
flarum/nicknamesPackagist
< 1.8.31.8.3

Affected products

1

Patches

1
4dde99729abd

Merge commit from fork

6 files changed · +168 2
  • js/src/admin/index.js+1 0 modified
    @@ -36,6 +36,7 @@ app.initializers.add('flarum/nicknames', () => {
           setting: 'flarum-nicknames.regex',
           type: 'text',
           label: app.translator.trans('flarum-nicknames.admin.settings.regex_label'),
    +      help: app.translator.trans('flarum-nicknames.admin.settings.regex_help'),
         })
         .registerSetting({
           setting: 'flarum-nicknames.min',
    
  • locale/en.yml+1 0 modified
    @@ -8,6 +8,7 @@ flarum-nicknames:
           random_username_label: Randomize Usernames
           random_username_help: This will hide the `username` input on registration, and use a random number instead. It will also make the `nickname` field mandatory. This will only take effect if "Allow setting nicknames on registration" is enabled.
           regex_label: Regular expression for validation
    +      regex_help: 'The following characters are always disallowed in nicknames: [ ] ( ) < >'
           set_on_registration_label: Allow setting nicknames on registration
           unique_label: Require unique nicknames
         wrong_driver: You must select "nickname" as the display name driver on the <a><strong>Basics Page</strong></a> for this extension to take effect.
    
  • src/AddNicknameValidation.php+7 0 modified
    @@ -37,6 +37,13 @@ public function __invoke($flarumValidator, Validator $validator)
             $rules = $validator->getRules();
     
             $rules['nickname'] = [
    +            function ($attribute, $value, $fail) {
    +                // Reject characters used in markdown link syntax that email clients
    +                // may render as hyperlinks in notification emails.
    +                if (preg_match('/[\[\]()<>]/', $value)) {
    +                    $fail($this->translator->trans('flarum-nicknames.api.invalid_nickname_message'));
    +                }
    +            },
                 function ($attribute, $value, $fail) {
                     $regex = $this->settings->get('flarum-nicknames.regex');
                     if ($regex && ! preg_match_all("/$regex/", $value)) {
    
  • src/NicknameDriver.php+11 1 modified
    @@ -16,6 +16,16 @@ class NicknameDriver implements DriverInterface
     {
         public function displayName(User $user): string
         {
    -        return $user->nickname ? $user->nickname : $user->username;
    +        $name = $user->nickname ? $user->nickname : $user->username;
    +
    +        // Strip characters used in markdown/HTML link syntax to prevent email
    +        // clients from rendering display names as hyperlinks. This also covers
    +        // nicknames stored before the save-time validation rule was introduced.
    +        $name = str_replace(['[', ']', '(', ')', '<', '>'], '', $name);
    +
    +        // Insert a zero-width space after every dot to prevent email clients
    +        // from auto-linking dotted strings as domain names (e.g. nasty.com).
    +        // The zero-width space is invisible in all rendering contexts.
    +        return str_replace('.', ".\u{200B}", $name);
         }
     }
    
  • tests/integration/api/EditUserTest.php+67 1 modified
    @@ -81,7 +81,73 @@ public function user_can_edit_own_nickname_if_allowed()
             );
     
             $this->assertEquals(200, $response->getStatusCode());
    -
             $this->assertEquals('new nickname', User::find(2)->nickname);
         }
    +
    +    /**
    +     * @test
    +     */
    +    public function nickname_with_dots_is_allowed()
    +    {
    +        $this->prepareDatabase([
    +            'group_permission' => [
    +                ['permission' => 'user.editOwnNickname', 'group_id' => 2],
    +            ]
    +        ]);
    +
    +        $response = $this->send(
    +            $this->request('PATCH', '/api/users/2', [
    +                'authenticatedAs' => 2,
    +                'json' => [
    +                    'data' => [
    +                        'attributes' => [
    +                            'nickname' => 'jane.smith',
    +                        ],
    +                    ],
    +                ],
    +            ])
    +        );
    +
    +        $this->assertEquals(200, $response->getStatusCode());
    +        $this->assertEquals('jane.smith', User::find(2)->nickname);
    +    }
    +
    +    /**
    +     * @test
    +     * @dataProvider markdownInjectionNicknames
    +     */
    +    public function nickname_with_markdown_injection_chars_is_rejected(string $nickname)
    +    {
    +        $this->prepareDatabase([
    +            'group_permission' => [
    +                ['permission' => 'user.editOwnNickname', 'group_id' => 2],
    +            ]
    +        ]);
    +
    +        $response = $this->send(
    +            $this->request('PATCH', '/api/users/2', [
    +                'authenticatedAs' => 2,
    +                'json' => [
    +                    'data' => [
    +                        'attributes' => [
    +                            'nickname' => $nickname,
    +                        ],
    +                    ],
    +                ],
    +            ])
    +        );
    +
    +        $this->assertEquals(422, $response->getStatusCode());
    +        $this->assertNull(User::find(2)->nickname);
    +    }
    +
    +    public function markdownInjectionNicknames(): array
    +    {
    +        return [
    +            'markdown link syntax' => ['[CLICK](https://evil.com)'],
    +            'square brackets only' => ['[username]'],
    +            'angle brackets'       => ['<evil.com>'],
    +            'parentheses'          => ['evil(com)'],
    +        ];
    +    }
     }
    
  • tests/unit/NicknameEmailSanitizerTest.php+81 0 added
    @@ -0,0 +1,81 @@
    +<?php
    +
    +/*
    + * This file is part of Flarum.
    + *
    + * For detailed copyright and license information, please view the
    + * LICENSE file that was distributed with this source code.
    + */
    +
    +namespace Flarum\Nicknames\Tests\unit;
    +
    +use Flarum\Nicknames\NicknameDriver;
    +use Flarum\Testing\unit\TestCase;
    +use Flarum\User\User;
    +
    +class NicknameEmailSanitizerTest extends TestCase
    +{
    +    private function displayName(string $nickname): string
    +    {
    +        $driver = new NicknameDriver();
    +
    +        $user = new User();
    +        $user->nickname = $nickname;
    +
    +        return $driver->displayName($user);
    +    }
    +
    +    /** @test */
    +    public function clean_nickname_is_unchanged()
    +    {
    +        $this->assertSame('Jane Smith', $this->displayName('Jane Smith'));
    +    }
    +
    +    /** @test */
    +    public function dot_gets_zero_width_space_inserted()
    +    {
    +        $this->assertSame("nasty.\u{200B}com", $this->displayName('nasty.com'));
    +    }
    +
    +    /** @test */
    +    public function multiple_dots_all_get_zero_width_space()
    +    {
    +        $this->assertSame("first.\u{200B}last.\u{200B}com", $this->displayName('first.last.com'));
    +    }
    +
    +    /** @test */
    +    public function square_brackets_are_stripped()
    +    {
    +        $this->assertSame('CLICK', $this->displayName('[CLICK]'));
    +    }
    +
    +    /** @test */
    +    public function parentheses_are_stripped()
    +    {
    +        $this->assertSame('evilcom', $this->displayName('evil(com)'));
    +    }
    +
    +    /** @test */
    +    public function angle_brackets_are_stripped()
    +    {
    +        $this->assertSame("evil.\u{200B}com", $this->displayName('<evil.com>'));
    +    }
    +
    +    /** @test */
    +    public function markdown_link_syntax_is_neutralised()
    +    {
    +        $this->assertSame("CLICKhttps://evil.\u{200B}com", $this->displayName('[CLICK](https://evil.com)'));
    +    }
    +
    +    /** @test */
    +    public function username_fallback_also_gets_sanitized()
    +    {
    +        $driver = new NicknameDriver();
    +
    +        $user = new User();
    +        $user->nickname = null;
    +        $user->username = 'nasty.com';
    +
    +        $this->assertSame("nasty.\u{200B}com", $driver->displayName($user));
    +    }
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

1