Shelf_Helmet helps you secure your Dart Shelf and Frog apps by setting various HTTP headers. It's not a silver bullet, but it can help! Heavily inspired by helmetjs.
First, run dart pub add shelf_helmet
for your app. Then:
import 'package:shelf_helmet/shelf_helmet.dart';
var handler = const Pipeline()
.addMiddleware(helmet())
.addMiddleware(logRequests())
.addHandler(_echoRequest);
import 'package:shelf_helmet/shelf_helmet.dart';
Handler middleware(Handler handler) {
return handler.use(
fromShelfMiddleware(helmet()),
);
}
import 'package:shelf_helmet/shelf_helmet.dart';
app.use(useShelfMiddleware(helmet()));
By default, Helmet sets the following headers:
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0
To set custom options for a header, add options like this:
// This sets custom options for the `referrerPolicy` middleware.
.addMiddleware(
helmet(
options: HelmetOptions(
referrerPolicyTokens: [
ReferrerPolicyToken.noReferrer,
ReferrerPolicyToken.sameOrigin,
],
),
),
);
You can also disable a middleware:
// This disables the `contentSecurityPolicy` middleware but keeps the rest.
.addMiddleware(
helmet(
options: HelmetOptions(
enableContentSecurityPolicy: false,
),
),
);
Helmet is Shelf and Dart Frog middleware.If you need support for other frameworks or languages, see this list.)
The top-level helmet
function is a wrapper around 13 smaller middlewares.
In other words, these two code snippets are equivalent:
import 'package:shelf_helmet/shelf_helmet.dart';
// ...
.addMiddleware(helmet())
import 'package:shelf_helmet/shelf_helmet.dart';
// ...
.addMiddleware(contentSecurityPolicy())
.addMiddleware(crossOriginOpenerPolicy())
.addMiddleware(crossOriginResourcePolicy())
.addMiddleware(originAgentCluster())
.addMiddleware(referrerPolicy())
.addMiddleware(strictTransportSecurity())
.addMiddleware(xContentTypeOptions())
.addMiddleware(xDnsPrefetchControl())
.addMiddleware(xDownloadOptions())
.addMiddleware(xFrameOptions())
.addMiddleware(xPermittedCrossDomainPolicies())
.addMiddleware(xXssProtection())
Helmet is the top-level middleware for this module, including all 13 others.
// Includes all 13 middlewares
.addMiddleware(helmet());
If you want to disable one, pass options to helmet
. For example, to disable frameguard
:
// Includes 12 out of 13 middlewares, skipping `helmet.frameguard`
.addMiddleware(
helmet(options: HelmetOptions(enableXFrameOptions: false)),
);
Most of the middlewares have options, which are documented in more detail below. For example, to pass { action: "deny" }
to frameguard
:
// Includes all 13 middlewares, setting an option for `XFrameOptions`
.addMiddleware(
helmet(options: HelmetOptions(xFrameOptionsToken: XFrameOptionsAction.deny)),
);
Each middleware's name is listed below.
Default:
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
contentSecurityPolicy
sets the Content-Security-Policy
header which helps mitigate cross-site scripting attacks, among other things. See MDN's introductory article on Content Security Policy.
This middleware performs very little validation. You should rely on CSP checkers like CSP Evaluator instead.
You can use this default with the ContentSecurityPolicyOptions.useDefaults()
constructor or set the bool to true. useDefaults
is true
by default.
You can set any directives you wish. defaultSrc
is required, but can be explicitly disabled by using the ContentSecurityPolicyOptions.dangerouslyDisableDefaultSrc()
constructor. Directives can be kebab-cased (like script-src
) or camel-cased (like scriptSrc
). They are equivalent, but duplicates are not allowed.
These directives are merged into a default policy, which you can disable by setting ContentSecurityPolicyOptions(useDefaults: false)
. Here is the default policy (whitespace added for readability):
default-src 'self';
base-uri 'self';
font-src 'self' https: data:;
form-action 'self';
frame-ancestors 'self';
img-src 'self' data:;
object-src 'none';
script-src 'self';
script-src-attr 'none';
style-src 'self' https: 'unsafe-inline';
upgrade-insecure-requests
ContentSecurityPolicyOptions(reportOnly)
is a boolean, defaulting to false
. If true
, the Content-Security-Policy-Report-Only
header will be set instead. If you want to set both the normal and Report-Only
headers, see this code snippet:
.addMiddleware(
contentSecurityPolicy(
options: const ContentSecurityPolicyOptions.useDefaults(
useDefaults: true,
reportOnly: false,
),
),
);
.addMiddleware(
contentSecurityPolicy(
options: const ContentSecurityPolicyOptions.useDefaults(
useDefaults: true,
reportOnly: true,
),
),
);
You can also get the default directives object with ContentSecurityPolicy.getDefaultDirectives
.`.
Examples:
// Sets all of the defaults, but overrides `script-src` and disables the default `style-src`
.addMiddleware(
contentSecurityPolicy(
options: const ContentSecurityPolicyOptions.useDefaults(
directives: {
"script-src": ["'self'", "example.com"],
"style-src": null,
},
),
),
);
// Sets "Content-Security-Policy: default-src 'self';script-src 'self' example.com;object-src 'none';upgrade-insecure-requests"
.addMiddleware(
contentSecurityPolicy(
options: const ContentSecurityPolicyOptions(
useDefaults: false,
reportOnly: false
dangerouslyDisableDefaultSrc: false,
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "example.com"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
),
),
);
// Sets the "Content-Security-Policy-Report-Only" header instead
.addMiddleware(
contentSecurityPolicy(
options: const ContentSecurityPolicyOptions.useDefaults(
directives: {
/* ... */
},
reportOnly: true,
),
),
);
// Sets "Content-Security-Policy: script-src 'self'"
.addMiddleware(
contentSecurityPolicy(
options: const ContentSecurityPolicyOptions.dangerouslyDisableDefaultSrc(
useDefaults: false,
directives: {
"script-src": ["'self'"],
},
),
),
);
// Sets the `frame-ancestors` directive to "'none'"
// See also: `xFrameOptions`
.addMiddleware(
contentSecurityPolicy(
options: const ContentSecurityPolicyOptions.useDefaults(
directives: {
frameAncestors: ["'none'"],
},
),
),
);
You can install this module separately as contentSecurityPolicy
.
This header is not set by default.
The Cross-Origin-Embedder-Policy
header helps control what resources can be loaded cross-origin. See MDN's article on this header for more.
Standalone example:
import 'package:shelf_helmet/shelf_helmet.dart'
// Helmet does not set Cross-Origin-Embedder-Policy
// by default.
.addMiddleware(helmet());
// Sets "Cross-Origin-Embedder-Policy: credentialless"
.addMiddleware(
helmet(
options: HelmetOptions(
coepOptions: CrossOriginEmbedderPolicyOptions.credentialLess,
),
);
);
You can install this module separately as crossEmbedderPolicy
.
Cross-Origin-Opener-Policy: same-origin
crossOriginOpenerPolicy
sets the Cross-Origin-Opener-Policy
header. For more, see MDN's article on this header.
Example usage with Helmet:
// Uses the default Helmet options and adds the `crossOriginOpenerPolicy` middleware.
// Sets "Cross-Origin-Opener-Policy: same-origin"
.addMiddleware(helmet());
// Sets "Cross-Origin-Opener-Policy: same-origin-allow-popups"
.addMiddleware(helmet(
options: const HelmetOptions(
coop: CrossOriginOpenerPolicyOptions.sameOriginAllowPopups,
),
));
You can install this module separately as crossOriginOpenerPolicy
.
Default:
Cross-Origin-Resource-Policy: same-origin
crossOriginResourcePolicy
sets the Cross-Origin-Resource-Policy
header. For more, see "Consider deploying Cross-Origin Resource Policy and MDN's article on this header.
Example usage with Helmet:
// Uses the default Helmet options and adds the `crossOriginResourcePolicy` middleware.
// Sets "Cross-Origin-Resource-Policy: same-origin"
app.use(helmet());
// Sets "Cross-Origin-Resource-Policy: same-site"
.addMiddleware(helmet(
options: const HelmetOptions(
corpOptions: CrossOriginCrossOriginResourcePolicyOptions.sameSite,
),
));
Standalone example:
import 'package:shelf_helmet/shelf_helmet.dart'
// Sets Cross-Origin-Resource-Policy: same-origin
.addMiddleware(crossOriginResourcePolicy());
// Sets "Cross-Origin-Resource-Policy: cross-origin"
.addMiddleware(crossOriginResourcePolicy(
policy: CrossOriginResourcePolicyOptions.crossOrigin
));
// Sets "Cross-Origin-Resource-Policy: same-site"
.addMiddleware(crossOriginResourcePolicy(
policy: CrossOriginResourcePolicyOptions.sameSite
));
You can install this module separately as crossOriginResourcePolicy
.
Default:
Referrer-Policy: no-referrer
referrerPolicy
sets the Referrer-Policy
header which controls what information is set in the Referer
header. See "Referer header: privacy and security concerns" and the header's documentation on MDN for more.
options.policy
is a string or array of strings representing the policy. If passed as an array, it will be joined with commas, which is useful when setting a fallback policy. It defaults to no-referrer
.
Examples:
import 'package:shelf_helmet/shelf_helmet.dart';
.addMiddleware(referrerPolicy(policies: [ReferrerPolicyToken.sameOrigin])) -> Referrer-Policy: same-origin
.addMiddleware(referrerPolicy(policies: [ReferrerPolicyToken.unsafeUrl])) -> Referrer-Policy: unsafe-url
.addMiddleware(referrerPolicy(policies: [ReferrerPolicyToken.noReferrer, ReferrerPolicyToken.unsafeUrl])) -> Referrer-Policy: no-referrer,unsafe-url
.addMiddleware(referrerPolicy()) -> Referrer-Policy: no-referrer
You can install this module separately as referrerPolicy
.
Default:
Strict-Transport-Security: max-age=15552000; includeSubDomains
This middleware adds the Strict-Transport-Security
header to the response.
This tells browsers, "hey, only use HTTPS for the next period of time".
(See the spec for more.)
Note that the header won't tell users on HTTP to switch to HTTPS,
it will just tell HTTPS users to stick around.
You can enforce HTTPS with the shelf-enforces-ssl package.
This will set the Strict Transport Security header, telling browsers to visit by HTTPS for the next 180 days:
import 'package:shelf_helmet/shelf_helmet.dart';
.addMiddleware(strictTransportSecurity())
// Sets "Strict-Transport-Security: max-age=15552000; includeSubDomains"
Note that the max age must be in seconds.
The includeSubDomains
directive is present by default.
If this header is set on example.com, supported browsers will also use HTTPS on my-subdomain.example.com.
You can disable this:
import 'package:shelf_helmet/shelf_helmet.dart';
.addMiddleware(strictTransportSecurity(includeSubDomains: false))
Some browsers let you submit your site's HSTS to be baked into the browser.
You can add preload
to the header with the following code.
You can check your eligibility and submit your site at hstspreload.org.
import 'package:shelf_helmet/shelf_helmet.dart';
.addMiddleware(
strictTransportSecurity(
maxAge: const Duration(days: 365), // Must be at least 1 year to be approved
preload: true
),
)
^ The header is ignored in insecure HTTP, so it's safe to set in development.
This header is somewhat well-supported by browsers.
Default:
X-Content-Type-Options: nosniff
Some browsers will try to "sniff" mimetypes. For example,
if my server serves file.txt with a text/plain content-type,
some browsers can still run that file with <script src="file.txt"></script>
.
Many browsers will allow file.js to be run even if the content-type isn't for JavaScript.
Browsers' same-origin policies generally prevent remote resources from
being loaded dangerously, but vulnerabilities in web browsers
can cause this to be abused.
Some browsers, like Chrome,
will further isolate memory if the X-Content-Type-Options
header is seen.
There are some other vulnerabilities, too.
This middleware prevents Chrome, Opera 13+, IE 8+ and
Firefox 50+
from doing this sniffing. The following example sets the X-Content-Type-Options
header to its only option, nosniff
:
import 'package:shelf_helmet/shelf_helmet.dart'
.addMiddleware(xContentTypeOptions())
MSDN has a good description of how browsers behave when this header is sent.
You can't install this module separately.
Default:
X-DNS-Prefetch-Control: off
This middleware lets you set the X-DNS-Prefetch-Control
to control
browsers' DNS prefetching.
Read more about it on MDN
and on Chromium's docs.
Usage:
import 'package:shelf_helmet/shelf_helmet.dart'
//Set X-DNS-Prefetch-Control: off
.addMiddleware(xDownloadOptions())
//Set X-DNS-Prefetch-Control: on
.addMiddleware(xDownloadOptions(allow: true))
You can install this module separately as xDnsPrefetchControl
.
Default:
X-Download-Options: noopen
This middleware sets the X-Download-Options
header to noopen
to prevent Internet Explorer users from executing downloads
in your site's context.
import 'package:shelf_helmet/shelf_helmet.dart'
.addMiddleware(xDownloadOptions())
Some web applications will serve untrusted HTML for download. By default, some versions of IE will allow you to open those HTML files in the context of your site, which means that an untrusted HTML page could start doing bad things in the context of your pages. For more, see this MSDN blog post.
This is pretty obscure, fixing a small bug on IE only. No real drawbacks other than performance/bandwidth of setting the headers, though.
You can install this module separately as xDownloadOptions
.
Default:
X-Frame-Options: SAMEORIGIN
The X-Frame-Options
HTTP header restricts who can put your site in a frame which can help mitigate things like clickjacking attacks. The header has two modes: DENY
and SAMEORIGIN
.
This header is superseded by the frame-ancestors
Content Security Policy directive but is still useful on old browsers.
If your app does not need to be framed (and most don't) you can use DENY
. If your site can be in frames from the same origin, you can set it to SAMEORIGIN
.
Usage:
import 'package:shelf_helmet/shelf_helmet.dart'
// Sets X-Frame-Options: sameorigin
.addMiddleware(xPermittedCrossDomainPolies());
// You can use any of the following values:
.addMiddleware(xPermittedCrossDomainPolies(permittedPolicie: PermittedPolicies.deny));
.addMiddleware(xPermittedCrossDomainPolies(permittedPolicie: PermittedPolicies.sameorigin));
Default:
X-Permitted-Cross-Domain-Policies: none
The X-Permitted-Cross-Domain-Policies
header tells some web clients
(like Adobe Flash or Adobe Acrobat) your domain's policy for loading
cross-domain content. See the description on
OWASP for more.
Usage:
import 'package:shelf_helmet/shelf_helmet.dart'
// Sets X-Permitted-Cross-Domain-Policies: none
.addMiddleware(xPermittedCrossDomainPolies());
// You can use any of the following values:
.addMiddleware(xPermittedCrossDomainPolies(permittedPolicie: PermittedPolicies.none));
.addMiddleware(xPermittedCrossDomainPolies(permittedPolicie: PermittedPolicies.masterOnly));
.addMiddleware(xPermittedCrossDomainPolies(permittedPolicie: PermittedPolicies.byContentType));
.addMiddleware(xPermittedCrossDomainPolies(permittedPolicie: PermittedPolicies.all));
The by-ftp-type
is not currently supported. Please open an issue or pull request if you desire this feature!
If you don't expect Adobe products to load data from your site, you get a minor security benefit by adding this header.
You can install this module separately as xPermittedCrossDomainPolicies
.
Simple instructions to remove the X-Powered-By
HTTP header.
Technically a middleware is the way of how to remove the header.
But in Shelf you can change this header only on the server top-level of shelf. so if you want to get rid of this header you need to do:
final server = await shelf_io.serve(handler, 'localhost', 8080, poweredByHeader: null);
You can find a tutorial of how to remove in the official dart_frog
documentation.
Hackers can exploit known vulnerabilities in Shelf/Dart if they see that your site is powered by Shelf (or whichever framework you use). For example, X-Powered-By: Dart with package:shelf
is sent in every HTTP request coming from Shelf and DartFrog, by default. This won't provide much security benefit (as discussed here), but might help a tiny bit. It will also improve performance by reducing the number of bytes sent.
Default:
X-XSS-Protection: 0
helmet.xssFilter
disables browsers' buggy cross-site scripting filter by setting the X-XSS-Protection
header to 0
. See discussion about disabling the header here and documentation on MDN.
This middleware takes no options.
Examples:
import 'package:shelf_helmet/shelf_helmet.dart'
.addMiddleware(xXssProtection())
You can install this module separately as xXssProtection
.