Просмотр исходного кода

fix: 允许白名单域名实时连接 CSP

samien 2 месяцев назад
Родитель
Сommit
b7ff8ff096
3 измененных файлов с 13 добавлено и 3 удалено
  1. 1 1
      src/server/app.ts
  2. 3 2
      src/server/security.ts
  3. 9 0
      tests/server/security.test.ts

+ 1 - 1
src/server/app.ts

@@ -106,7 +106,7 @@ export function createApplication(options: ApplicationOptions = {}): Application
     }
   })
   app.use(async (ctx, next) => {
-    applySecurityHeaders(ctx, Boolean(config.tlsCert))
+    applySecurityHeaders(ctx, Boolean(config.tlsCert), config.allowedHosts)
     if (!isAllowedHostHeader(ctx.get('host'), config)) {
       throw new HttpError(421, 'Request Host is not allowed', 'invalid_host')
     }

+ 3 - 2
src/server/security.ts

@@ -169,8 +169,9 @@ export function requestAccountKey(request: IncomingMessage): string {
   return accountKeyFromCookieHeader(request.headers.cookie, request.socket.remoteAddress)
 }
 
-export function applySecurityHeaders(ctx: Koa.Context, tls: boolean): void {
+export function applySecurityHeaders(ctx: Koa.Context, tls: boolean, allowedHosts: ReadonlySet<string> = new Set()): void {
   const socketOrigin = `${tls ? 'wss' : 'ws'}://${ctx.host}`
+  const proxySocketOrigins = [...allowedHosts].flatMap(host => [`ws://${host}`, `wss://${host}`])
   ctx.set('Content-Security-Policy', [
     "default-src 'self'",
     "base-uri 'none'",
@@ -181,7 +182,7 @@ export function applySecurityHeaders(ctx: Koa.Context, tls: boolean): void {
     "style-src 'self' 'unsafe-inline'",
     "img-src 'self' data: blob:",
     "media-src 'self' blob:",
-    `connect-src 'self' ${socketOrigin}`,
+    `connect-src 'self' ${socketOrigin} ${proxySocketOrigins.join(' ')}`.trim(),
   ].join('; '))
   const requestHostname = splitHostPort(ctx.host)?.hostname ?? ''
   if (tls || isLoopbackHost(requestHostname)) {

+ 9 - 0
tests/server/security.test.ts

@@ -6,6 +6,7 @@ import {
   CsrfProtection,
   isAllowedHostHeader,
   isExactOrigin,
+  applySecurityHeaders,
 } from '../../src/server/security.js'
 
 function config(overrides: Partial<ServerConfig> = {}): ServerConfig {
@@ -55,6 +56,14 @@ describe('server security boundary', () => {
     expect(isExactOrigin('http://attacker.example', '10.1.5.100', false, allowed)).toBe(false)
   })
 
+  it('adds configured reverse-proxy domains to the WebSocket CSP', () => {
+    const headers: Record<string, string> = {}
+    const ctx = { host: '10.1.5.100', set(name: string, value: string) { headers[name] = value } } as unknown as Koa.Context
+    applySecurityHeaders(ctx, false, new Set(['yaoyao-lc.samien.cn']))
+    expect(headers['Content-Security-Policy']).toContain('ws://yaoyao-lc.samien.cn')
+    expect(headers['Content-Security-Policy']).toContain('wss://yaoyao-lc.samien.cn')
+  })
+
   it('requires an exact scheme and host Origin', () => {
     expect(isExactOrigin('http://127.0.0.1:8800', '127.0.0.1:8800', false)).toBe(true)
     expect(isExactOrigin('https://127.0.0.1:8800', '127.0.0.1:8800', false)).toBe(true)