diff --git a/index.html b/index.html index 485c45c..ab3c809 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + Catalyst diff --git a/server/server.js b/server/server.js index b14ab0f..5f83e90 100644 --- a/server/server.js +++ b/server/server.js @@ -11,10 +11,18 @@ export const app = express(); app.use(helmet({ contentSecurityPolicy: { + useDefaults: false, // explicit — upgrade-insecure-requests breaks HTTP deployments directives: { - ...helmet.contentSecurityPolicy.getDefaultDirectives(), - 'style-src': ["'self'", 'https://fonts.googleapis.com'], - 'font-src': ["'self'", 'https://fonts.gstatic.com'], + 'default-src': ["'self'"], + 'base-uri': ["'self'"], + 'font-src': ["'self'", 'https://fonts.gstatic.com'], + 'form-action': ["'self'"], + 'frame-ancestors': ["'self'"], + 'img-src': ["'self'", 'data:'], + 'object-src': ["'none'"], + 'script-src': ["'self'"], + 'script-src-attr': ["'unsafe-inline'"], // allow onclick handlers + 'style-src': ["'self'", 'https://fonts.googleapis.com'], }, }, })); diff --git a/tests/api.test.js b/tests/api.test.js index 79deb87..38138ab 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -237,3 +237,43 @@ describe('DELETE /api/instances/:vmid', () => { expect(res.status).toBe(400) }) }) + +// ── Static assets & SPA routing ─────────────────────────────────────────────── + +describe('static assets and SPA routing', () => { + it('serves index.html at root', async () => { + const res = await request(app).get('/') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch(/html/) + }) + + it('serves index.html for deep SPA routes (e.g. /instance/117)', async () => { + const res = await request(app).get('/instance/117') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch(/html/) + }) + + it('serves CSS with correct content-type (not sniffed as HTML)', async () => { + const res = await request(app).get('/css/app.css') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch(/text\/css/) + }) + + it('does not set upgrade-insecure-requests in CSP (HTTP deployments must work)', async () => { + const res = await request(app).get('/') + const csp = res.headers['content-security-policy'] ?? '' + expect(csp).not.toContain('upgrade-insecure-requests') + }) + + it('allows inline event handlers in CSP (onclick attributes)', async () => { + const res = await request(app).get('/') + const csp = res.headers['content-security-policy'] ?? '' + // script-src-attr must not be 'none' — that blocks onclick handlers + expect(csp).not.toContain("script-src-attr 'none'") + }) + + it('index.html contains base href / for correct asset resolution on deep routes', async () => { + const res = await request(app).get('/') + expect(res.text).toContain('') + }) +})