Add Vitest security test suite and update security evaluation doc
Set up Vitest with server (node) and client (jsdom) test projects. 79 tests across 8 files verify all Phase 0-1 security controls: escapeHtml sanitization, DOMPurify markdown XSS prevention, CSRF enforcement, security headers, rate limiting, auth guards, profile field allowlist, and login anti-enumeration. Updated SECURITY_EVALUATION.md with remediation status, implementation summary, and automated test coverage details.
This commit is contained in:
parent
d5c95ace0a
commit
29c96a207e
14 changed files with 2454 additions and 3 deletions
106
tests/server/middleware/security-headers.test.js
Normal file
106
tests/server/middleware/security-headers.test.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||
import { createMockEvent } from '../helpers/createMockEvent.js'
|
||||
import securityHeadersMiddleware from '../../../server/middleware/02.security-headers.js'
|
||||
|
||||
describe('security-headers middleware', () => {
|
||||
const originalNodeEnv = process.env.NODE_ENV
|
||||
|
||||
afterEach(() => {
|
||||
process.env.NODE_ENV = originalNodeEnv
|
||||
})
|
||||
|
||||
describe('always-present headers', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NODE_ENV = 'development'
|
||||
})
|
||||
|
||||
it('sets X-Content-Type-Options to nosniff', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['x-content-type-options']).toBe('nosniff')
|
||||
})
|
||||
|
||||
it('sets X-Frame-Options to DENY', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['x-frame-options']).toBe('DENY')
|
||||
})
|
||||
|
||||
it('sets X-XSS-Protection to 0', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['x-xss-protection']).toBe('0')
|
||||
})
|
||||
|
||||
it('sets Referrer-Policy', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['referrer-policy']).toBe('strict-origin-when-cross-origin')
|
||||
})
|
||||
|
||||
it('sets Permissions-Policy', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['permissions-policy']).toBe('camera=(), microphone=(), geolocation=()')
|
||||
})
|
||||
})
|
||||
|
||||
describe('production-only headers', () => {
|
||||
it('sets HSTS in production', () => {
|
||||
process.env.NODE_ENV = 'production'
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['strict-transport-security']).toBe('max-age=31536000; includeSubDomains')
|
||||
})
|
||||
|
||||
it('does not set HSTS in development', () => {
|
||||
process.env.NODE_ENV = 'development'
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['strict-transport-security']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('sets CSP in production', () => {
|
||||
process.env.NODE_ENV = 'production'
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['content-security-policy']).toBeDefined()
|
||||
})
|
||||
|
||||
it('does not set CSP in development', () => {
|
||||
process.env.NODE_ENV = 'development'
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
expect(event._testSetHeaders['content-security-policy']).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CSP directives', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NODE_ENV = 'production'
|
||||
})
|
||||
|
||||
it('includes Helcim sources in CSP', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
const csp = event._testSetHeaders['content-security-policy']
|
||||
expect(csp).toContain('myposjs.helcim.com')
|
||||
expect(csp).toContain('api.helcim.com')
|
||||
expect(csp).toContain('secure.helcim.com')
|
||||
})
|
||||
|
||||
it('includes Cloudinary sources in CSP', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
const csp = event._testSetHeaders['content-security-policy']
|
||||
expect(csp).toContain('res.cloudinary.com')
|
||||
})
|
||||
|
||||
it('includes Plausible sources in CSP', () => {
|
||||
const event = createMockEvent({ path: '/' })
|
||||
securityHeadersMiddleware(event)
|
||||
const csp = event._testSetHeaders['content-security-policy']
|
||||
expect(csp).toContain('plausible.io')
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue