Files
hermes-web-ui/tests/server/security-policy.test.ts
2026-06-05 10:42:23 +08:00

75 lines
3.1 KiB
TypeScript

import http from 'http'
import Koa from 'koa'
import { afterEach, describe, expect, it } from 'vitest'
import {
createCorsOriginResolver,
isOriginAllowed,
securityHeaders,
shouldRejectUpgradeOrigin,
} from '../../packages/server/src/security'
function fakeCtx(origin: string, host: string) {
return {
host,
get(name: string) {
return name.toLowerCase() === 'origin' ? origin : ''
},
} as any
}
describe('server security policy', () => {
let servers: http.Server[] = []
afterEach(async () => {
const closing = servers.map(server => new Promise<void>((resolve) => server.close(() => resolve())))
servers = []
await Promise.all(closing)
})
it('allows same-host browser origins without enabling wildcard CORS', async () => {
const resolveOrigin = createCorsOriginResolver('')
await expect(resolveOrigin(fakeCtx('http://127.0.0.1:8648', '127.0.0.1:8648'))).resolves.toBe('http://127.0.0.1:8648')
await expect(resolveOrigin(fakeCtx('https://evil.example', '127.0.0.1:8648'))).resolves.toBe('')
})
it('allows configured origins and explicit wildcard opt-in only', () => {
expect(isOriginAllowed('https://app.example', '127.0.0.1:8648', 'https://app.example')).toBe(true)
expect(isOriginAllowed('https://evil.example', '127.0.0.1:8648', 'https://app.example')).toBe(false)
expect(isOriginAllowed('null', '127.0.0.1:8648', '')).toBe(false)
expect(isOriginAllowed('null', '127.0.0.1:8648', '*')).toBe(true)
expect(isOriginAllowed('https://evil.example', '127.0.0.1:8648', '*')).toBe(true)
})
it('rejects disallowed browser websocket upgrade origins', () => {
expect(shouldRejectUpgradeOrigin({ headers: { origin: 'https://evil.example', host: '127.0.0.1:8648' } } as any, '')).toBe(true)
expect(shouldRejectUpgradeOrigin({ headers: { origin: 'null', host: '127.0.0.1:8648' } } as any, '')).toBe(true)
expect(shouldRejectUpgradeOrigin({ headers: { origin: 'http://127.0.0.1:8648', host: '127.0.0.1:8648' } } as any, '')).toBe(false)
expect(shouldRejectUpgradeOrigin({ headers: { host: '127.0.0.1:8648' } } as any, '')).toBe(false)
})
it('adds baseline browser security headers', async () => {
const app = new Koa()
app.use(securityHeaders())
app.use((ctx) => {
ctx.body = { ok: true }
})
const server = app.listen(0)
servers.push(server)
await new Promise<void>((resolve) => server.once('listening', () => resolve()))
const address = server.address()
if (!address || typeof address === 'string') throw new Error('expected tcp server')
const response = await fetch(`http://127.0.0.1:${address.port}/health`, {
headers: { 'x-forwarded-proto': 'https' },
})
expect(response.headers.get('x-content-type-options')).toBe('nosniff')
expect(response.headers.get('x-frame-options')).toBe('DENY')
expect(response.headers.get('referrer-policy')).toBe('no-referrer')
expect(response.headers.get('cross-origin-opener-policy')).toBe('same-origin-allow-popups')
expect(response.headers.get('content-security-policy')).toContain("default-src 'self'")
expect(response.headers.get('strict-transport-security')).toContain('max-age=31536000')
})
})