diff --git a/.changeset/fix-csp-ws-http-scheme-mapping.md b/.changeset/fix-csp-ws-http-scheme-mapping.md new file mode 100644 index 00000000..6a41f3bc --- /dev/null +++ b/.changeset/fix-csp-ws-http-scheme-mapping.md @@ -0,0 +1,14 @@ +--- +"nostream": patch +--- + +Fix the Content-Security-Policy `connect-src` directive for relays served over plain `ws://`. + +The web app factory derived an HTTP(S) origin from the relay's WebSocket URL but mapped +`ws:` to the invalid scheme `':'`, which the WHATWG URL API silently ignores. As a result the +`connect-src` directive kept a `ws://…` entry instead of the intended `http://…` origin for +local/dev, Tor, or reverse-proxied setups. The `ws:` protocol now correctly maps to `http:`. + +Adds regression test coverage for the protocol mapping (`getWebProtocolForRelay`, extracted from +`createWebApp` so it can be unit tested directly), since this file previously had no test coverage +at all. diff --git a/src/factories/web-app-factory.ts b/src/factories/web-app-factory.ts index 88112ab5..39b25621 100644 --- a/src/factories/web-app-factory.ts +++ b/src/factories/web-app-factory.ts @@ -5,6 +5,10 @@ import { createSettings } from './settings-factory' import router from '../routes' import { getGrafanaFrameOrigin } from '../utils/admin-grafana' +// Extracted so the ws:/wss: -> http:/https: mapping can be unit tested directly, +// without needing to exercise the full Express middleware stack. +export const getWebProtocolForRelay = (relayProtocol: string): string => (relayProtocol === 'wss:' ? 'https:' : 'http:') + export const createWebApp = (): Express => { const app = express() app @@ -16,7 +20,7 @@ export const createWebApp = (): Express => { const relayUrl = new URL(settings.info.relay_url) const webRelayUrl = new URL(relayUrl.toString()) - webRelayUrl.protocol = relayUrl.protocol === 'wss:' ? 'https:' : 'http:' + webRelayUrl.protocol = getWebProtocolForRelay(relayUrl.protocol) const directives = { 'img-src': ["'self'", 'data:', 'https://cdn.zebedee.io/an/nostr/'], diff --git a/test/unit/factories/web-app-factory.spec.ts b/test/unit/factories/web-app-factory.spec.ts new file mode 100644 index 00000000..c3f7e274 --- /dev/null +++ b/test/unit/factories/web-app-factory.spec.ts @@ -0,0 +1,13 @@ +import { expect } from 'chai' + +import { getWebProtocolForRelay } from '../../../src/factories/web-app-factory' + +describe('getWebProtocolForRelay', () => { + it('maps wss: to https:', () => { + expect(getWebProtocolForRelay('wss:')).to.equal('https:') + }) + + it('maps ws: to http: (regression: previously mapped to the invalid scheme ":")', () => { + expect(getWebProtocolForRelay('ws:')).to.equal('http:') + }) +})