So we received the broken browser's headers and responded with "406 You're broken". The CDN's server then cached this response, thus responding to every subsequent request (even non-broken ones) with the same error response.
This is just a theory, but I'd say it's correct with 99.9% probability, in particular since as soon as I purged the CDN cache (and with it the 406 response), all three people reporting this error said "yep, works again."
To confirm that this theory is correct, and also to give the data to our CDN provider (as this is obviously something that shouldn't happen again), please let me know what CDN data center you're hitting: http://debug-02.netdna-cdn.com/ will give you that information.
If my theory is correct, the three of you will probably be hitting the same data center (the probability that this issue would happen on two different servers simultaneously is pretty low).
Follow-up: It turns out that this was in fact our fault, not the CDN's. With every response to a static file request, we were returning a
Cache-Control: max-age=604800 header, in other words, we were saying that the response is cacheable for a week; we were doing this for every response, regardless of the status code. The applicable RFC is pretty clear about this situation (emphasis mine):
A response received with any [status code other than 200, 203, 206, 300, 301 or 410] MUST NOT be returned in a reply to a subsequent request unless there are cache-control directives or another header(s) that explicitly allow it. For example, these include the following: an Expires header (section 14.21); a "max-age", "s-maxage", "must- revalidate", "proxy-revalidate", "public" or "private" cache-control directive (section 14.9).
This means that the CDN was just following the standard by caching and reusing this response (there was no
Vary header on the response); any other caching proxy between us and the user could have done the same thing.
It looks like it's not possible to solve this problem in general with IIS. Thus we're probably going to solve this issue by having our load balancer (HAProxy) strip the
Cache-Control header from any response that does not have the status 200.