보안 헤더 미들웨어
보안 헤더 미들웨어는 보안 헤더 설정을 간편하게 해준다. 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 |
| contentSecurityPolicy | Content-Security-Policy | 사용법: Content-Security-Policy 설정 | 설정 없음 |
| contentSecurityPolicyReportOnly | Content-Security-Policy-Report-Only | 사용법: Content-Security-Policy 설정 | 설정 없음 |
| 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 | 사용법: Content-Security-Policy 설정 | 설정 없음 |
| reportTo | Report-To | 사용법: Content-Security-Policy 설정 | 설정 없음 |
| 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 | 사용법: 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에서 가져온 NONCE를 scriptSrc 또는 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")
},
})
)