Skip to content
14 changes: 14 additions & 0 deletions .changeset/fix-csp-ws-http-scheme-mapping.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 5 additions & 1 deletion src/factories/web-app-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/'],
Expand Down
13 changes: 13 additions & 0 deletions test/unit/factories/web-app-factory.spec.ts
Original file line number Diff line number Diff line change
@@ -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:')
})
})
Loading