jdaugherty commented on code in PR #15967:
URL: https://github.com/apache/grails-core/pull/15967#discussion_r4088375617
##########
grails-doc/src/en/guide/security.adoc:
##########
@@ -31,3 +31,65 @@ Grails has a few built in safety mechanisms by default.
* The default link:scaffolding.html[scaffolding] templates HTML escape all
data fields when displayed
* Grails link creating tags (link:{gspTagsRef}link.html[link],
link:{gspTagsRef}form.html[form], link:{gspTagsRef}createLink.html[createLink],
link:{gspTagsRef}createLinkTo.html[createLinkTo] and others) all use
appropriate escaping mechanisms to prevent code injection
* Grails provides <<codecs,codecs>> to let you trivially escape data when
rendered as HTML, JavaScript and URLs to prevent injection attacks here.
+
+==== HTTP Security Headers
+
+Grails servlet web applications send a small set of browser hardening headers
by default:
+
+[cols="1,1", options="header"]
+|===
+| Header
+| Default value
+
+| `X-Content-Type-Options`
+| `nosniff`
+
+| `X-Frame-Options`
+| `SAMEORIGIN`
+
+| `Referrer-Policy`
+| `strict-origin-when-cross-origin`
+
+| `X-XSS-Protection`
+| `0`
+|===
+
+HSTS and Content Security Policy are not enabled by default because their
correct values depend on deployment topology and application assets.
+When HSTS is enabled, Grails sends `Strict-Transport-Security` only for secure
requests (`request.isSecure()`).
+
+If your application is deployed behind a TLS-terminating reverse proxy (the
typical nginx/haproxy topology), the connection between the proxy and the
application is plain HTTP, so `request.isSecure()` is `false` unless the
container is told to trust the proxy's forwarded headers.
+Configure the Spring Boot `server.forward-headers-strategy: framework` (or
`native`) property so `X-Forwarded-Proto` is honored, or set HSTS at the proxy
instead, where TLS actually terminates.
+Without one of these, enabling HSTS here is a silent no-op behind most reverse
proxies.
+
+If Spring Security's own header-writing filter (`HeaderWriterFilter`) is on
the classpath, this filter does not apply at all - Spring Security already
provides its own configurable header defaults, and layering Grails' eager
defaults underneath would let them win over an application's explicit Spring
Security header configuration.
+
+You can disable the whole filter, disable individual headers, or override
values from `application.yml`:
+
+[source,yaml]
+.grails-app/conf/application.yml
+----
+grails:
+ security:
+ headers:
+ enabled: true
+ frame-options:
+ value: DENY
+ referrer-policy:
+ value: no-referrer-when-downgrade
+ hsts:
+ enabled: true
+ value: max-age=31536000; includeSubDomains
+ content-security-policy:
+ enabled: true
+ value: default-src 'self'
+----
+
+Set `grails.security.headers.<header>.enabled` to `false` to omit a single
header.
+For example, `grails.security.headers.xss-protection.enabled: false` omits
`X-XSS-Protection`.
+If an application or another filter has already set one of these headers on
the response, Grails leaves that value unchanged.
+
+This "already set" check can only see headers set inside the servlet container
- it has no visibility into headers a reverse proxy adds to the response after
it leaves the application.
Review Comment:
The duplication problem needs a behavioral fix, not a paragraph telling
users to turn the feature off after they discover it. This is on by default, so
a proxy-managed deployment changes what the browser receives the moment it
upgrades, and nothing in the app logs or tests will tell them.
The application cannot see headers the proxy appends on the way out, but it
can detect that the request came through a proxy. Signals available per
request: `Forwarded`, `X-Forwarded-For`, `X-Forwarded-Proto`,
`X-Forwarded-Host`, `Via`, `X-Real-IP`. Signals available from configuration:
`server.forward-headers-strategy` set to `framework` or `native` (needed
because the forwarding filter or valve strips those request headers before this
filter sees them), or an active Spring Boot `CloudPlatform` (Boot itself uses
that signal to turn forwarded-header handling on).
Suggested behavior, as a `grails.security.headers.defaults` setting with
three values:
- `auto` (default): apply the built-in default values unless the request is
detected as proxied. When it is, send only the headers the application
configured explicitly and log once so operators can see why.
- `always`: apply the defaults on every response, for deployments that know
the proxy does not touch these headers.
- `never`: never apply the defaults, only explicitly configured headers, for
deployments where the proxy owns the headers but detection cannot see it.
Explicitly configured headers (any `grails.security.headers.<header>.*` key)
are sent in every mode. Direct-exposed apps, the ones that most need these
defaults, keep them. Proxied apps send exactly what they sent on 7.x unless
they opt in, so there is no upgrade regression in either direction.
One caveat worth writing into the design: a client can add a forwarded
header to its own direct request and suppress the defaults for that one
response. That is not a practical bypass for clickjacking or MIME sniffing,
since the attacker does not control the victim browser's request headers, but
it should be stated.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]