Skip to content

Commit

Permalink
Replace next-secure-headers with Nosecone for security headers (#343)
Browse files Browse the repository at this point in the history
* Replace next-secure-headers with Nosecone

* Disable CSP

* Centralize Nosecone config

* Set web security headers

* Add security headers docs

* Update pnpm-lock.yaml

* Minor changes

* Update headers.mdx

---------

Co-authored-by: Hayden Bleasel <[email protected]>
  • Loading branch information
davidmytton and haydenbleasel authored Dec 11, 2024
1 parent 1ddbc0f commit 32ee746
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 45 deletions.
5 changes: 4 additions & 1 deletion apps/app/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { authMiddleware } from '@repo/auth/middleware';
import { noseconeConfig, noseconeMiddleware } from '@repo/security/middleware';

export default authMiddleware();
const securityHeaders = noseconeMiddleware(noseconeConfig);

export default authMiddleware(() => securityHeaders());

export const config = {
matcher: [
Expand Down
2 changes: 1 addition & 1 deletion apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
},
"dependencies": {
"@prisma/client": "6.0.1",
"@repo/auth": "workspace:*",
"@repo/analytics": "workspace:*",
"@repo/auth": "workspace:*",
"@repo/collaboration": "workspace:*",
"@repo/database": "workspace:*",
"@repo/design-system": "workspace:*",
Expand Down
7 changes: 5 additions & 2 deletions apps/web/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { authMiddleware } from '@repo/auth/middleware';
import { env } from '@repo/env';
import { parseError } from '@repo/observability/error';
import { secure } from '@repo/security';
import { noseconeConfig, noseconeMiddleware } from '@repo/security/middleware';
import { NextResponse } from 'next/server';

export const config = {
Expand All @@ -10,9 +11,11 @@ export const config = {
matcher: ['/((?!_next/static|_next/image|ingest|favicon.ico).*)'],
};

const securityHeaders = noseconeMiddleware(noseconeConfig);

export default authMiddleware(async (_auth, request) => {
if (!env.ARCJET_KEY) {
return NextResponse.next();
return securityHeaders();
}

try {
Expand All @@ -26,7 +29,7 @@ export default authMiddleware(async (_auth, request) => {
request
);

return NextResponse.next();
return securityHeaders();
} catch (error) {
const message = parseError(error);

Expand Down
107 changes: 94 additions & 13 deletions docs/features/security/headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,106 @@ title: Security Headers
description: Security headers used to protect your application.
---

next-forge uses [next-secure-headers](https://github.com/jagaapple/next-secure-headers) to set HTTP response headers related to security.
import { Authors } from '/snippets/authors.mdx';

<Authors data={[{
user: {
name: 'Hayden Bleasel',
id: 'haydenbleasel',
},
company: {
name: 'next-forge',
id: 'next-forge',
},
}, {
user: {
name: 'David Mytton',
id: 'davidmytton',
},
company: {
name: 'Arcjet',
id: 'arcjet',
},
}]} />

next-forge uses [Nosecone](https://docs.arcjet.com/nosecone/quick-start) to set HTTP response headers related to security.

## Configuration

Here are the headers we have enabled:

| Property | Header | Description | Value |
| --- | --- | --- | --- |
| `forceHTTPSRedirect` | `Strict-Transport-Security` | Prevents browsers from connecting to your site over HTTP. | `[true, { maxAge: 63_072_000, includeSubDomains: true, preload: true }]` |
| `frameGuard` | `X-Frame-Options` | Prevents browsers from rendering your site in an iframe. | `deny` |
| `noopen` | `X-Download-Options` | Prevents browsers from automatically opening downloaded files in the same origin as the page. | `noopen` |
| `nosniff` | `X-Content-Type-Options` | Prevents browsers from MIME-sniffing a response away from the declared content type. | `nosniff` |
| `xssProtection` | `X-XSS-Protection` | Prevents browsers from executing inline scripts if a cross-site scripting attack is detected. | `sanitize` |
| `contentSecurityPolicy` | `Content-Security-Policy` | Sets a policy to prevent a wide range of different types of attacks, including Cross Site Scripting (XSS) and data injection attacks. | `false` |
| `expectCT` | `Expect-CT` | Enables a mechanism to mitigate the risk of fraudulent certificates being used in connections to your site. | `false` |
| `referrerPolicy` | `Referrer-Policy` | Controls how much of the full URL is included in the `Referer` header. | `false` |
- `Cross-Origin-Embedder-Policy` (COEP)
- `Cross-Origin-Opener-Policy`
- `Cross-Origin-Resource-Policy`
- `Origin-Agent-Cluster`
- `Referrer-Policy`
- `Strict-Transport-Security` (HSTS)
- `X-Content-Type-Options`
- `X-DNS-Prefetch-Control`
- `X-Download-Options`
- `X-Frame-Options`
- `X-Permitted-Cross-Domain-Policies`
- `X-XSS-Protection`

<Tip>The `forceHTTPSRedirect` property has been customized from the default to include subdomains and preload the HSTS policy. This should allow you to submit your site at [hstspreload.org](https://hstspreload.org/) without any issues.</Tip>
See the [Nosecone reference](https://docs.arcjet.com/nosecone/reference) for details on each header and configuration options.

## Usage

The headers are enabled by default when using the `next-config` package. If you are customizing your `next.config.ts` file, you can extend the headers manually.
Recommended headers are set by default and configured in `@repo/security/middleware`. Changing the configuration here will affect all apps.

They are then attached to the response within the middleware in `apps/app/middleware` and `apps/web/middleware.ts`. Adjusting the configuration in these files will only affect the specific app.

## Content Security Policy (CSP)

The CSP header is not set by default because it requires specific configuration based on the next-forge features you have enabled.

In the meantime, you can set the CSP header using the Nosecone configuration. For example, the following CSP configuration will work with the default next-forge features:

```ts
import type { NoseconeOptions } from '@nosecone/next';
import { defaults as noseconeDefaults } from '@nosecone/next';

const noseconeOptions: NoseconeOptions = {
...noseconeDefaults,
contentSecurityPolicy: {
...noseconeDefaults.contentSecurityPolicy,
directives: {
...noseconeDefaults.contentSecurityPolicy.directives,
scriptSrc: [
// We have to use unsafe-inline because next-themes and Vercel Analytics
// do not support nonce
// https://github.com/pacocoursey/next-themes/issues/106
// https://github.com/vercel/analytics/issues/122
//...noseconeDefaults.contentSecurityPolicy.directives.scriptSrc,
"'self'",
"'unsafe-inline'",
"https://www.googletagmanager.com",
"https://*.clerk.accounts.dev",
"https://va.vercel-scripts.com",
],
connectSrc: [
...noseconeDefaults.contentSecurityPolicy.directives.connectSrc,
"https://*.clerk.accounts.dev",
"https://*.google-analytics.com",
"https://clerk-telemetry.com",
],
workerSrc: [
...noseconeDefaults.contentSecurityPolicy.directives.workerSrc,
"blob:",
"https://*.clerk.accounts.dev"
],
imgSrc: [
...noseconeDefaults.contentSecurityPolicy.directives.imgSrc,
"https://img.clerk.com"
],
objectSrc: [
...noseconeDefaults.contentSecurityPolicy.directives.objectSrc,
],
// We only set this in production because the server may be started
// without HTTPS
upgradeInsecureRequests: process.env.NODE_ENV === "production",
},
},
}
```

17 changes: 0 additions & 17 deletions packages/next-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { env } from '@repo/env';
import { withSentryConfig } from '@sentry/nextjs';
import withVercelToolbar from '@vercel/toolbar/plugins/next';
import type { NextConfig } from 'next';
import { createSecureHeaders } from 'next-secure-headers';

const otelRegex = /@opentelemetry\/instrumentation/;

Expand Down Expand Up @@ -39,22 +38,6 @@ const baseConfig: NextConfig = {
];
},

// biome-ignore lint/suspicious/useAwait: headers is async
async headers() {
return [
{
source: '/(.*)',
headers: createSecureHeaders({
// HSTS Preload: https://hstspreload.org/
forceHTTPSRedirect: [
true,
{ maxAge: 63_072_000, includeSubDomains: true, preload: true },
],
}),
},
];
},

webpack(config, { isServer }) {
if (isServer) {
config.plugins = [...config.plugins, new PrismaPlugin()];
Expand Down
3 changes: 1 addition & 2 deletions packages/next-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"@next/bundle-analyzer": "^15.1.0",
"@prisma/nextjs-monorepo-workaround-plugin": "^6.0.1",
"@sentry/nextjs": "^8.43.0",
"@vercel/toolbar": "^0.1.28",
"next-secure-headers": "^2.2.0"
"@vercel/toolbar": "^0.1.28"
}
}
23 changes: 23 additions & 0 deletions packages/security/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
type NoseconeOptions,
defaults,
withVercelToolbar,
} from '@nosecone/next';
import { env } from '@repo/env';
export { createMiddleware as noseconeMiddleware } from '@nosecone/next';

// Nosecone security headers configuration
// https://docs.arcjet.com/nosecone/quick-start
const noseconeOptions: NoseconeOptions = {
...defaults,
// Content Security Policy (CSP) is disabled by default because the values
// depend on which Next Forge features are enabled. See
// https://docs.next-forge.com/features/security/headers for guidance on how
// to configure it.
contentSecurityPolicy: false,
};

export const noseconeConfig: NoseconeOptions =
env.NODE_ENV === 'development' && env.FLAGS_SECRET
? withVercelToolbar(noseconeOptions)
: noseconeOptions;
2 changes: 2 additions & 0 deletions packages/security/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@repo/security",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"clean": "git clean -xdf .cache .turbo dist node_modules",
"format": "biome lint --write .",
Expand All @@ -10,6 +11,7 @@
},
"dependencies": {
"@arcjet/next": "1.0.0-alpha.34",
"@nosecone/next": "1.0.0-alpha.34",
"@repo/env": "workspace:*"
},
"devDependencies": {
Expand Down
29 changes: 20 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 32ee746

Please sign in to comment.