Page MenuHomePhabricator

CVE-2025-62666: CirrusSearch: DoS vector through the cirrusbuilddoc query API
Closed, ResolvedPublic2 Estimated Story PointsSecurity

Description

In reviewing the Trust and Safety Product Team logstash logs, I found that queries to the cirrusbuilddoc query API are consistently timing out on wikidata.org. I can't seem to find the exact reason because mw-debug servers are not producing a profile of the request for me due to the request timeouts.

For example, opening the URL https://www.wikidata.org/w/api.php?action=query&cbbuilders=content%7Clinks&format=json&format=json&formatversion=2&pageids=19705541&prop=cirrusbuilddoc causes the following in response:

{"error":{"code":"internal_api_error_Wikimedia\\RequestTimeout\\RequestTimeoutException","info":"[c10a585a-b480-999e-8c8e-b738c7f12e9d] Caught exception of type Wikimedia\\RequestTimeout\\RequestTimeoutException","errorclass":"Wikimedia\\RequestTimeout\\RequestTimeoutException"},"servedby":"mw-debug.eqiad.pinkunicorn-6bbbf5bd5b-hbw4h"}

The associated log in logstash has a different stack trace each time (so can't be sure what's causing the timeout). The ones I noticed included GlobalBlocking methods in the stack trace, but the one I triggered just had CirrusSearch and parsing in the stack trace:

from /srv/mediawiki/php-1.45.0-wmf.12/vendor/wikimedia/request-timeout/src/Detail/ExcimerTimerWrapper.php(130)
#0 /srv/mediawiki/php-1.45.0-wmf.12/vendor/wikimedia/request-timeout/src/Detail/ExcimerTimerWrapper.php(105): Wikimedia\RequestTimeout\Detail\ExcimerTimerWrapper->onTimeout(int)
#1 /srv/mediawiki/php-1.45.0-wmf.12/vendor/wikimedia/remex-html/src/Tokenizer/Tokenizer.php(563): Wikimedia\RequestTimeout\Detail\ExcimerTimerWrapper->Wikimedia\RequestTimeout\Detail\{closure}(int)
#2 /srv/mediawiki/php-1.45.0-wmf.12/vendor/wikimedia/remex-html/src/Tokenizer/Tokenizer.php(405): Wikimedia\RemexHtml\Tokenizer\Tokenizer->dataState(bool)
#3 /srv/mediawiki/php-1.45.0-wmf.12/vendor/wikimedia/remex-html/src/Tokenizer/Tokenizer.php(214): Wikimedia\RemexHtml\Tokenizer\Tokenizer->executeInternal(bool)
#4 /srv/mediawiki/php-1.45.0-wmf.12/includes/tidy/RemexDriver.php(80): Wikimedia\RemexHtml\Tokenizer\Tokenizer->execute(array)
#5 /srv/mediawiki/php-1.45.0-wmf.12/includes/parser/Parser.php(1759): MediaWiki\Tidy\RemexDriver->tidy(string, array)
#6 /srv/mediawiki/php-1.45.0-wmf.12/includes/parser/Parser.php(708): MediaWiki\Parser\Parser->internalParseHalfParsed(string, bool, bool)
#7 /srv/mediawiki/php-1.45.0-wmf.12/includes/content/WikitextContentHandler.php(382): MediaWiki\Parser\Parser->parse(string, MediaWiki\Title\Title, MediaWiki\Parser\ParserOptions, bool, bool, int)
#8 /srv/mediawiki/php-1.45.0-wmf.12/includes/content/ContentHandler.php(1695): MediaWiki\Content\WikitextContentHandler->fillParserOutput(MediaWiki\Content\WikitextContent, MediaWiki\Content\Renderer\ContentParseParams, MediaWiki\Parser\ParserOutput)
#9 /srv/mediawiki/php-1.45.0-wmf.12/includes/content/Renderer/ContentRenderer.php(75): MediaWiki\Content\ContentHandler->getParserOutput(MediaWiki\Content\WikitextContent, MediaWiki\Content\Renderer\ContentParseParams)
#10 /srv/mediawiki/php-1.45.0-wmf.12/includes/Revision/RenderedRevision.php(261): MediaWiki\Content\Renderer\ContentRenderer->getParserOutput(MediaWiki\Content\WikitextContent, MediaWiki\Page\PageIdentityValue, MediaWiki\Revision\RevisionStoreRecord, MediaWiki\Parser\ParserOptions, array)
#11 /srv/mediawiki/php-1.45.0-wmf.12/includes/Revision/RenderedRevision.php(233): MediaWiki\Revision\RenderedRevision->getSlotParserOutputUncached(MediaWiki\Content\WikitextContent, array)
#12 /srv/mediawiki/php-1.45.0-wmf.12/includes/Revision/RevisionRenderer.php(238): MediaWiki\Revision\RenderedRevision->getSlotParserOutput(string, array)
#13 /srv/mediawiki/php-1.45.0-wmf.12/includes/Revision/RevisionRenderer.php(171): MediaWiki\Revision\RevisionRenderer->combineSlotOutput(MediaWiki\Revision\RenderedRevision, MediaWiki\Parser\ParserOptions, array)
#14 /srv/mediawiki/php-1.45.0-wmf.12/includes/Revision/RenderedRevision.php(196): MediaWiki\Revision\RevisionRenderer->MediaWiki\Revision\{closure}(MediaWiki\Revision\RenderedRevision, array)
#15 /srv/mediawiki/php-1.45.0-wmf.12/includes/page/ParserOutputAccess.php(602): MediaWiki\Revision\RenderedRevision->getRevisionParserOutput()
#16 /srv/mediawiki/php-1.45.0-wmf.12/includes/page/ParserOutputAccess.php(516): MediaWiki\Page\ParserOutputAccess->renderRevision(MediaWiki\Page\WikiPage, MediaWiki\Parser\ParserOptions, MediaWiki\Revision\RevisionStoreRecord, array, null)
#17 /srv/mediawiki/php-1.45.0-wmf.12/includes/content/ContentHandler.php(1465): MediaWiki\Page\ParserOutputAccess->getParserOutput(MediaWiki\Page\WikiPage, MediaWiki\Parser\ParserOptions, MediaWiki\Revision\RevisionStoreRecord, array)
#18 /srv/mediawiki/php-1.45.0-wmf.12/extensions/CirrusSearch/includes/BuildDocument/ParserOutputPageProperties.php(99): MediaWiki\Content\ContentHandler->getParserOutputForIndexing(MediaWiki\Page\WikiPage, null, MediaWiki\Revision\RevisionStoreRecord)
#19 /srv/mediawiki/php-1.45.0-wmf.12/includes/libs/objectcache/WANObjectCache.php(1844): CirrusSearch\BuildDocument\ParserOutputPageProperties->CirrusSearch\BuildDocument\{closure}(bool, int, array, null, array)
#20 /srv/mediawiki/php-1.45.0-wmf.12/includes/libs/objectcache/WANObjectCache.php(1648): Wikimedia\ObjectCache\WANObjectCache->fetchOrRegenerate(string, int, Closure, array, array)
#21 /srv/mediawiki/php-1.45.0-wmf.12/extensions/CirrusSearch/includes/BuildDocument/ParserOutputPageProperties.php(114): Wikimedia\ObjectCache\WANObjectCache->getWithSetCallback(string, int, Closure)
#22 /srv/mediawiki/php-1.45.0-wmf.12/extensions/CirrusSearch/includes/BuildDocument/ParserOutputPageProperties.php(49): CirrusSearch\BuildDocument\ParserOutputPageProperties->finalizeReal(Elastica\Document, MediaWiki\Page\WikiPage, CirrusSearch\CirrusSearch, MediaWiki\Revision\RevisionStoreRecord)
#23 /srv/mediawiki/php-1.45.0-wmf.12/extensions/CirrusSearch/includes/BuildDocument/BuildDocument.php(215): CirrusSearch\BuildDocument\ParserOutputPageProperties->finalize(Elastica\Document, MediaWiki\Title\Title, MediaWiki\Revision\RevisionStoreRecord)
#24 /srv/mediawiki/php-1.45.0-wmf.12/extensions/CirrusSearch/includes/Api/QueryBuildDocument.php(128): CirrusSearch\BuildDocument\BuildDocument->finalize(Elastica\Document, bool, MediaWiki\Revision\RevisionStoreRecord)
#25 /srv/mediawiki/php-1.45.0-wmf.12/includes/api/ApiQuery.php(745): CirrusSearch\Api\QueryBuildDocument->execute()
#26 /srv/mediawiki/php-1.45.0-wmf.12/includes/api/ApiMain.php(2032): MediaWiki\Api\ApiQuery->execute()
#27 /srv/mediawiki/php-1.45.0-wmf.12/includes/api/ApiMain.php(954): MediaWiki\Api\ApiMain->executeAction()
#28 /srv/mediawiki/php-1.45.0-wmf.12/includes/api/ApiMain.php(925): MediaWiki\Api\ApiMain->executeActionWithErrorHandling()
#29 /srv/mediawiki/php-1.45.0-wmf.12/includes/api/ApiEntryPoint.php(152): MediaWiki\Api\ApiMain->execute()
#30 /srv/mediawiki/php-1.45.0-wmf.12/includes/MediaWikiEntryPoint.php(198): MediaWiki\Api\ApiEntryPoint->execute()
#31 /srv/mediawiki/php-1.45.0-wmf.12/api.php(44): MediaWiki\MediaWikiEntryPoint->run()
#32 /srv/mediawiki/w/api.php(3): require(string)
#33 {main}

Event Timeline

Dreamy_Jazz renamed this task from CirrusSearch: Potentially DoS vector through the cirrusbuilddoc query API to CirrusSearch: DoS vector through the cirrusbuilddoc query API.Aug 6 2025, 8:16 AM
pfischer set the point value for this task to 2.Aug 11 2025, 3:46 PM
sbassett added a project: SecTeam-Processed.
sbassett added subscribers: pfischer, TJones, sbassett.

Hey @TJones, @pfischer - let us know when there's a patch ready for this issue and, assuming it's for ext:CirrusSearch, we'll want to review it on this task and get it into Wikimedia production via the security deployment process. Thanks.

Unsubbing myself. I just moved the ticket on the workboard during triage.

dcausse subscribed.

I believe these are due to pages extremely hard to parse, you get similar stack with /w/api.php?action=parse&format=jsonfm&formatversion=2&pageid=19705541&wrappedhtml=1:

from /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/request-timeout/src/Detail/ExcimerTimerWrapper.php(130)
#0 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/request-timeout/src/Detail/ExcimerTimerWrapper.php(105): Wikimedia\RequestTimeout\Detail\ExcimerTimerWrapper->onTimeout(int)
#1 /srv/mediawiki/php-1.45.0-wmf.14/includes/parser/Sanitizer.php(838): Wikimedia\RequestTimeout\Detail\ExcimerTimerWrapper->Wikimedia\RequestTimeout\Detail\{closure}(int)
#2 /srv/mediawiki/php-1.45.0-wmf.14/includes/tidy/RemexCompatFormatter.php(58): MediaWiki\Parser\Sanitizer::armorFrenchSpaces(string)
#3 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/Serializer/Serializer.php(169): MediaWiki\Tidy\RemexCompatFormatter->characters(Wikimedia\RemexHtml\Serializer\SerializerNode, string, int, int)
#4 /srv/mediawiki/php-1.45.0-wmf.14/includes/tidy/RemexCompatMunger.php(212): Wikimedia\RemexHtml\Serializer\Serializer->characters(int, Wikimedia\RemexHtml\Serializer\SerializerNode, string, int, int, int, int)
#5 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/TreeBuilder/TreeBuilder.php(250): MediaWiki\Tidy\RemexCompatMunger->characters(int, Wikimedia\RemexHtml\TreeBuilder\Element, string, int, int, int, int)
#6 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/TreeBuilder/InBody.php(73): Wikimedia\RemexHtml\TreeBuilder\TreeBuilder->insertCharacters(string, int, int, int, int)
#7 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/TreeBuilder/InBody.php(78): Wikimedia\RemexHtml\TreeBuilder\InBody->Wikimedia\RemexHtml\TreeBuilder\{closure}(string, int, int, int, int)
#8 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/TreeBuilder/InCell.php(18): Wikimedia\RemexHtml\TreeBuilder\InBody->characters(string, int, int, int, int)
#9 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/TreeBuilder/Dispatcher.php(398): Wikimedia\RemexHtml\TreeBuilder\InCell->characters(string, int, int, int, int)
#10 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/Tokenizer/Tokenizer.php(1132): Wikimedia\RemexHtml\TreeBuilder\Dispatcher->characters(string, int, int, int, int)
#11 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/Tokenizer/Tokenizer.php(584): Wikimedia\RemexHtml\Tokenizer\Tokenizer->emitDataRange(int, int, bool, bool)
#12 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/Tokenizer/Tokenizer.php(405): Wikimedia\RemexHtml\Tokenizer\Tokenizer->dataState(bool)
#13 /srv/mediawiki/php-1.45.0-wmf.14/vendor/wikimedia/remex-html/src/Tokenizer/Tokenizer.php(214): Wikimedia\RemexHtml\Tokenizer\Tokenizer->executeInternal(bool)
#14 /srv/mediawiki/php-1.45.0-wmf.14/includes/tidy/RemexDriver.php(80): Wikimedia\RemexHtml\Tokenizer\Tokenizer->execute(array)
#15 /srv/mediawiki/php-1.45.0-wmf.14/includes/parser/Parser.php(1682): MediaWiki\Tidy\RemexDriver->tidy(string, array)
#16 /srv/mediawiki/php-1.45.0-wmf.14/includes/parser/Parser.php(631): MediaWiki\Parser\Parser->internalParseHalfParsed(string, bool, bool)
#17 /srv/mediawiki/php-1.45.0-wmf.14/includes/content/WikitextContentHandler.php(389): MediaWiki\Parser\Parser->parse(string, MediaWiki\Title\Title, MediaWiki\Parser\ParserOptions, bool, bool, int)
#18 /srv/mediawiki/php-1.45.0-wmf.14/includes/content/ContentHandler.php(1695): MediaWiki\Content\WikitextContentHandler->fillParserOutput(MediaWiki\Content\WikitextContent, MediaWiki\Content\Renderer\ContentParseParams, MediaWiki\Parser\ParserOutput)
#19 /srv/mediawiki/php-1.45.0-wmf.14/includes/content/Renderer/ContentRenderer.php(75): MediaWiki\Content\ContentHandler->getParserOutput(MediaWiki\Content\WikitextContent, MediaWiki\Content\Renderer\ContentParseParams)
#20 /srv/mediawiki/php-1.45.0-wmf.14/includes/Revision/RenderedRevision.php(261): MediaWiki\Content\Renderer\ContentRenderer->getParserOutput(MediaWiki\Content\WikitextContent, MediaWiki\Page\PageIdentityValue, MediaWiki\Revision\RevisionStoreCacheRecord, MediaWiki\Parser\ParserOptions, array)
#21 /srv/mediawiki/php-1.45.0-wmf.14/includes/Revision/RenderedRevision.php(233): MediaWiki\Revision\RenderedRevision->getSlotParserOutputUncached(MediaWiki\Content\WikitextContent, array)
#22 /srv/mediawiki/php-1.45.0-wmf.14/includes/Revision/RevisionRenderer.php(237): MediaWiki\Revision\RenderedRevision->getSlotParserOutput(string, array)
#23 /srv/mediawiki/php-1.45.0-wmf.14/includes/Revision/RevisionRenderer.php(170): MediaWiki\Revision\RevisionRenderer->combineSlotOutput(MediaWiki\Revision\RenderedRevision, MediaWiki\Parser\ParserOptions, array)
#24 /srv/mediawiki/php-1.45.0-wmf.14/includes/Revision/RenderedRevision.php(196): MediaWiki\Revision\RevisionRenderer->MediaWiki\Revision\{closure}(MediaWiki\Revision\RenderedRevision, array)
#25 /srv/mediawiki/php-1.45.0-wmf.14/includes/page/ParserOutputAccess.php(602): MediaWiki\Revision\RenderedRevision->getRevisionParserOutput()
#26 /srv/mediawiki/php-1.45.0-wmf.14/includes/page/ParserOutputAccess.php(516): MediaWiki\Page\ParserOutputAccess->renderRevision(MediaWiki\Page\WikiPage, MediaWiki\Parser\ParserOptions, MediaWiki\Revision\RevisionStoreCacheRecord, array, null)
#27 /srv/mediawiki/php-1.45.0-wmf.14/includes/page/WikiPage.php(1134): MediaWiki\Page\ParserOutputAccess->getParserOutput(MediaWiki\Page\WikiPage, MediaWiki\Parser\ParserOptions, MediaWiki\Revision\RevisionStoreCacheRecord, array)
#28 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiParse.php(190): MediaWiki\Page\WikiPage->getParserOutput(MediaWiki\Parser\ParserOptions, null, bool)
#29 /srv/mediawiki/php-1.45.0-wmf.14/includes/poolcounter/PoolCounterWorkViaCallback.php(82): MediaWiki\Api\ApiParse::MediaWiki\Api\{closure}()
#30 /srv/mediawiki/php-1.45.0-wmf.14/includes/poolcounter/PoolCounterWork.php(173): MediaWiki\PoolCounter\PoolCounterWorkViaCallback->doWork()
#31 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiParse.php(197): MediaWiki\PoolCounter\PoolCounterWork->execute()
#32 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiParse.php(900): MediaWiki\Api\ApiParse->getPageParserOutput(MediaWiki\Page\WikiPage, null, MediaWiki\Parser\ParserOptions, bool)
#33 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiParse.php(323): MediaWiki\Api\ApiParse->getParsedContent(MediaWiki\Page\WikiPage, MediaWiki\Parser\ParserOptions, bool, int, null, bool)
#34 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiMain.php(2033): MediaWiki\Api\ApiParse->execute()
#35 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiMain.php(955): MediaWiki\Api\ApiMain->executeAction()
#36 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiMain.php(926): MediaWiki\Api\ApiMain->executeActionWithErrorHandling()
#37 /srv/mediawiki/php-1.45.0-wmf.14/includes/api/ApiEntryPoint.php(152): MediaWiki\Api\ApiMain->execute()
#38 /srv/mediawiki/php-1.45.0-wmf.14/includes/MediaWikiEntryPoint.php(198): MediaWiki\Api\ApiEntryPoint->execute()
#39 /srv/mediawiki/php-1.45.0-wmf.14/api.php(44): MediaWiki\MediaWikiEntryPoint->run()
#40 /srv/mediawiki/w/api.php(3): require(string)
#41 {main}

Looking over time out errors from the cirrus updater perspective I captured 461 unique page ids.
I'm not entirely sure what to do, some of these pages eventually parse successfully but I suspect they are slow enough to be close the 60sec excimer timeout.
@Dreamy_Jazz regarding the dos vector, is there something different that action=parse is doing but not the cirrus API?

Looking over time out errors from the cirrus updater perspective I captured 461 unique page ids.
I'm not entirely sure what to do, some of these pages eventually parse successfully but I suspect they are slow enough to be close the 60sec excimer timeout.
@Dreamy_Jazz regarding the dos vector, is there something different that action=parse is doing but not the cirrus API?

Sorry just saw that ApiParse is protected via the PoolCounter, would you consider this a valid protection if we did the same in the cirrus API? (cc @sbassett)

Applying PoolCounter should address the immediate concern.

I filed it as a DoS concern because I didnt see what would prevent a user from very quickly loading the API multiple times.

I dont have enough context on this code to say for sure what the best solution is, so feel free to use your best judgement on what to do here.

+1 for PoolCounter as a means of addressing the potential Vuln-DoS here, especially if there isn't another obvious means of optimizing the cirrusbuilddoc query API's performance.

+1 for PoolCounter as a means of addressing the potential Vuln-DoS here, especially if there isn't another obvious means of optimizing the cirrusbuilddoc query API's performance.

Thanks!

I don't think we can do much about much about perf, I think this is a known problem that some pages are extremely costly to parse.

I'll get a patch ready but wanted to confirm something with you.
This API is marked internal and not meant to be used by external users. The bulk of it comes from the cirrus updater process that talks to the mw-api-int k8s cluster, this process connects to the cluster with a dedicated user sets in PrivateSettings.php, I'm tempted to add a new setting in CirrusSearch to let such user bypass the poolcounter protection by doing something like:

		if ( $this->getUser()->getName() === $engine->getConfig()->get( "CirrusSearchStreamingUpdaterUsername" ) ) {
			// Bypass poolcounter protection for the internal cirrus user
			$this->doExecute( $engine );
		} else {
			// Protect against too many concurrent requests
			// Use a global key, this API is internal and could only be useful for manual debugging purposes
			// so no real need to have it on a per user basis.
			$worker = new PoolCounterWorkViaCallback( 'CirrusSearch-QueryBuildDocument', 'QueryBuildDocument',
				[
					'doWork' => function () use ( $engine ) {
						return $this->doExecute( $engine);
					},
					'error' => function () {
						$this->dieWithError( 'apierror-concurrency-limit' );
					},
				]
			);
			$worker->execute();
		}

I'd set $wgCirrusSearchStreamingUpdaterUsername = 'CirrusSearch Streaming Updater' in PrivateSettings.php, not necessarily because it's secret (it's public: http://meta.wikimedia.org/wiki/User:CirrusSearch_Streaming_Updater) but rather for clarity so that it's defined closer to where the user secrets are defined.

Does this sound sane to you? (cc @EBernhardson )

Semi-related, eventhough the action=parse API is using the pool counter I don't seem to find where we actually configured this pool (not seeing this T243803#6043986 config bits in our codebase), so I wonder if we should do this as well.

I'm definitely not a subject matter expert on CirrusSearch, but this at least seems reasonable as config in PS.php. You might also want to ask @CDanis et al if they have any concerns about this.

Patch for CirrusSearch:

I will need a change to the mw-config after applying this patch to actually enable the protection:

Pinging @EBernhardson for review, thanks!

Patches look reasonable to me, this should address the issue.

Patches look reasonable to me, this should address the issue.

Thanks, both. Security-Team can security-deploy the ext:CirrusSearch patch tomorrow or by next Monday's security deployment window, at the latest. The config patch will need to go out publicly as a standard config-deploy after that. Once those are both out, it would be great if someone from the search team could confirm the patches are behaving as expected in Wikimedia production.

Patches look reasonable to me, this should address the issue.

Thanks, both. Security-Team can security-deploy the ext:CirrusSearch patch tomorrow or by next Monday's security deployment window, at the latest. The config patch will need to go out publicly as a standard config-deploy after that. Once those are both out, it would be great if someone from the search team could confirm the patches are behaving as expected in Wikimedia production.

Sure, please let us know when you plan to do the deploy and I'll try to be around, thanks.

Patch for CirrusSearch:

I will need a change to the mw-config after applying this patch to actually enable the protection:

Pinging @EBernhardson for review, thanks!

CirrusSearch patch deployed, mw-config patch to go out later this week

Thanks for the deploy!
Unfortunately the config deploy did no go as expected, I realized that my approach depends on the NetworkSession extension being enabled everywhere but unfortunately this was disabled on all public wikis in T373826.
I'm going to try re-enabling it everywhere and see.

If the above approach is a no-go another way could be to use the mw-config ClusterConfig class and add a new trait to detect if the server is running in the kube-mw-api-int cluster, patch:


@CDanis when you have a moment could you please take a look and let us know what you think.

If the above approach is a no-go another way could be to use the mw-config ClusterConfig class and add a new trait to detect if the server is running in the kube-mw-api-int cluster, patch:

I think Security-Team would probably prefer this approach since it doesn't require re-enabling a possibly still problematic extension.

Discussed with @Clement_Goubert about this and they pointed out that the rest-gateway might be hitting api-int from external requests but it does not hit the action API for now so this is safe. Clément suggested that it would not hurt to use both approaches and I agree, I adapted the patch with this idea:


In short if the NetworkSession extension is enabled the protection is bypassed thanks to the internal client authentication.
If for some reason the NetworkSession extension is disabled in the near future the protection is still bypassed thanks to the ClusterConfig.
In the long run when the NetworkSession extension is stable and no longer prone to being disabled we might want to remove the ClusterConfig approach to only rely on client auth.

Discussed with @Clement_Goubert about this and they pointed out that the rest-gateway might be hitting api-int from external requests but it does not hit the action API for now so this is safe. Clément suggested that it would not hurt to use both approaches and I agree, I adapted the patch with this idea:

Patch makes sense to me on its face, but I'm admittedly not a domain expert on this code. If its basically just enabling PoolCounter for the cirrusbuilddoc query based upon certain conditions, that sounds fine. Since this is part of wmf-config, I'd imagine we'd just config-deploy the patch via gerrit during a security deployment or backport window.

sbassett changed the task status from Open to In Progress.Aug 28 2025, 8:58 PM
sbassett triaged this task as Medium priority.

@sbassett I finally deployed the config patch, sorry for the delay. I could trigger the poolcounter protection using apache ab with a concurrency at 15.

@sbassett I finally deployed the config patch, sorry for the delay. I could trigger the poolcounter protection using apache ab with a concurrency at 15.

Excellent, thanks! I'd note that we are tracking this issue for the next supplemental security release (T397776), due out later this month. Some of the patches here seem fairly Wikimedia-specific though? I think we would defer to the Search Platform team to better understand what might make the most sense to release for ext:CirrusSearch, within the context of a supplemental security release targeted mainly towards external operators of MediaWiki.

I think it's indeed very WMF specific, unless we strongly suggest third parties to install the PoolCounter extension to protect all mediawiki endpoints allowing a parse I'm not totally sure that it deserves any special treatment because it's imo no more dangerous than a default MW install without any poolcounter set. Would it be OK to just ship the patch to master and ignore it from the security release?

Would it be OK to just ship the patch to master and ignore it from the security release?

I would probably check in with @Reedy to see what they believe would be appropriate in this situation. The ext:CirrusSearch patch is simple to release. It's not bundled and would be a part of the upcoming supplemental release. The rest of the code implicates core and the likely not-commonly-used NetworkSession extension, and is the more Wikimedia-specific config, it seems.

SecurityPatchBot changed the task status from In Progress to Open.Sep 15 2025, 11:54 PM
SecurityPatchBot raised the priority of this task from Medium to Unbreak Now!.
Patch is blocking upcoming release

Patch 01-T401220.patch is currently failing to apply for the most recent code in the mainline branch of extensions/CirrusSearch. This is blocking MediaWiki release 1.45.0-wmf.19(T396380)


If the patch needs to be rebased

A new version of the patch can be placed at the right location in the deployment server with the following Scap command:

REVISED_PATCH=<path_to_revised_patch>
scap update-patch --message-body 'Rebase to solve merge conflicts' /srv/patches/next/extensions/CirrusSearch/01-T401220.patch "$REVISED_PATCH"

If the patch has been made public

The patch can be dropped in the deployment server with the following Scap command:

scap remove-patch --message-body 'Dropping patch already made public' /srv/patches/next/extensions/CirrusSearch/01-T401220.patch
Patch is blocking this week's MediaWiki train!

Patch 01-T401220.patch is currently failing to apply for version 1.45.0-wmf.19 of extensions/CirrusSearch. MW train cannot move forward until the patch is fixed (T396380)
Please note you can disregard any existing previous messages in this task from SecurityPatchBot concerning version 1.45.0-wmf.19. To unblock the train, run one of the commands in this message


If the patch needs to be rebased

A new version of the patch can be placed at the right location in the deployment server with the following Scap command:

REVISED_PATCH=<path_to_revised_patch>
scap update-patch --message-body 'Rebase to solve merge conflicts' /srv/patches/1.45.0-wmf.19/extensions/CirrusSearch/01-T401220.patch "$REVISED_PATCH"

If the patch has been made public

The patch can be dropped in the deployment server with the following Scap command:

scap remove-patch --message-body 'Dropping patch already made public' /srv/patches/1.45.0-wmf.19/extensions/CirrusSearch/01-T401220.patch
dcausse lowered the priority of this task from Unbreak Now! to Medium.Sep 16 2025, 6:50 AM

Just updated the patch

Change #1189195 merged by jenkins-bot:

[mediawiki/extensions/CirrusSearch@master] SECURITY: protect cirrusbuilddoc via PoolCounter

https://gerrit.wikimedia.org/r/1189195

Change #1189223 had a related patch set uploaded (by Reedy; author: DCausse):

[mediawiki/extensions/CirrusSearch@REL1_44] SECURITY: protect cirrusbuilddoc via PoolCounter

https://gerrit.wikimedia.org/r/1189223

Change #1189225 had a related patch set uploaded (by Reedy; author: DCausse):

[mediawiki/extensions/CirrusSearch@REL1_43] SECURITY: protect cirrusbuilddoc via PoolCounter

https://gerrit.wikimedia.org/r/1189225

Change #1189223 merged by jenkins-bot:

[mediawiki/extensions/CirrusSearch@REL1_44] SECURITY: protect cirrusbuilddoc via PoolCounter

https://gerrit.wikimedia.org/r/1189223

Change #1189225 merged by jenkins-bot:

[mediawiki/extensions/CirrusSearch@REL1_43] SECURITY: protect cirrusbuilddoc via PoolCounter

https://gerrit.wikimedia.org/r/1189225

Mstyles renamed this task from CirrusSearch: DoS vector through the cirrusbuilddoc query API to CVE-2025-62666: CirrusSearch: DoS vector through the cirrusbuilddoc query API.Oct 18 2025, 5:08 AM
Mstyles changed the visibility from "Custom Policy" to "Public (No Login Required)".
Mstyles changed the edit policy from "Custom Policy" to "All Users".