feat(server): add security hardening for public hosting
- Add CSP, frame and referrer headers for served static files - Enforce WebSocket origin check to prevent cross-site hijacking - Trust X-Forwarded-For only when the peer is from a private proxy network - Limit concurrent connections (500 total / 20 per IP) and rooms (300) - Add ALLOWED_ORIGINS env var for additional WebSocket origins - Document reverse proxy setup (NPM/NPMplus) in README - Add scripts/security-test.mjs to verify origin and limit behavior
This commit is contained in:
parent
b506ffad55
commit
4ec0e483aa
3 changed files with 184 additions and 0 deletions
64
scripts/security-test.mjs
Normal file
64
scripts/security-test.mjs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { request } from 'node:http'
|
||||
|
||||
const PORT = 3101
|
||||
|
||||
function wsUpgrade(origin) {
|
||||
return new Promise((resolve) => {
|
||||
const req = request({
|
||||
host: 'localhost',
|
||||
port: PORT,
|
||||
path: '/',
|
||||
headers: {
|
||||
Connection: 'Upgrade',
|
||||
Upgrade: 'websocket',
|
||||
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
...(origin ? { Origin: origin } : {}),
|
||||
},
|
||||
})
|
||||
req.on('response', (res) => resolve({ status: res.statusCode }))
|
||||
req.on('error', () => resolve({ status: 'destroyed' }))
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
const { WebSocket } = await import('ws')
|
||||
|
||||
function wsOpen(origin, sendCreate) {
|
||||
return new Promise((resolve) => {
|
||||
const ws = new WebSocket(`ws://localhost:${PORT}`, origin ? { origin } : undefined)
|
||||
const done = (ok, info) => {
|
||||
try { ws.close() } catch {}
|
||||
resolve({ ok, info })
|
||||
}
|
||||
ws.on('open', () => {
|
||||
if (sendCreate) ws.send(JSON.stringify({ t: 'create', mode: 'coop', name: 'Test' }))
|
||||
else done(true, 'connected')
|
||||
})
|
||||
ws.on('message', (d) => {
|
||||
const m = JSON.parse(d.toString())
|
||||
if (m.t === 'room') done(true, 'room ' + m.code)
|
||||
if (m.t === 'error') done(false, m.msg)
|
||||
})
|
||||
ws.on('error', () => done(false, 'error'))
|
||||
setTimeout(() => resolve({ ok: false, info: 'timeout' }), 3000)
|
||||
})
|
||||
}
|
||||
|
||||
// 1) böser Origin → 403
|
||||
const evil = await wsUpgrade('https://boese-seite.example.com')
|
||||
console.log('1) Origin-Check böser Origin :', evil.status === 403 ? 'OK (403)' : `FEHLER (${evil.status})`)
|
||||
|
||||
// 2) same-origin → erlaubt
|
||||
const same = await wsOpen('http://localhost:' + PORT, false)
|
||||
console.log('2) Same-Origin :', same.ok ? 'OK' : 'FEHLER ' + same.info)
|
||||
|
||||
// 3) dev-origin (localhost:5173) → erlaubt
|
||||
const dev = await wsOpen('http://localhost:5173', false)
|
||||
console.log('3) Dev-Origin (:5173) :', dev.ok ? 'OK' : 'FEHLER ' + dev.info)
|
||||
|
||||
// 4) ohne Origin + Raum erstellen
|
||||
const plain = await wsOpen(undefined, true)
|
||||
console.log('4) Non-Browser + Raum :', plain.ok ? 'OK (' + plain.info + ')' : 'FEHLER ' + plain.info)
|
||||
|
||||
process.exit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue