There's two issues with cross-domain requests: whether a request makes it to the server and whether a response is visible to the client script that issued the request. So-called "simple" requests (which use a "simple" method and include only "simple" headers) are guaranteed to make it to the server, but their visibility to the client script still depends on appropriate CORS headers. Non-simple requests are not immediately sent to the server; first, the browser sends a "preflight" request (using OPTIONS) and uses the headers in that preflight response to decide whether to send the actual request. This is because non-simple requests might alter server state, and thus could cause damage even if the client was forbidden from seeing the response.
Let's be clear that the result of a preflight OPTIONS request is never made available to the client script. It is only used privately by the browser to decide whether to perform the actual request initiated by the script:
If you wrote a script that tried to make an actual (i.e., client-visible) OPTIONS request, it would be subject to normal CORS rules:
var xhr = new XMLHttpRequest();
xhr.open("OPTIONS", "http://cors-enabled-server.example.com/");
xhr.onload = function(e) { console.log(e); }
xhr.send();
This request will generate a preflight (because OPTIONS is a non-simple method) and the request will fail, unless the server sends a Access-Control-Allow-Methods: OPTIONS
response. Note that this generates a preflight OPTIONS request, but that preflight OPTIONS request is never made visible to the script. The script can only see the "actual" OPTIONS request/response, which comes after a successful preflight.
Second, you ask:
But a HEAD request won't receive anything other than headers and a status... yet it would effectively be blocked.
This is not true: the visibility of the result might be blocked, but the request is not. This is because the HEAD method is a simple method according to CORS and thus does not necessitate a preflight, just like GET and POST:
A method is said to be a simple method if it is a case-sensitive match
for one of the following:
A simple cross-domain HEAD request still requires an acceptable Access-Control-Allow-Origin
response, but it does not require a preflight. Therefore, a simple HEAD request (i.e., one that has no special headers necessitating a preflight) will always be sent directly to the server.
OPTIONS, on the other hand, is a non-simple method and must be approved by Access-Control-Allow-Methods
in a preflight response. Do not confuse a preflight OPTIONS request (which is never subject to CORS, but is visible only to the browser, not the script) with a JavaScript OPTIONS request (which is visible to the client, but only if it passes CORS requirements).
To answer the title question in your title, "How could a JavaScript making a cross-domain HEAD request be a threat?": it's a threat roughly on the order of allowing a cross-domain GET request, since HEAD should be --according to the HTTP spec -- the same as making a GET request, but without the response body. There's still the potential for lots of sensitive data to leak through headers and status codes.