rembrembdocs

Secure Headers Middleware

Secure Headers Middleware simplifies the setup of security headers. Inspired in part by the capabilities of Helmet, it allows you to control the activation and deactivation of specific security headers.

Import

ts

import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'

Usage

You can use the optimal settings by default.

ts

const app = new Hono()
app.use(secureHeaders())

You can suppress unnecessary headers by setting them to false.

ts

const app = new Hono()
app.use(
  '*',
  secureHeaders({
    xFrameOptions: false,
    xXssProtection: false,
  })
)

You can override default header values using a string.

ts

const app = new Hono()
app.use(
  '*',
  secureHeaders({
    strictTransportSecurity:
      'max-age=63072000; includeSubDomains; preload',
    xFrameOptions: 'DENY',
    xXssProtection: '1',
  })
)

Supported Options

Each option corresponds to the following Header Key-Value pairs.

Option

Header

Value

Default

-

X-Powered-By

(Delete Header)

True

contentSecurityPolicy

Content-Security-Policy

Usage: Setting Content-Security-Policy

No Setting

contentSecurityPolicyReportOnly

Content-Security-Policy-Report-Only

Usage: Setting Content-Security-Policy

No Setting

trustedTypes

Trusted Types

Usage: Setting Content-Security-Policy

No Setting

requireTrustedTypesFor

Require Trusted Types For

Usage: Setting Content-Security-Policy

No Setting

crossOriginEmbedderPolicy

Cross-Origin-Embedder-Policy

require-corp

False

crossOriginResourcePolicy

Cross-Origin-Resource-Policy

same-origin

True

crossOriginOpenerPolicy

Cross-Origin-Opener-Policy

same-origin

True

originAgentCluster

Origin-Agent-Cluster

?1

True

referrerPolicy

Referrer-Policy

no-referrer

True

reportingEndpoints

Reporting-Endpoints

Usage: Setting Content-Security-Policy

No Setting

reportTo

Report-To

Usage: Setting Content-Security-Policy

No Setting

strictTransportSecurity

Strict-Transport-Security

max-age=15552000; includeSubDomains

True

xContentTypeOptions

X-Content-Type-Options

nosniff

True

xDnsPrefetchControl

X-DNS-Prefetch-Control

off

True

xDownloadOptions

X-Download-Options

noopen

True

xFrameOptions

X-Frame-Options

SAMEORIGIN

True

xPermittedCrossDomainPolicies

X-Permitted-Cross-Domain-Policies

none

True

xXssProtection

X-XSS-Protection

0

True

permissionPolicy

Permissions-Policy

Usage: Setting Permission-Policy

No Setting

Middleware Conflict

Please be cautious about the order of specification when dealing with middleware that manipulates the same header.

In this case, Secure-headers operates and the x-powered-by is removed:

ts

const app = new Hono()
app.use(secureHeaders())
app.use(poweredBy())

In this case, Powered-By operates and the x-powered-by is added:

ts

const app = new Hono()
app.use(poweredBy())
app.use(secureHeaders())

Setting Content-Security-Policy

ts

const app = new Hono()
app.use(
  '/test',
  secureHeaders({
    reportingEndpoints: [
      {
        name: 'endpoint-1',
        url: 'https://example.com/reports',
      },
    ],
    // -- or alternatively
    // reportTo: [
    //   {
    //     group: 'endpoint-1',
    //     max_age: 10886400,
    //     endpoints: [{ url: 'https://example.com/reports' }],
    //   },
    // ],
    contentSecurityPolicy: {
      defaultSrc: ["'self'"],
      baseUri: ["'self'"],
      childSrc: ["'self'"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'", 'https:', 'data:'],
      formAction: ["'self'"],
      frameAncestors: ["'self'"],
      frameSrc: ["'self'"],
      imgSrc: ["'self'", 'data:'],
      manifestSrc: ["'self'"],
      mediaSrc: ["'self'"],
      objectSrc: ["'none'"],
      reportTo: 'endpoint-1',
      reportUri: '/csp-report',
      sandbox: ['allow-same-origin', 'allow-scripts'],
      scriptSrc: ["'self'"],
      scriptSrcAttr: ["'none'"],
      scriptSrcElem: ["'self'"],
      styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
      styleSrcAttr: ['none'],
      styleSrcElem: ["'self'", 'https:', "'unsafe-inline'"],
      upgradeInsecureRequests: [],
      workerSrc: ["'self'"],
    },
  })
)

nonce attribute

You can add a nonce attribute to a script or style element by adding the NONCE imported from hono/secure-headers to a scriptSrc or styleSrc:

tsx

import { secureHeaders, NONCE } from 'hono/secure-headers'
import type { SecureHeadersVariables } from 'hono/secure-headers'

// Specify the variable types to infer the `c.get('secureHeadersNonce')`:
type Variables = SecureHeadersVariables

const app = new Hono<{ Variables: Variables }>()

// Set the pre-defined nonce value to `scriptSrc`:
app.get(
  '*',
  secureHeaders({
    contentSecurityPolicy: {
      scriptSrc: [NONCE, 'https://allowed1.example.com'],
    },
  })
)

// Get the value from `c.get('secureHeadersNonce')`:
app.get('/', (c) => {
  return c.html(
    <html>
      <body>
        {/** contents */}
        <script
          src='/js/client.js'
          nonce={c.get('secureHeadersNonce')}
        />
      </body>
    </html>
  )
})

If you want to generate the nonce value yourself, you can also specify a function as the following:

tsx

const app = new Hono<{
  Variables: { myNonce: string }
}>()

const myNonceGenerator: ContentSecurityPolicyOptionHandler = (c) => {
  // This function is called on every request.
  const nonce = Math.random().toString(36).slice(2)
  c.set('myNonce', nonce)
  return `'nonce-${nonce}'`
}

app.get(
  '*',
  secureHeaders({
    contentSecurityPolicy: {
      scriptSrc: [myNonceGenerator, 'https://allowed1.example.com'],
    },
  })
)

app.get('/', (c) => {
  return c.html(
    <html>
      <body>
        {/** contents */}
        <script src='/js/client.js' nonce={c.get('myNonce')} />
      </body>
    </html>
  )
})

Setting Permission-Policy

The Permission-Policy header allows you to control which features and APIs can be used in the browser. Here's an example of how to set it:

ts

const app = new Hono()
app.use(
  '*',
  secureHeaders({
    permissionsPolicy: {
      fullscreen: ['self'], // fullscreen=(self)
      bluetooth: ['none'], // bluetooth=(none)
      payment: ['self', 'https://example.com'], // payment=(self "https://example.com")
      syncXhr: [], // sync-xhr=()
      camera: false, // camera=none
      microphone: true, // microphone=*
      geolocation: ['*'], // geolocation=*
      usb: ['self', 'https://a.example.com', 'https://b.example.com'], // usb=(self "https://a.example.com" "https://b.example.com")
      accelerometer: ['https://*.example.com'], // accelerometer=("https://*.example.com")
      gyroscope: ['src'], // gyroscope=(src)
      magnetometer: [
        'https://a.example.com',
        'https://b.example.com',
      ], // magnetometer=("https://a.example.com" "https://b.example.com")
    },
  })
)