Skip to content

보안 헤더 미들웨어

보안 헤더 미들웨어는 보안 헤더 설정을 간편하게 해준다. Helmet의 기능에서 영감을 받아 특정 보안 헤더를 활성화하거나 비활성화할 수 있다.

Import

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

사용 방법

기본적으로 최적의 설정을 사용할 수 있다.

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

불필요한 헤더는 false로 설정해 제외할 수 있다.

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

문자열을 사용해 기본 헤더 값을 재정의할 수도 있다.

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

지원 옵션

각 옵션은 다음과 같은 헤더 키-값 쌍에 해당한다.

옵션헤더기본값
-X-Powered-By(헤더 삭제)True
contentSecurityPolicyContent-Security-Policy사용법: Content-Security-Policy 설정설정 없음
contentSecurityPolicyReportOnlyContent-Security-Policy-Report-Only사용법: Content-Security-Policy 설정설정 없음
crossOriginEmbedderPolicyCross-Origin-Embedder-Policyrequire-corpFalse
crossOriginResourcePolicyCross-Origin-Resource-Policysame-originTrue
crossOriginOpenerPolicyCross-Origin-Opener-Policysame-originTrue
originAgentClusterOrigin-Agent-Cluster?1True
referrerPolicyReferrer-Policyno-referrerTrue
reportingEndpointsReporting-Endpoints사용법: Content-Security-Policy 설정설정 없음
reportToReport-To사용법: Content-Security-Policy 설정설정 없음
strictTransportSecurityStrict-Transport-Securitymax-age=15552000; includeSubDomainsTrue
xContentTypeOptionsX-Content-Type-OptionsnosniffTrue
xDnsPrefetchControlX-DNS-Prefetch-ControloffTrue
xDownloadOptionsX-Download-OptionsnoopenTrue
xFrameOptionsX-Frame-OptionsSAMEORIGINTrue
xPermittedCrossDomainPoliciesX-Permitted-Cross-Domain-PoliciesnoneTrue
xXssProtectionX-XSS-Protection0True
permissionPolicyPermissions-Policy사용법: Permission-Policy 설정설정 없음

미들웨어 충돌 주의사항

동일한 헤더를 조작하는 미들웨어를 다룰 때는 순서를 주의해야 한다.

이 경우에는 Secure-headers가 먼저 동작하여 x-powered-by 헤더가 제거된다:

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

이 경우에는 Powered-By가 먼저 동작하여 x-powered-by 헤더가 추가된다:

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

Content-Security-Policy 설정

ts
const app = new Hono()
app.use(
  '/test',
  secureHeaders({
    reportingEndpoints: [
      {
        name: 'endpoint-1',
        url: 'https://example.com/reports',
      },
    ],
    // -- 또는 다음과 같이 대체 가능
    // 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',
      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 속성

script 또는 style 엘리먼트에 nonce 속성을 추가하려면 hono/secure-headers에서 가져온 NONCEscriptSrc 또는 styleSrc에 지정한다:

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

// `c.get('secureHeadersNonce')`의 타입을 추론하기 위해 변수 타입을 지정한다:
type Variables = SecureHeadersVariables

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

// 미리 정의된 nonce 값을 `scriptSrc`에 설정한다:
app.get(
  '*',
  secureHeaders({
    contentSecurityPolicy: {
      scriptSrc: [NONCE, 'https://allowed1.example.com'],
    },
  })
)

// `c.get('secureHeadersNonce')`에서 값을 가져온다:
app.get('/', (c) => {
  return c.html(
    <html>
      <body>
        {/** contents */}
        <script
          src='/js/client.js'
          nonce={c.get('secureHeadersNonce')}
        />
      </body>
    </html>
  )
})

만약 nonce 값을 직접 생성하고 싶다면, 다음과 같이 함수를 지정할 수도 있다:

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

const myNonceGenerator: ContentSecurityPolicyOptionHandler = (c) => {
  // 이 함수는 모든 요청에서 호출된다.
  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>
  )
})

Permission-Policy 설정

Permission-Policy 헤더를 사용하면 브라우저에서 어떤 기능과 API를 사용할 수 있는지 제어할 수 있다. 다음은 이를 설정하는 예제다:

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")
    },
  })
)

Released under the MIT License.