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
72
tests/server/helpers/createMockEvent.js
Normal file
72
tests/server/helpers/createMockEvent.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { IncomingMessage, ServerResponse } from 'node:http'
|
||||
import { Socket } from 'node:net'
|
||||
import { createEvent } from 'h3'
|
||||
|
||||
/**
|
||||
* Create a real h3 event backed by real Node.js request/response objects.
|
||||
*
|
||||
* Options:
|
||||
* method - HTTP method (default 'GET')
|
||||
* path - Request path (default '/')
|
||||
* headers - Object of request headers
|
||||
* cookies - Object of cookie key/value pairs (serialized to cookie header)
|
||||
* body - Request body (will be JSON-serialized)
|
||||
* remoteAddress - Client IP (default '127.0.0.1')
|
||||
*/
|
||||
export function createMockEvent(options = {}) {
|
||||
const {
|
||||
method = 'GET',
|
||||
path = '/',
|
||||
headers = {},
|
||||
cookies = {},
|
||||
body = undefined,
|
||||
remoteAddress = '127.0.0.1'
|
||||
} = options
|
||||
|
||||
// Build cookie header from cookies object
|
||||
const cookiePairs = Object.entries(cookies).map(([k, v]) => `${k}=${v}`)
|
||||
if (cookiePairs.length > 0) {
|
||||
headers.cookie = [headers.cookie, cookiePairs.join('; ')].filter(Boolean).join('; ')
|
||||
}
|
||||
|
||||
// Build a real IncomingMessage
|
||||
const socket = new Socket()
|
||||
// remoteAddress is a getter-only property on Socket, so use defineProperty
|
||||
Object.defineProperty(socket, 'remoteAddress', {
|
||||
value: remoteAddress,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
const req = new IncomingMessage(socket)
|
||||
req.method = method
|
||||
req.url = path
|
||||
req.headers = Object.fromEntries(
|
||||
Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])
|
||||
)
|
||||
|
||||
// If body is provided, push it into the request stream so readBody can consume it
|
||||
if (body !== undefined) {
|
||||
const bodyStr = typeof body === 'string' ? body : JSON.stringify(body)
|
||||
req.headers['content-type'] = req.headers['content-type'] || 'application/json'
|
||||
req.headers['content-length'] = Buffer.byteLength(bodyStr).toString()
|
||||
req.push(bodyStr)
|
||||
req.push(null)
|
||||
}
|
||||
|
||||
const res = new ServerResponse(req)
|
||||
|
||||
// Capture response headers for test assertions
|
||||
const setHeaders = {}
|
||||
const originalSetHeader = res.setHeader.bind(res)
|
||||
res.setHeader = (name, value) => {
|
||||
setHeaders[name.toLowerCase()] = value
|
||||
return originalSetHeader(name, value)
|
||||
}
|
||||
|
||||
const event = createEvent(req, res)
|
||||
|
||||
// Attach captured headers for test access
|
||||
event._testSetHeaders = setHeaders
|
||||
|
||||
return event
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue