Page MenuHomePhabricator

POST by mw.ForeignApi is CORS-blocked when a Promise-Non-Write-API-Action header is provided
Closed, ResolvedPublicBUG REPORT

Description

Steps to replicate the issue (include links if applicable):

mw.loader.using('mediawiki.ForeignApi').then(() => {
	const ajaxOptions = {
		ajax: {
			headers: {
				'Promise-Non-Write-API-Action': '1',
			},
		},
	};
	const api = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', ajaxOptions);

	api.post({
		action: 'query',
		formatversion: '2',
		titles: 'Main page',
	}).then(console.log, console.error);
});

What happens?:

The request fails due to a CORS preflight error:

Access to XMLHttpRequest at 'https://meta.wikimedia.org/w/api.php?action=query&format=json&origin=https%3A%2F%2Fja.wikipedia.org&meta=userinfo%7Ctokens' from origin 'https://ja.wikipedia.org' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
GET https://meta.wikimedia.org/w/api.php?action=query&format=json&origin=https%3A%2F%2Fja.wikipedia.org&meta=userinfo%7Ctokens net::ERR_FAILED

Access to XMLHttpRequest at 'https://meta.wikimedia.org/w/api.php?centralauthtoken=[REDACTED]&origin=https%3A%2F%2Fja.wikipedia.org&action=query' from origin 'https://ja.wikipedia.org' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
POST https://meta.wikimedia.org/w/api.php?centralauthtoken=[REDACTED]&origin=https%3A%2F%2Fja.wikipedia.org&action=query net::ERR_FAILED

{
    "xhr": {
        "readyState": 0,
        "status": 0,
        "statusText": "error"
    },
    "textStatus": "error",
    "exception": ""
}

What should have happened instead?:

The cross-origin POST request should succeed.

Software version (on Special:Version page; skip for WMF-hosted wikis like Wikipedia):

Other information (browser name/version, screenshots, etc.):

The issue occurs specifically when the Promise-Non-Write-API-Action header is present. The same request succeeds if the custom header is removed.

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript
matmarex subscribed.

The OPTIONS response includes this header explaining why: MediaWiki-CORS-Rejection: Unsupported header requested in preflight, generated here: https://gerrit.wikimedia.org/g/mediawiki/core/+/cfc4399d7155c88c33f00678a8dbc2971526d10b/includes/Api/ApiMain.php#1230

We should add Promise-Non-Write-API-Action to the default value of $wgAllowedCorsHeaders.

Change #1285880 had a related patch set uploaded (by Bartosz Dziewoński; author: Bartosz Dziewoński):

[mediawiki/core@master] Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders

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

A_smart_kitten subscribed.

2026-user-javascript-incident, Regression

To confirm, is this related to/caused by mitigations introduced for the 2026 user JS incident? (Only asking as I'm not immediately seeing the relation, but I could well be missing something :))

FWIW, I also just noticed that https://www.mediawiki.org/wiki/API:Etiquette#POST_requests currently says:

There is no need to set [the Promise-Non-Write-API-Action] header for GET requests, and neither should it be set when making cross-wiki requests within a wiki family using CentralAuth; see T91820.

(according to WikiBlame, this was added by this edit in May 2024)

I'm not completely sure whether the latter warning against setting the header when "making cross-wiki requests within a wiki family using CentralAuth" would apply to this task or not, but it seemed worth noting in case.

Removing the tags, as this doesn’t seem to be a CSP issue after all.

@BBlack Your input would be appreciated on:

FWIW, I also just noticed that https://www.mediawiki.org/wiki/API:Etiquette#POST_requests currently says:

There is no need to set [the Promise-Non-Write-API-Action] header for GET requests, and neither should it be set when making cross-wiki requests within a wiki family using CentralAuth; see T91820.

(according to WikiBlame, this was added by this edit in May 2024)

I'm not completely sure whether the latter warning against setting the header when "making cross-wiki requests within a wiki family using CentralAuth" would apply to this task or not, but it seemed worth noting in case.

I haven’t had a chance to check CentralAuth’s DB connection provider yet, but may I ask why the Promise-Non-Write-API-Action header shouldn’t be used for cross-wiki requests?

Chasing this further, this advice is originally from @tstarling in T91820#8015440, where it was qualified with "probably".

  • Cache/stash write optimisations => master DC
    • GET/POST api.php action=centralauthtoken or centralauthtoken=, or an Authorization header which starts with CentralAuthToken (T267270)
      • Reason: Foreign API tokens need to be set and then immediately consumed. Latency will probably be reduced by routing these requests at the CDN layer rather using mcrouter to do cross-DC memcached requests.

My understanding is that, when you make a request with centralauthtoken=..., CentralAuth code will need to connect to the Memcached servers in the primary datacenter to validate the token (as they are only stored there, in MicroStash). Therefore there is little benefit in trying to route your API request to the local datacenter (which is what "Promise-Non-Write-API-Action" asks for), and it may even be slower, since the API servers there will need to connect to the Memcached servers in the primary datacenter; it may be faster to route your API request to the primary datacenter in the first place, so that the queries to Memcached are faster.

That said, I had a look at the routing code (which appears to be here: https://gerrit.wikimedia.org/g/operations/puppet/+/d89096417f99b062dd39fade2bdaff278356e364/modules/profile/files/trafficserver/multi-dc.lua#94) and it looks like setting the "Promise-Non-Write-API-Action" header will have no effect in this case: it will skip the return false on line 129, but it'll hit the later one on line 167 anyway.

mw.ForeignApi will only use centralauthtoken=... when necessary (when the user is not logged in on the foreign wiki, or if browser's settings prevent it from sending cookies with these requests), so I think you should still set "Promise-Non-Write-API-Action" when using it, and we should allow that header for CORS requests in MediaWiki, for the benefit of the (hopefully more common) requests without centralauthtoken=..., which will be routed to the local datacenter.

I'd appreciate if someone could double-check my logic. If I'm not misunderstanding something, we should probably remove that piece of advice from https://www.mediawiki.org/wiki/API:Etiquette.

Change #1285880 merged by jenkins-bot:

[mediawiki/core@master] Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders

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

Change #1286891 had a related patch set uploaded (by Bartosz Dziewoński; author: Bartosz Dziewoński):

[mediawiki/core@wmf/1.47.0-wmf.1] Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders

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

Change #1286892 had a related patch set uploaded (by Bartosz Dziewoński; author: Bartosz Dziewoński):

[mediawiki/core@wmf/1.47.0-wmf.2] Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders

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

Change #1286891 merged by jenkins-bot:

[mediawiki/core@wmf/1.47.0-wmf.1] Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders

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

Change #1286892 merged by jenkins-bot:

[mediawiki/core@wmf/1.47.0-wmf.2] Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders

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

Mentioned in SAL (#wikimedia-operations) [2026-05-13T13:59:07Z] <lucaswerkmeister-wmde@deploy1003> Started scap sync-world: Backport for [[gerrit:1286890|ApiQueryGlobalUsers: Fix parsing logic for legacy log_params entries (T426033)]], [[gerrit:1286897|ApiQueryGlobalUsers: Fix parsing logic for legacy log_params entries (T426033)]], [[gerrit:1286891|Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders (T425972)]], [[gerrit:1286892|Add 'Promise-Non-Write-API-Action' to $wgAll

Mentioned in SAL (#wikimedia-operations) [2026-05-13T14:01:10Z] <lucaswerkmeister-wmde@deploy1003> dragoniez, matmarex, lucaswerkmeister-wmde: Backport for [[gerrit:1286890|ApiQueryGlobalUsers: Fix parsing logic for legacy log_params entries (T426033)]], [[gerrit:1286897|ApiQueryGlobalUsers: Fix parsing logic for legacy log_params entries (T426033)]], [[gerrit:1286891|Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders (T425972)]], [[gerrit:1286892|Add 'Promise-Non-Write-AP

Mentioned in SAL (#wikimedia-operations) [2026-05-13T14:07:52Z] <lucaswerkmeister-wmde@deploy1003> Finished scap sync-world: Backport for [[gerrit:1286890|ApiQueryGlobalUsers: Fix parsing logic for legacy log_params entries (T426033)]], [[gerrit:1286897|ApiQueryGlobalUsers: Fix parsing logic for legacy log_params entries (T426033)]], [[gerrit:1286891|Add 'Promise-Non-Write-API-Action' to $wgAllowedCorsHeaders (T425972)]], [[gerrit:1286892|Add 'Promise-Non-Write-API-Action' to $wgAl

@matmarex Thanks for the patch and the update to the help doc.

Closing this as resolved, as there doesn’t seem to be anything else to work on here.

That said, I agree with this↓, which makes sense to me:

mw.ForeignApi will only use centralauthtoken=... when necessary (when the user is not logged in on the foreign wiki, or if browser's settings prevent it from sending cookies with these requests), so I think you should still set "Promise-Non-Write-API-Action" when using it, and we should allow that header for CORS requests in MediaWiki, for the benefit of the (hopefully more common) requests without centralauthtoken=..., which will be routed to the local datacenter.