Skip to content

Latest commit

 

History

History
663 lines (479 loc) · 21.1 KB

README.md

File metadata and controls

663 lines (479 loc) · 21.1 KB

Shelf_Helmet

codecov

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.

Quick start

First, run dart pub add shelf_helmet for your app. Then:

As shelf middleware

import 'package:shelf_helmet/shelf_helmet.dart';

var handler = const Pipeline()
    .addMiddleware(helmet())
    .addMiddleware(logRequests())
    .addHandler(_echoRequest);

As dart_frog middleware

import 'package:shelf_helmet/shelf_helmet.dart';

Handler middleware(Handler handler) {
  return handler.use(
    fromShelfMiddleware(helmet()),
  );
}

As pharaoh middleware

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,
        ),
    ),
);

How it works

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())

Reference

helmet(options)

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.

contentSecurityPolicy(options)

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.

crossOriginEmbedderPolicy(options)

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.

crossOriginOpenerPolicy()

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.

crossOriginResourcePolicy()

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.

helmet.referrerPolicy(options)

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.

strictTransportSecurity

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.

xContentTypeOptions

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.

xDnsPrefetchControl

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.

xDownloadOptions

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.

xFrameOptions(options)

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));

xPermittedCrossDomainPolicies(options)

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.

xPoweredBy

Simple instructions to remove the X-Powered-By HTTP header. Technically a middleware is the way of how to remove the header.

Remove the Header in shelf

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);

Remove the Header in dart_frog

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.

xXssProtection

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.