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.
| Package | Affected versions | Patched versions |
|---|---|---|
flarum/nicknamesPackagist | < 1.8.3 | 1.8.3 |
Affected products
1Patches
16 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- github.com/advisories/GHSA-3c4m-j3g4-hh25ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-30913ghsaADVISORY
- github.com/flarum/framework/security/advisories/GHSA-3c4m-j3g4-hh25nvdWEB
- github.com/flarum/nicknames/commit/4dde99729abdce8f6e2a7437c86e38735fdcca28nvdWEB
- github.com/flarum/nicknames/releases/tag/v1.8.nvdWEB
- github.com/flarum/nicknames/releases/tag/v1.8.3ghsaWEB
News mentions
1- Inside the REMUS Infostealer: Session Theft, MaaS, and Rapid EvolutionBleepingComputer · May 15, 2026