diff --git a/packages/hint-x-content-type-options/README.md b/packages/hint-x-content-type-options/README.md
index 93e0b2e590c..a6dea64fe09 100644
--- a/packages/hint-x-content-type-options/README.md
+++ b/packages/hint-x-content-type-options/README.md
@@ -1,7 +1,7 @@
# Use `X-Content-Type-Options` header (`x-content-type-options`)
-`x-content-type-options` requires that all scripts and
-stylesheets are served with the `X-Content-Type-Options: nosniff`
+`x-content-type-options` requires that all resources are
+served with the `X-Content-Type-Options: nosniff`
HTTP response header.
## Why is this important?
@@ -29,19 +29,19 @@ header is sent for the script and the browser detects that it’s a script
and it wasn’t served with one of the [JavaScript media types][javascript
media types], the script will be blocked.
-Note: [Modern browsers only respect the header for scripts and
-stylesheets][fetch spec blocking] and sending the header for other
-resources (such as images) when they are served with the wrong media
-type may [create problems in older browsers][fetch spec issue].
+While [modern browsers respect the header mainly for scripts and
+stylesheets][fetch spec blocking], [Chromium uses this response header on
+other resources][chromium ssca] for
+[Cross-Origin Read Blocking][chromium corb].
## What does the hint check?
-The hint checks if all scripts and stylesheets are served with the
+The hint checks if all resources are served with the
`X-Content-Type-Options` HTTP headers with the value of `nosniff`.
### Examples that **trigger** the hint
-Resource that is not script or stylesheet is served with the
+Resource is not served with the
`X-Content-Type-Options` HTTP header.
```text
@@ -50,7 +50,6 @@ HTTP/... 200 OK
...
Content-Type: image/png
-X-Content-Type-Options: nosniff
```
Script is served with the `X-Content-Type-Options` HTTP header
@@ -77,6 +76,69 @@ Content-Type: text/javascript; charset=utf-8
X-Content-Type-Options: nosniff
```
+## How to configure the server to pass this hint
+
+How to configure Apache
+
+Apache can be configured to add headers using the [`Header`
+directive][header directive].
+
+```apache
+
+ Header always set X-Content-Type-Options nosniff
+
+```
+
+Note that:
+
+* The above snippet works with Apache `v2.2.0+`, but you need to have
+ [`mod_headers`][mod_headers] [enabled][how to enable apache modules]
+ for it to take effect.
+
+* If you have access to the [main Apache configuration file][main
+ apache conf file] (usually called `httpd.conf`), you should add
+ the logic in, for example, a [``][apache directory]
+ section in that file. This is usually the recommended way as
+ [using `.htaccess` files slows down][htaccess is slow] Apache!
+
+ If you don't have access to the main configuration file (quite
+ common with hosting services), add the snippets in a `.htaccess`
+ file in the root of the web site/app.
+
+For the complete set of configurations, not just for this rule, see
+the [Apache server configuration related documentation][apache config].
+
+
+
+
+
+How to configure IIS
+
+You can add this header unconditionally to all responses.
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+Note that:
+
+* The above snippet works with IIS 7+.
+* You should use the above snippet in the `web.config` of your
+ application.
+
+For the complete set of configurations, not just for this rule,
+see the [IIS server configuration related documentation][iis config].
+
+
+
## How to use this hint?
To use it you will have to install it via `npm`:
@@ -115,22 +177,24 @@ And then activate it via the [`.hintrc`][hintrc] configuration file:
+[chromium corb]: https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md
+[chromium ssca]: https://www.chromium.org/Home/chromium-security/ssca
[fetch spec blocking]: https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff%3F
[fetch spec issue]: https://github.com/whatwg/fetch/issues/395
+[hintrc]: https://webhint.io/docs/user-guide/configuring-webhint/summary/
[javascript media types]: https://html.spec.whatwg.org/multipage/scripting.html#javascript-mime-type
[mime sniffing spec]: https://mimesniff.spec.whatwg.org/
-[hintrc]: https://webhint.io/docs/user-guide/configuring-webhint/summary/
+[apache config]: https://webhint.io/docs/user-guide/server-configurations/apache/
[apache directory]: https://httpd.apache.org/docs/current/mod/core.html#directory
[header directive]: https://httpd.apache.org/docs/current/mod/mod_headers.html#header
[how to enable apache modules]: https://github.com/h5bp/server-configs-apache/tree/7eb30da6a06ec4fc24daf33c75b7bd86f9ad1f68#enable-apache-httpd-modules
[htaccess is slow]: https://httpd.apache.org/docs/current/howto/htaccess.html#when
[main apache conf file]: https://httpd.apache.org/docs/current/configuring.html#main
[mod_headers]: https://httpd.apache.org/docs/current/mod/mod_headers.html
-[mod_mime]: https://httpd.apache.org/docs/current/mod/mod_mime.html
-[url rewrite]: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-the-url-rewrite-module
+[iis config]: https://webhint.io/docs/user-guide/server-configurations/iis/
diff --git a/packages/hint-x-content-type-options/src/hint.ts b/packages/hint-x-content-type-options/src/hint.ts
index 7f95d9417d9..151647db0ed 100644
--- a/packages/hint-x-content-type-options/src/hint.ts
+++ b/packages/hint-x-content-type-options/src/hint.ts
@@ -10,7 +10,7 @@
*/
import { debug as d } from 'hint/dist/src/lib/utils/debug';
-import { IAsyncHTMLElement, FetchEnd, IHint } from 'hint/dist/src/lib/types';
+import { FetchEnd, IHint } from 'hint/dist/src/lib/types';
import normalizeString from 'hint/dist/src/lib/utils/misc/normalize-string';
import isDataURI from 'hint/dist/src/lib/utils/network/is-data-uri';
import { HintContext } from 'hint/dist/src/lib/hint-context';
@@ -28,36 +28,6 @@ export default class XContentTypeOptionsHint implements IHint {
public static readonly meta = meta;
public constructor(context: HintContext) {
-
- const isHeaderRequired = (element: IAsyncHTMLElement | null): boolean => {
- if (!element) {
- return false;
- }
-
- const nodeName = normalizeString(element.nodeName);
-
- /*
- * See:
- *
- * * https://github.com/whatwg/fetch/issues/395
- * * https://fetch.spec.whatwg.org/#x-content-type-options-header
- */
-
- if (nodeName === 'script') {
- return true;
-
- }
-
- if (nodeName === 'link') {
- // We check if element exists before and `normalizeString` will return `''` as default
- const relValues = (normalizeString(element.getAttribute('rel'), ''))!.split(' ');
-
- return relValues.includes('stylesheet');
- }
-
- return false;
- };
-
const validate = async ({ element, resource, response }: FetchEnd) => {
// This check does not make sense for data URI.
@@ -69,25 +39,19 @@ export default class XContentTypeOptionsHint implements IHint {
const headerValue: string | null = normalizeString(response.headers && response.headers['x-content-type-options']);
- if (isHeaderRequired(element)) {
- if (headerValue === null) {
- await context.report(resource, `Response should include 'x-content-type-options' header.`, { element });
+ if (headerValue === null) {
+ await context.report(resource, `Response should include 'x-content-type-options' header.`, { element });
- return;
- }
-
- if (headerValue !== 'nosniff') {
- await context.report(resource, `'x-content-type-options' header value should be 'nosniff', not '${headerValue}'.`, { element });
+ return;
+ }
- return;
- }
+ if (headerValue !== 'nosniff') {
+ await context.report(resource, `'x-content-type-options' header value should be 'nosniff', not '${headerValue}'.`, { element });
return;
}
- if (headerValue) {
- await context.report(resource, `Response should not include unneeded 'x-content-type-options' header.`, { element });
- }
+ return;
};
context.on('fetch::end::*', validate);
diff --git a/packages/hint-x-content-type-options/tests/tests.ts b/packages/hint-x-content-type-options/tests/tests.ts
index f391d3563fe..83bf0436385 100644
--- a/packages/hint-x-content-type-options/tests/tests.ts
+++ b/packages/hint-x-content-type-options/tests/tests.ts
@@ -3,19 +3,18 @@ import { getHintPath } from 'hint/dist/src/lib/utils/hint-helpers';
import { HintTest } from '@hint/utils-tests-helpers/dist/src/hint-test-type';
import * as hintRunner from '@hint/utils-tests-helpers/dist/src/hint-runner';
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
// Page data.
const pageWithAlternateStylesheet = generateHTMLPage('');
const pageWithScript = generateHTMLPage(undefined, '');
+const pageWithScriptAndStylesheet = generateHTMLPage('', '');
const pageWithStylesheet = generateHTMLPage('');
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
// Error messages.
const noHeaderErrorMessage = `Response should include 'x-content-type-options' header.`;
-const unneededHeaderErrorMessage = `Response should not include unneeded 'x-content-type-options' header.`;
-
const generateInvalidValueMessage = (value: string = '') => {
return `'x-content-type-options' header value should be 'nosniff', not '${value}'.`;
};
@@ -27,21 +26,26 @@ const generateInvalidValueMessage = (value: string = '') => {
const tests: HintTest[] = [
{
name: `HTML page is served without 'X-Content-Type-Options' header`,
- serverConfig: { '/': '' }
+ reports: [{ message: noHeaderErrorMessage }],
+ serverConfig: {
+ '/': { content: generateHTMLPage() },
+ '/favicon.ico': { headers: { 'X-Content-Type-Options': 'nosniff' } }
+ }
},
{
- name: `Script is served without 'X-Content-Type-Options' header`,
+ name: `Favicon is served without 'X-Content-Type-Options' header`,
reports: [{ message: noHeaderErrorMessage }],
serverConfig: {
- '/': pageWithScript,
- '/test.js': ''
+ '/': { content: generateHTMLPage(), headers: { 'Content-Type': 'text/html', 'X-Content-Type-Options': 'nosniff' } },
+ '/favicon.ico': ''
}
},
{
name: `Stylesheet is served without 'X-Content-Type-Options' header`,
reports: [{ message: noHeaderErrorMessage }],
serverConfig: {
- '/': pageWithStylesheet,
+ '/': { content: pageWithStylesheet, headers: { 'Content-Type': 'text/html', 'X-Content-Type-Options': 'nosniff' } },
+ '/favicon.ico': { headers: { 'X-Content-Type-Options': 'nosniff' } },
'/test.css': ''
}
},
@@ -49,26 +53,33 @@ const tests: HintTest[] = [
name: `Alternate stylesheet is served without 'X-Content-Type-Options' header`,
reports: [{ message: noHeaderErrorMessage }],
serverConfig: {
- '/': pageWithAlternateStylesheet,
+ '/': { content: pageWithAlternateStylesheet, headers: { 'Content-Type': 'text/html', 'X-Content-Type-Options': 'nosniff' } },
+ '/favicon.ico': { headers: { 'X-Content-Type-Options': 'nosniff' } },
'/test.css': ''
}
},
{
name: `Resource is specified as a data URI`,
- serverConfig: { '/': generateHTMLPage(undefined, '') }
- },
- {
- name: `HTML page is served with the 'X-Content-Type-Options' header`,
- reports: [{ message: unneededHeaderErrorMessage }],
- serverConfig: { '/': { headers: { 'X-Content-Type-Options': 'nosniff' } } }
+ serverConfig: {
+ '/': {
+ content: generateHTMLPage(undefined, ''),
+ headers: { 'X-Content-Type-Options': 'nosniff' }
+ },
+ '/favicon.ico': { headers: { 'X-Content-Type-Options': 'nosniff' } }
+ }
},
{
name: `Script is served with 'X-Content-Type-Options' header with invalid value`,
reports: [{ message: generateInvalidValueMessage('invalid') }],
serverConfig: {
- '/': pageWithScript,
+ '/': { content: pageWithScript, headers: { 'Content-Type': 'text/html', 'X-Content-Type-Options': 'nosniff' } },
+ '/favicon.ico': { headers: { 'X-Content-Type-Options': 'nosniff' } },
'/test.js': { headers: { 'X-Content-Type-Options': 'invalid' } }
}
+ },
+ {
+ name: `All resources are served with 'X-Content-Type-Options' header`,
+ serverConfig: { '*': { content: pageWithScriptAndStylesheet, headers: { 'Content-Type': 'text/html', 'X-Content-Type-Options': 'nosniff' } } }
}
];
diff --git a/packages/hint/docs/user-guide/server-configurations/apache.md b/packages/hint/docs/user-guide/server-configurations/apache.md
index 68fc955b230..464ef2fc265 100644
--- a/packages/hint/docs/user-guide/server-configurations/apache.md
+++ b/packages/hint/docs/user-guide/server-configurations/apache.md
@@ -529,6 +529,17 @@ AddDefaultCharset utf-8
# Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
#
+# ----------------------------------------------------------------------
+# | X-Content-Type-Options |
+# ----------------------------------------------------------------------
+
+# Serve resources with the x-content-type-options header set to `nosniff`.
+# https://webhint.io/docs/user-guide/hints/hint-x-content-type-options/
+
+#
+# Header always set X-Content-Type-Options nosniff
+#
+
# ######################################################################
# # Unnedded / Disallowed headers #
diff --git a/packages/hint/docs/user-guide/server-configurations/iis.md b/packages/hint/docs/user-guide/server-configurations/iis.md
index 46643221cf5..e1b3f3edc2d 100644
--- a/packages/hint/docs/user-guide/server-configurations/iis.md
+++ b/packages/hint/docs/user-guide/server-configurations/iis.md
@@ -47,6 +47,12 @@ related hint.
+
+
-
-
-
-
-
-
-
-