Skip to content

Commit 99b270d

Browse files
committed
Optimization: e.g. stop sending X-Frame-Options for CSS
1 parent d6b64f9 commit 99b270d

File tree

3 files changed

+71
-12
lines changed

3 files changed

+71
-12
lines changed

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ X-Content-Type-Options: nosniff <-----------
3838
## Key Features
3939

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

77-
Next are the common security headers being set. It's worth noting that special value of `omit` for directives below
78-
will disable sending a particular header by the module (useful if you want to let your backend app to send it).
78+
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:
79+
80+
> ... it is used to prevent infinite loops and unnecessary rewrites when PageSpeed
81+
> fetches resources from an origin that also uses PageSpeed
82+
83+
So it's best to specify `hide_server_tokens on;` in a front-facing NGINX insances, e.g.
84+
the one being accessed by actual browsers, and not the ones consumed by Varnish or other software.
85+
86+
In most cases you will be just fine with `security_headers on;` and `hide_server_tokens on;`, without any adjustments.
87+
88+
For fine-tuning, use the header-specific directives below.
89+
A special value `omit` disables sending a particular header by the module (useful if you want to let your backend app to send it).
7990

8091
### `security_headers_xss`
8192

@@ -141,6 +152,7 @@ To compile the module into NGINX, run:
141152
make
142153
make install
143154

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

146-
load_module modules/ngx_http_security_headers_module.so;
158+
load_module /path/to/ngx_http_security_headers_module.so;

src/ngx_http_security_headers_module.c

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ typedef struct {
3737
ngx_uint_t rp;
3838

3939
ngx_hash_t nosniff_types;
40-
ngx_array_t *types_keys;
40+
ngx_array_t *nosniff_types_keys;
41+
42+
ngx_hash_t text_types;
43+
ngx_array_t *text_types_keys;
4144

4245
} ngx_http_security_headers_loc_conf_t;
4346

@@ -99,6 +102,14 @@ ngx_str_t ngx_http_security_headers_default_nosniff_types[] = {
99102
ngx_null_string
100103
};
101104

105+
ngx_str_t ngx_http_security_headers_default_text_types[] = {
106+
ngx_string("text/html"),
107+
ngx_string("application/xhtml+xml"),
108+
ngx_string("text/xml"),
109+
ngx_string("text/plain"),
110+
ngx_null_string
111+
};
112+
102113
static ngx_command_t ngx_http_security_headers_commands[] = {
103114

104115
{ ngx_string( "security_headers" ),
@@ -119,7 +130,7 @@ static ngx_command_t ngx_http_security_headers_commands[] = {
119130
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
120131
ngx_http_types_slot,
121132
NGX_HTTP_LOC_CONF_OFFSET,
122-
offsetof(ngx_http_security_headers_loc_conf_t, types_keys),
133+
offsetof(ngx_http_security_headers_loc_conf_t, nosniff_types_keys),
123134
&ngx_http_security_headers_default_nosniff_types[0] },
124135

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

157+
{ ngx_string("security_headers_text_types"),
158+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
159+
ngx_http_types_slot,
160+
NGX_HTTP_LOC_CONF_OFFSET,
161+
offsetof(ngx_http_security_headers_loc_conf_t, text_types_keys),
162+
&ngx_http_security_headers_default_text_types[0] },
163+
146164
ngx_null_command
147165
};
148166

@@ -242,7 +260,8 @@ ngx_http_security_headers_filter(ngx_http_request_t *r)
242260

243261
/* Add X-XSS-Protection */
244262
if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED
245-
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->xss)
263+
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->xss
264+
&& ngx_http_test_content_type(r, &slcf->text_types) != NULL)
246265
{
247266
ngx_str_set(&key, "X-XSS-Protection");
248267
if (NGX_HTTP_XSS_HEADER_ON == slcf->xss) {
@@ -265,7 +284,8 @@ ngx_http_security_headers_filter(ngx_http_request_t *r)
265284

266285
/* Add X-Frame-Options */
267286
if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED
268-
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->fo)
287+
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->fo
288+
&& ngx_http_test_content_type(r, &slcf->text_types) != NULL)
269289
{
270290
ngx_str_set(&key, "X-Frame-Options");
271291
if (NGX_HTTP_FO_HEADER_SAME == slcf->fo) {
@@ -339,14 +359,22 @@ ngx_http_security_headers_merge_loc_conf(ngx_conf_t *cf, void *parent,
339359
ngx_conf_merge_value(conf->hide_server_tokens,
340360
prev->hide_server_tokens, 0 );
341361

342-
if (ngx_http_merge_types(cf, &conf->types_keys, &conf->nosniff_types,
343-
&prev->types_keys, &prev->nosniff_types,
362+
if (ngx_http_merge_types(cf, &conf->nosniff_types_keys, &conf->nosniff_types,
363+
&prev->nosniff_types_keys, &prev->nosniff_types,
344364
ngx_http_security_headers_default_nosniff_types)
345365
!= NGX_OK)
346366
{
347367
return NGX_CONF_ERROR;
348368
}
349369

370+
if (ngx_http_merge_types(cf, &conf->text_types_keys, &conf->text_types,
371+
&prev->text_types_keys, &prev->text_types,
372+
ngx_http_security_headers_default_text_types)
373+
!= NGX_OK)
374+
{
375+
return NGX_CONF_ERROR;
376+
}
377+
350378
ngx_conf_merge_uint_value(conf->xss, prev->xss,
351379
NGX_HTTP_XSS_HEADER_BLOCK);
352380
ngx_conf_merge_uint_value(conf->fo, prev->fo,

t/headers.t

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ hello world
2323
=== TEST 2: no nosniff for html
2424
--- config
2525
security_headers on;
26+
charset utf-8;
2627
location = /hello {
2728
return 200 "hello world\n";
2829
}
@@ -31,6 +32,7 @@ hello world
3132
--- response_body
3233
hello world
3334
--- response_headers
35+
content-type: text/plain; charset=utf-8
3436
!x-content-type-options
3537
x-frame-options: SAMEORIGIN
3638
x-xss-protection: 1; mode=block
@@ -140,4 +142,21 @@ hello world
140142
!x-content-type-options
141143
x-frame-options: SAMEORIGIN
142144
x-xss-protection: 1; mode=block
143-
referrer-policy: origin
145+
referrer-policy: origin
146+
147+
=== TEST 8: X-Frame-Options should not be sent for CSS (even when encoding specified)
148+
--- config
149+
security_headers on;
150+
charset utf-8;
151+
charset_types text/css;
152+
location = /hello.css {
153+
default_type "text/css";
154+
return 200 "hello world\n";
155+
}
156+
--- request
157+
GET /hello.css
158+
--- response_body
159+
hello world
160+
--- response_headers
161+
content-type: text/css; charset=utf-8
162+
!x-frame-options

0 commit comments

Comments
 (0)