W3C has this spec about headers allowed by setRequestHeader
Terminate these steps if header is a case-insensitive match for one of
the following headers:
- Accept-Charset
- Accept-Encoding
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Connection
- Content-Length
- Cookie
- Cookie2
- Content-Transfer-Encoding
- Date
- Expect
- Host
- Keep-Alive
- Origin
- Referer
- TE
- Trailer
- Transfer-Encoding
- Upgrade
- User-Agent
- Via
… or if the start of header is a case-insensitive match for Proxy- or
Sec- (including when header is just Proxy- or Sec-).
The above headers are controlled by the user agent to let it control
those aspects of transport. This guarantees data integrity to some
extent. Header names starting with Sec- are not allowed to be set to
allow new headers to be minted that are guaranteed not to come from
XMLHttpRequest.
Also you may consider:
If header is not in the author-request-headers list append header with its associated value to the list and terminate these steps.
About browsers implementation, I've found this nice test: https://dvcs.w3.org/hg/webapps/diff/5814514eeba4/tests/XMLHttpRequest/setrequestheader-header-forbidden.htm that you cant use to find current differences.
For example, IE has this definition of security on headers:
IE: Refer to RFC2616, Section 14: Header Field Definitions for a general list of standard headers. The server is ultimately responsible for honoring the headers of the request. By far the most common request header is Content-Type, which is required by some XML Web services.