Skip to content

Commit

Permalink
Import Referrer-Policy (referrer-policy) middleware
Browse files Browse the repository at this point in the history
This imports the [helmet-csp package][0] into this repo as part of my
effort to make Helmet a monorepo. You can find its prior history in the
old repo.

Similar to:

* 936cd27 which imported
  `referrer-policy`
* 141f131 which imported `crossdomain`
* ff12fb7 which imported
  `dont-sniff-mimetype`
* 2b64d11 which imported
  `hide-powered-by`
* 7906601 which imported `frameguard`
* d03c555 which imported `expect-ct`
* e933c28 which imported
  `dns-prefetch-control`
* 13b496f which imported `ienoopen`

[0]: https://github.com/helmetjs/csp
  • Loading branch information
EvanHahn committed Jul 8, 2020
1 parent ab556c2 commit df561bb
Show file tree
Hide file tree
Showing 12 changed files with 687 additions and 41 deletions.
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,34 @@

## 4.0.0 - Unreleased

### Added

- `helmet.contentSecurityPolicy`:
- If no `default-src` directive is supplied, an error is thrown
- Directive lists can be any iterable, not just arrays

### Changed

- `helmet.contentSecurityPolicy`:
- There is now a default set of directives if none are supplied
- Duplicate keys now throw an error
- This middleware is more lenient

### Removed

- `helmet.featurePolicy`. If you still need it, use the `feature-policy` package on npm.
- `helmet.hpkp`. If you still need it, use the `hpkp` package on npm.
- `helmet.noCache`. If you still need it, use the `nocache` package on npm.
- `helmet.frameguard` no longer supports the `ALLOW-FROM` action. [Read more here.](https://github.com/helmetjs/helmet/wiki/How-to-use-X%E2%80%93Frame%E2%80%93Options's-%60ALLOW%E2%80%93FROM%60-directive)
- `helmet.contentSecurityPolicy`:
- Removed browser sniffing (including the `browserSniff` parameter)
- Removed conditional support. This includes directive functions and support for a function as the `reportOnly`. [Read this if you need help.](https://github.com/helmetjs/helmet/wiki/Conditionally-using-middleware)
- Removed a lot of checks—you should be checking your CSP with a different tool
- Removed support for legacy headers (and therefore the `setAllHeaders` parameter). [Read this if you need help.](https://github.com/helmetjs/helmet/wiki/Setting-legacy-Content-Security-Policy-headers-in-Helmet-4)
- Dropped support for old Node versions. Node 10+ is now required
- Removed the `loose` option
- Removed the `disableAndroid` option
- `helmet.frameguard`:
- Dropped support for the `ALLOW-FROM` action. [Read more here.](https://github.com/helmetjs/helmet/wiki/How-to-use-X%E2%80%93Frame%E2%80%93Options's-%60ALLOW%E2%80%93FROM%60-directive)

## Unreleased

Expand Down
3 changes: 2 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IncomingMessage, ServerResponse } from "http";
import contentSecurityPolicy from "./middlewares/content-security-policy";
import expectCt from "./middlewares/expect-ct";
import referrerPolicy from "./middlewares/referrer-policy";
import xContentTypeOptions from "./middlewares/x-content-type-options";
Expand Down Expand Up @@ -120,7 +121,7 @@ function helmet(options: Readonly<HelmetOptions> = {}) {
};
}

helmet.contentSecurityPolicy = require("helmet-csp");
helmet.contentSecurityPolicy = contentSecurityPolicy;
helmet.dnsPrefetchControl = xDnsPrefetchControl;
helmet.expectCt = expectCt;
helmet.frameguard = xFrameOptions;
Expand Down
91 changes: 91 additions & 0 deletions middlewares/content-security-policy/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Changelog

## 3.0.0 - Unreleased

### Added

- If no `default-src` directive is supplied, an error is thrown
- Directive lists can be any iterable, not just arrays

### Changed

- There is now a default set of directives if none are supplied
- Duplicate keys now throw an error
- This middleware is more lenient

### Removed

- Removed browser sniffing (including the `browserSniff` parameter)
- Removed conditional support. This includes directive functions and support for a function as the `reportOnly`. [Read this if you need help.](https://github.com/helmetjs/helmet/wiki/Conditionally-using-middleware)
- Removed a lot of checks—you should be checking your CSP with a different tool
- Removed support for legacy headers (and therefore the `setAllHeaders` parameter). [Read this if you need help.](https://github.com/helmetjs/helmet/wiki/Setting-legacy-Content-Security-Policy-headers-in-Helmet-4)
- Dropped support for old Node versions. Node 10+ is now required
- Removed the `loose` option
- Removed the `disableAndroid` option

## 2.9.5 - 2020-02-22

### Changed

- Updated `bowser` subdependency from 2.7.0 to 2.9.0

### Fixed

- Fixed an issue some people were having when importing the `bowser` subdependency. See [#96](https://github.com/helmetjs/csp/issues/96) and [#101](https://github.com/helmetjs/csp/pull/101)
- Fixed a link in the readme. See [#100](https://github.com/helmetjs/csp/pull/100)

## 2.9.4 - 2019-10-21

### Changed

- Updated `bowser` subdependency from 2.6.1 to 2.7.0. See [#94](https://github.com/helmetjs/csp/pull/94)

## 2.9.3 - 2019-09-30

### Fixed

- Published a missing TypeScript type definition file. See [#90](https://github.com/helmetjs/csp/issues/90)

## 2.9.2 - 2019-09-20

### Fixed

- Fixed a bug where a request from Firefox 4 could delete `default-src` from future responses
- Fixed tablet PC detection by updating `bowser` subdependency to latest version

## 2.9.1 - 2019-09-04

### Changed

- Updated `bowser` subdependency from 2.5.3 to 2.5.4. See [#88](https://github.com/helmetjs/csp/pull/88)

### Fixed

- The "security" keyword was declared twice in package metadata. See [#87](https://github.com/helmetjs/csp/pull/87)

## 2.9.0 - 2019-08-28

### Added

- Added TypeScript type definitions. See [#86](https://github.com/helmetjs/csp/pull/86)

### Fixed

- Switched from `platform` to `bowser` to quiet a security vulnerability warning. See [#80](https://github.com/helmetjs/csp/issues/80)

## 2.8.0 - 2019-07-24

### Added

- Added a new `sandbox` directive, `allow-downloads-without-user-activation` (see [#85](https://github.com/helmetjs/csp/pull/85))
- Created a changelog
- Added some package metadata

### Changed

- Updated documentation to use ES2015
- Updated documentation to remove dependency on UUID package
- Updated `content-security-policy-builder` to 2.1.0
- Excluded some files from the npm package

Changes in versions 2.7.1 and below can be found in [Helmet's changelog](https://github.com/helmetjs/helmet/blob/master/CHANGELOG.md).
63 changes: 63 additions & 0 deletions middlewares/content-security-policy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Content Security Policy middleware

Content Security Policy helps prevent unwanted content being injected into your webpages. This can mitigate cross-site scripting (XSS) vulnerabilities, malicious frames, unwanted trackers, and much more.

If you want to learn how CSP works, check out the fantastic [HTML5 Rocks guide](http://www.html5rocks.com/en/tutorials/security/content-security-policy/), the [Content Security Policy Reference](http://content-security-policy.com/), and the [Content Security Policy specification](http://www.w3.org/TR/CSP/).

This middleware helps set Content Security Policies.

Basic usage:

```javascript
const csp = require("helmet-csp");

app.use(
csp({
directives: {
defaultSrc: ["'self'", "default.example"],
scriptSrc: ["'self'", "'unsafe-inline'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
reportOnly: false,
})
);
```

You can set any directives you wish. `defaultSrc` is required. Directives can be kebab-cased (like `script-src`) or camel-cased (like `scriptSrc`). They are equivalent, but duplicates are not allowed.

The `reportOnly` option, if set to `true`, sets the `Content-Security-Policy-Report-Only` header instead.

This middleware does minimal validation. You should use a more sophisticated CSP validator, like [Google's CSP Evaluator](https://csp-evaluator.withgoogle.com/), to make sure your CSP looks good.

## Recipe: generating nonces

You can dynamically generate nonces to allow inline `<script>` tags to be safely evaluated. Here's a simple example:

```js
const crypto = require("crypto");

app.use((req, res, next) {
res.locals.nonce = crypto.randomBytes(16).toString("hex");
next();
});

app.use((req, res, next) => {
csp({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", `'nonce-${res.locals.nonce}'`],
},
})(req, res, next);
});

app.use((req, res) => {
res.end(`<script nonce="${res.locals.nonce}">alert(1 + 1);</script>`);
});
```

## See also

- [Google's CSP Evaluator tool](https://csp-evaluator.withgoogle.com/)
- [GitHub's CSP journey](http://githubengineering.com/githubs-csp-journey/)
- [Content Security Policy for Single Page Web Apps](https://developer.squareup.com/blog/content-security-policy-for-single-page-web-apps/)
136 changes: 136 additions & 0 deletions middlewares/content-security-policy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { IncomingMessage, ServerResponse } from "http";

export interface ContentSecurityPolicyOptions {
directives?: {
[directiveName: string]: Iterable<string>;
};
reportOnly?: boolean;
}

const isRawPolicyDirectiveNameInvalid = (rawDirectiveName: string): boolean =>
rawDirectiveName.length === 0 || /[^a-zA-Z0-9-]/.test(rawDirectiveName);

const dashify = (str: string): string =>
str.replace(/[A-Z]/g, (capitalLetter) => "-" + capitalLetter.toLowerCase());

function getHeaderNameFromOptions({
reportOnly,
}: ContentSecurityPolicyOptions): string {
if (reportOnly) {
return "Content-Security-Policy-Report-Only";
} else {
return "Content-Security-Policy";
}
}

function getHeaderValueFromOptions(
options: ContentSecurityPolicyOptions
): string {
if ("loose" in options) {
console.warn(
"Content-Security-Policy middleware no longer needs the `loose` parameter. You should remove it."
);
}
if ("setAllHeaders" in options) {
console.warn(
"Content-Security-Policy middleware no longer supports the `setAllHeaders` parameter. See <https://github.com/helmetjs/helmet/wiki/Setting-legacy-Content-Security-Policy-headers-in-Helmet-4>."
);
}
["disableAndroid", "browserSniff"].forEach((deprecatedOption) => {
if (deprecatedOption in options) {
console.warn(
`Content-Security-Policy middleware no longer does browser sniffing, so you can remove the \`${deprecatedOption}\` option. See <https://github.com/helmetjs/csp/issues/97> for discussion.`
);
}
});

const {
directives = {
"default-src": ["'self'"],
"base-uri": ["'self'"],
"block-all-mixed-content": [],
"font-src": ["'self'", "https:", "data:"],
"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": [],
},
} = options;

const directiveNamesUsed = new Set<string>();

const result = Object.entries(directives)
.map(([rawDirectiveName, rawDirectiveValue]) => {
if (isRawPolicyDirectiveNameInvalid(rawDirectiveName)) {
throw new Error(
`Content-Security-Policy received an invalid directive name ${JSON.stringify(
rawDirectiveName
)}`
);
}
const directiveName = dashify(rawDirectiveName);
if (directiveNamesUsed.has(directiveName)) {
throw new Error(
`Content-Security-Policy received a duplicate directive ${JSON.stringify(
directiveName
)}`
);
}
directiveNamesUsed.add(directiveName);

let directiveValue: string;
if (typeof rawDirectiveValue === "string") {
directiveValue = " " + rawDirectiveValue;
} else {
directiveValue = "";
for (const element of rawDirectiveValue) {
directiveValue += " " + element;
}
}

if (!directiveValue) {
return directiveName;
}

if (/;|,/.test(directiveValue)) {
throw new Error(
`Content-Security-Policy received an invalid directive value for ${JSON.stringify(
directiveName
)}`
);
}

return `${directiveName}${directiveValue}`;
})
.join(";");

if (!directiveNamesUsed.has("default-src")) {
throw new Error(
"Content-Security-Policy needs a default-src but none was provided"
);
}

return result;
}

function contentSecurityPolicy(
options: Readonly<ContentSecurityPolicyOptions> = {}
) {
const headerName = getHeaderNameFromOptions(options);
const headerValue = getHeaderValueFromOptions(options);

return function contentSecurityPolicyMiddleware(
_req: IncomingMessage,
res: ServerResponse,
next: () => void
) {
res.setHeader(headerName, headerValue);
next();
};
}

module.exports = contentSecurityPolicy;
export default contentSecurityPolicy;
1 change: 1 addition & 0 deletions middlewares/content-security-policy/package-files.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["index.js", "index.d.ts"]
12 changes: 12 additions & 0 deletions middlewares/content-security-policy/package-overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "helmet-csp",
"author": "Adam Baldwin <[email protected]> (https://evilpacket.net)",
"contributors": [
"Evan Hahn <[email protected]> (https://evanhahn.com)",
"Ryan Cannon <[email protected]> (https://ryancannon.com)"
],
"description": "Content Security Policy middleware",
"version": "2.9.5",
"keywords": ["express", "security", "content-security-policy", "csp", "xss"],
"homepage": "https://helmetjs.github.io/docs/csp/"
}
Loading

0 comments on commit df561bb

Please sign in to comment.