Skip to content

Commit

Permalink
Optimization: e.g. stop sending X-Frame-Options for CSS
Browse files Browse the repository at this point in the history
  • Loading branch information
dvershinin committed Feb 29, 2020
1 parent d6b64f9 commit 99b270d
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 12 deletions.
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ X-Content-Type-Options: nosniff <-----------
## Key Features

* Plug-n-Play: the default set of security headers can be enabled with `security_headers on;` in your NGINX configuration
* Sends `X-Content-Type-Options` only for appropriate MIME types, preserving unnecessary bits from being transferred for non-JS and non-CSS resources
* Sends `X-Content-Type-Options` only for relevant MIME types (CSS/JS), preserving unnecessary headers from being sent for HTML documents
* Similiarly, sends HTML-only relevant headers for relevant types and skips sending for others e.g. `X-Frame-Options` is useless for CSS
* Plays well with conditional `GET` requests: the security headers are not included there unnecessarily
* Does not suffer the `add_header` directive's pitfalls
* Hides `X-Powered-By`, which often leaks PHP version information
Expand Down Expand Up @@ -74,8 +75,18 @@ Enables hiding headers which leak software information:
* `X-Page-Speed`
* `X-Varnish`

Next are the common security headers being set. It's worth noting that special value of `omit` for directives below
will disable sending a particular header by the module (useful if you want to let your backend app to send it).
It's worth noting that some of those headers bear functional use, e.g. [`X-Page-Speed` docs](https://www.modpagespeed.com/doc/configuration#XHeaderValue) mention:

> ... it is used to prevent infinite loops and unnecessary rewrites when PageSpeed
> fetches resources from an origin that also uses PageSpeed
So it's best to specify `hide_server_tokens on;` in a front-facing NGINX insances, e.g.
the one being accessed by actual browsers, and not the ones consumed by Varnish or other software.

In most cases you will be just fine with `security_headers on;` and `hide_server_tokens on;`, without any adjustments.

For fine-tuning, use the header-specific directives below.
A special value `omit` disables sending a particular header by the module (useful if you want to let your backend app to send it).

### `security_headers_xss`

Expand Down Expand Up @@ -141,6 +152,7 @@ To compile the module into NGINX, run:
make
make install

Or you can compile it as dynamic module. In that case, use `--add-dynamic-module` instead, and load the module after compilation via:
Or you can compile it as dynamic module. In that case, use `--add-dynamic-module` instead, and load the module after
compilation by adding to `nginx.conf`:

load_module modules/ngx_http_security_headers_module.so;
load_module /path/to/ngx_http_security_headers_module.so;
40 changes: 34 additions & 6 deletions src/ngx_http_security_headers_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ typedef struct {
ngx_uint_t rp;

ngx_hash_t nosniff_types;
ngx_array_t *types_keys;
ngx_array_t *nosniff_types_keys;

ngx_hash_t text_types;
ngx_array_t *text_types_keys;

} ngx_http_security_headers_loc_conf_t;

Expand Down Expand Up @@ -99,6 +102,14 @@ ngx_str_t ngx_http_security_headers_default_nosniff_types[] = {
ngx_null_string
};

ngx_str_t ngx_http_security_headers_default_text_types[] = {
ngx_string("text/html"),
ngx_string("application/xhtml+xml"),
ngx_string("text/xml"),
ngx_string("text/plain"),
ngx_null_string
};

static ngx_command_t ngx_http_security_headers_commands[] = {

{ ngx_string( "security_headers" ),
Expand All @@ -119,7 +130,7 @@ static ngx_command_t ngx_http_security_headers_commands[] = {
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
ngx_http_types_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_security_headers_loc_conf_t, types_keys),
offsetof(ngx_http_security_headers_loc_conf_t, nosniff_types_keys),
&ngx_http_security_headers_default_nosniff_types[0] },

{ ngx_string("security_headers_xss"),
Expand All @@ -143,6 +154,13 @@ static ngx_command_t ngx_http_security_headers_commands[] = {
offsetof(ngx_http_security_headers_loc_conf_t, rp),
ngx_http_referrer_policy },

{ ngx_string("security_headers_text_types"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
ngx_http_types_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_security_headers_loc_conf_t, text_types_keys),
&ngx_http_security_headers_default_text_types[0] },

ngx_null_command
};

Expand Down Expand Up @@ -242,7 +260,8 @@ ngx_http_security_headers_filter(ngx_http_request_t *r)

/* Add X-XSS-Protection */
if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->xss)
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->xss
&& ngx_http_test_content_type(r, &slcf->text_types) != NULL)
{
ngx_str_set(&key, "X-XSS-Protection");
if (NGX_HTTP_XSS_HEADER_ON == slcf->xss) {
Expand All @@ -265,7 +284,8 @@ ngx_http_security_headers_filter(ngx_http_request_t *r)

/* Add X-Frame-Options */
if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->fo)
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->fo
&& ngx_http_test_content_type(r, &slcf->text_types) != NULL)
{
ngx_str_set(&key, "X-Frame-Options");
if (NGX_HTTP_FO_HEADER_SAME == slcf->fo) {
Expand Down Expand Up @@ -339,14 +359,22 @@ ngx_http_security_headers_merge_loc_conf(ngx_conf_t *cf, void *parent,
ngx_conf_merge_value(conf->hide_server_tokens,
prev->hide_server_tokens, 0 );

if (ngx_http_merge_types(cf, &conf->types_keys, &conf->nosniff_types,
&prev->types_keys, &prev->nosniff_types,
if (ngx_http_merge_types(cf, &conf->nosniff_types_keys, &conf->nosniff_types,
&prev->nosniff_types_keys, &prev->nosniff_types,
ngx_http_security_headers_default_nosniff_types)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}

if (ngx_http_merge_types(cf, &conf->text_types_keys, &conf->text_types,
&prev->text_types_keys, &prev->text_types,
ngx_http_security_headers_default_text_types)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}

ngx_conf_merge_uint_value(conf->xss, prev->xss,
NGX_HTTP_XSS_HEADER_BLOCK);
ngx_conf_merge_uint_value(conf->fo, prev->fo,
Expand Down
21 changes: 20 additions & 1 deletion t/headers.t
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ hello world
=== TEST 2: no nosniff for html
--- config
security_headers on;
charset utf-8;
location = /hello {
return 200 "hello world\n";
}
Expand All @@ -31,6 +32,7 @@ hello world
--- response_body
hello world
--- response_headers
content-type: text/plain; charset=utf-8
!x-content-type-options
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
Expand Down Expand Up @@ -140,4 +142,21 @@ hello world
!x-content-type-options
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
referrer-policy: origin
referrer-policy: origin
=== TEST 8: X-Frame-Options should not be sent for CSS (even when encoding specified)
--- config
security_headers on;
charset utf-8;
charset_types text/css;
location = /hello.css {
default_type "text/css";
return 200 "hello world\n";
}
--- request
GET /hello.css
--- response_body
hello world
--- response_headers
content-type: text/css; charset=utf-8
!x-frame-options

0 comments on commit 99b270d

Please sign in to comment.