Skip to content

Helmet

@vafast/helmet 为响应附加常见安全相关 HTTP 头(CSP、HSTS、X-Frame-Options 等)。只改响应头,不改业务逻辑。

调用 vafastHelmet(config?) 返回中间件:在 next() 拿到业务响应后,把安全头合并进新的 Response 再返回。

先搞清几个概念(给新用户)

浏览器会按响应头决定「能不能嵌 iframe」「脚本从哪加载」「是否强制 HTTPS」等。Helmet 就是帮你批量写好这些头,减少漏配。

名词白话
CSP(Content-Security-Policy)告诉浏览器:脚本、样式、图片、接口请求等允许从哪些来源加载。收紧后可明显降低 XSS 注入脚本的危害
Nonce一次性随机串。开启 useNonce 后写入 CSP,并额外返回 X-Nonce。页面里内联 <script> / <style> 需带上同一 nonce,才允许执行
HSTS(Strict-Transport-Security)告诉浏览器:以后访问本站必须用 HTTPS,并记住一段时间。本包仅在 NODE_ENV === 'production' 时写出,避免本地 HTTP 开发被「锁死」
X-Frame-Options控制本站能否被别的页面用 <iframe> 嵌入,用来防点击劫持
Referrer-Policy控制跳转到外站时,浏览器会不会带上完整来源 URL(可能含路径、查询串)
Permissions-Policy控制页面能否用摄像头、麦克风、定位等浏览器能力;空数组 [] 表示禁用该能力
CORP / COOP跨源资源策略 / 跨源打开者策略:限制谁能加载你的资源、弹窗之间是否共享浏览上下文(与隔离、安全相关)
Report-To告诉浏览器把违规/崩溃等报告发到哪些端点(配合监控)

不必一次全懂:默认配置已经可用;业务有 CDN、第三方脚本时再改 CSP。

安装

bash
npm install @vafast/helmet

快速开始

typescript
import { Server, defineRoute, defineRoutes, json, serve } from 'vafast'
import { vafastHelmet } from '@vafast/helmet'

const routes = defineRoutes([
  defineRoute({
    method: 'GET',
    path: '/',
    handler: () => json({ ok: true }),
  }),
])

const server = new Server(routes)
server.use(vafastHelmet())
serve({ fetch: server.fetch, port: 3000 })

零配置时会带上默认 CSP、X-Frame-Options: DENY、XSS 相关头、Referrer-Policy、Permissions-Policy、CORP/COOP 等;HSTS 仅在生产环境出现。另会始终设置 X-Content-Type-Options: nosniff(不可通过选项关闭)。

用法

全局挂载

typescript
server.use(vafastHelmet())

单路由挂载

typescript
defineRoute({
  method: 'GET',
  path: '/secure',
  middleware: [vafastHelmet()],
  handler: () => json({ ok: true }),
})

自定义 CSP

传入的 csp 会与默认 CSP 浅合并(只覆盖你写的字段,其余保留默认):

typescript
import { vafastHelmet, permission } from '@vafast/helmet'

server.use(
  vafastHelmet({
    csp: {
      defaultSrc: [permission.SELF],
      scriptSrc: [permission.SELF],
      imgSrc: [permission.SELF, permission.DATA, 'https:'],
    },
    frameOptions: 'SAMEORIGIN',
  }),
)

开启 CSP Nonce

csp.useNonce: true 时会生成 nonce,写入 script-src / style-src,并额外设置响应头 X-Nonce(业务侧把该值注入 HTML 模板):

typescript
server.use(
  vafastHelmet({
    csp: {
      useNonce: true,
      scriptSrc: [permission.SELF],
      styleSrc: [permission.SELF],
    },
  }),
)

Report-Only 模式

先观察违规、不拦截加载,适合上线前试跑:

typescript
server.use(
  vafastHelmet({
    csp: {
      reportOnly: true,
      reportUri: '/csp-report',
    },
  }),
)

此时写出的是 Content-Security-Policy-Report-Only,而不是正式的 Content-Security-Policy

配置 Report-To

typescript
server.use(
  vafastHelmet({
    reportTo: [
      {
        group: 'csp-endpoint',
        maxAge: 10886400,
        endpoints: [{ url: 'https://example.com/reports' }],
        includeSubdomains: true,
      },
    ],
  }),
)

额外自定义头

typescript
server.use(
  vafastHelmet({
    customHeaders: {
      'X-App-Version': '1.0.0',
    },
  }),
)

API

导出

导出说明
vafastHelmet(config?)主入口,返回中间件
elysiaHelmetvafastHelmet 的兼容别名
permissionCSP 常用字面量常量(见下表)
SecurityConfig顶层配置类型
CSPConfigCSP 子配置类型
HSTSConfigHSTS 子配置类型
ReportToConfigReport-To 单项配置类型

vafastHelmet(config?: Partial<SecurityConfig>)

未传的字段使用默认值;csp / hsts / permissionsPolicy 在传入时与默认对象浅合并

参数类型默认说明
cspCSPConfig见下方「默认 CSP」与「CSP 指令含义」Content-Security-Policy(或 Report-Only 变体)
frameOptions'DENY' | 'SAMEORIGIN' | 'ALLOW-FROM''DENY'X-Frame-OptionsDENY = 禁止任何嵌入;SAMEORIGIN = 仅同源可嵌;ALLOW-FROM 为历史取值,现代浏览器支持差,一般不要用
xssProtectionbooleantruetrue 时写 X-XSS-Protection: 1; mode=block(旧浏览器遗留机制;现代防护仍主要靠 CSP)
dnsPrefetchbooleanfalseX-DNS-Prefetch-Controltrueonfalseoff
referrerPolicy见下方枚举'strict-origin-when-cross-origin'Referrer-Policy:控制外链请求携带多少 Referer
permissionsPolicyRecord<string, string[]>见下方「默认 Permissions-Policy」Permissions-Policy;某能力对应空数组表示禁用
hstsHSTSConfig{ maxAge: 15552000, includeSubDomains: true, preload: true }NODE_ENV === 'production' 时写入 Strict-Transport-Security
corp'same-origin' | 'same-site' | 'cross-origin''same-origin'Cross-Origin-Resource-Policy:谁可以加载本响应为资源
coop'unsafe-none' | 'same-origin-allow-popups' | 'same-origin''same-origin'Cross-Origin-Opener-Policy:与弹窗 / window.opener 隔离相关
reportToReportToConfig[]Report-To 头(JSON);未配置则不写
customHeadersRecord<string, string>额外自定义响应头,直接合并

另会始终设置 X-Content-Type-Options: nosniff(防止浏览器把非脚本 MIME 误当成脚本执行)。

referrerPolicy 可选值

白话
no-referrer从不发送 Referer
no-referrer-when-downgradeHTTPS→HTTP 时不发,其它情况发完整 URL
origin只发源(协议+主机+端口)
origin-when-cross-origin同源发完整 URL,跨源只发 origin
same-origin仅同源请求带 Referer
strict-origin只发 origin;HTTPS→HTTP 不发
strict-origin-when-cross-origin(默认)同源完整 URL;跨源只发 origin;降级不发
unsafe-url总是发完整 URL(可能泄露路径/查询串,慎用)

默认 CSP

指令(配置字段)默认值
defaultSrc[permission.SELF]'self'
scriptSrc[permission.SELF, permission.UNSAFE_INLINE]
styleSrc[permission.SELF, permission.UNSAFE_INLINE]
imgSrc[permission.SELF, permission.DATA, permission.BLOB]
fontSrc[permission.SELF]
connectSrc[permission.SELF]
frameSrc[permission.SELF]
objectSrc[permission.NONE]
baseUri[permission.SELF]

默认含 'unsafe-inline',是为了让未改造的内联脚本/样式先能跑;生产建议逐步去掉,改用 nonce 或外链。

CSP 指令含义(CSPConfig

配置字段为 camelCase,写出的头里会转成 kebab-case(如 scriptSrcscript-src)。数组里每一项是一个允许的源表达式。

字段对应指令白话
defaultSrcdefault-src兜底:其它未单独声明的资源类型走这里
scriptSrcscript-src允许执行哪些脚本来源
styleSrcstyle-src允许加载哪些样式来源
imgSrcimg-src允许加载哪些图片来源
fontSrcfont-src允许加载哪些字体来源
connectSrcconnect-src允许 fetch / XHR / WebSocket 等连哪些源
frameSrcframe-src允许本页嵌哪些源的 frame / iframe
objectSrcobject-src允许 <object> / <embed> / <applet> 的源;默认 'none'
baseUribase-uri限制 <base href> 能设到哪,防被改基准 URL
reportUrireport-uriCSP 违规报告提交地址(字符串,不是数组)
useNonce—(布尔开关)true 时给 script-src / style-src 追加 'nonce-...',并写响应头 X-Nonce
reportOnly—(布尔开关)true 时使用 Content-Security-Policy-Report-Only,只上报不拦截

useNonce / reportOnly 不会被拼进 CSP 指令字符串,只影响生成逻辑。

HSTSConfig

字段类型默认说明
maxAgenumber15552000(约 180 天)浏览器记住「必须 HTTPS」的秒数。初始化时若 < 0 会抛错
includeSubDomainsbooleantruetrue 时附加 ; includeSubDomains,子域也强制 HTTPS
preloadbooleantruetrue 时附加 ; preload(提交浏览器预加载列表时需要;未全站 HTTPS 勿开)

再次强调:即使配置了 hsts非 production 也不会写出 Strict-Transport-Security

默认 Permissions-Policy

能力键默认白话
camera[]禁用摄像头
microphone[]禁用麦克风
geolocation[]禁用地理位置
interest-cohort[]禁用 FLoC / interest-cohort 类追踪相关能力

空数组序列化为 camera=() 这种「不允许任何源」的形式。若要允许自身:{ camera: ["self"] }(键名按浏览器 Permissions-Policy 规范书写)。

传入 permissionsPolicy 时与默认表浅合并(只覆盖你写的键)。

ReportToConfig

字段类型必填说明
groupstring端点组名
maxAgenumber配置缓存秒数;< 0 时初始化抛错
endpointsArray<{ url: string; priority?: number; weight?: number }>至少一个端点,否则初始化抛错
includeSubdomainsboolean是否包含子域

写出时字段名会转为规范 JSON:max_ageinclude_subdomains

permission 常量

写 CSP 数组时优先用常量,避免漏引号:

常量白话
permission.SELF'self'允许与当前文档同源
permission.UNSAFE_INLINE'unsafe-inline'允许内联脚本/样式(削弱 XSS 防护,尽量少用)
permission.HTTPShttps:允许任意 HTTPS 源(很宽)
permission.DATAdata:允许 data: URL(常见于小图)
permission.NONE'none'不允许任何源
permission.BLOBblob:允许 blob: URL

也可在数组里直接写具体源,如 'https://cdn.example.com'

最佳实践

  • 生产环境再依赖 HSTS;本地开发不要强行模拟 NODE_ENV=production,除非已全站 HTTPS。
  • CSP 按业务收紧:尽量去掉 'unsafe-inline',需要内联脚本时用 useNonce
  • 对外嵌入第三方页面时,按需放宽 frameOptions / corp / coop,避免误伤合法跨域。
  • 新策略可先用 csp.reportOnly: true 收集违规,再切正式拦截。
  • 使用 CDN / 第三方埋点时,把对应域名加进 scriptSrc / connectSrc / imgSrc,否则会被浏览器拦掉。

注意事项

  • HSTS 只在生产环境写出;非 production 即使配置了 hsts 也不会设置该头。
  • hsts.maxAge < 0reportTo 配置非法(maxAge < 0、endpoints 为空)会在初始化时抛错。
  • 中间件在 next() 之后包装 Response 写入头;下游若返回不可变 Headers,行为取决于运行时。
  • frameOptions: 'ALLOW-FROM' 已过时,现代浏览器请优先用 CSP 的 frame-ancestors(本包 CSP 配置未单独暴露该指令字段;若需要可考虑 customHeaders 或收紧其它策略)。
  • Nonce 每次请求重新生成;SSR 页面必须把当次响应的 X-Nonce 注入到 HTML,不能写死。

相关链接