joewalnes/websocketd · error

same origin policy violated

Error message

same origin policy violated

What it means

checkOrigin enforces cross-origin WebSocket security. When the request's Origin header parses to a host/port different from the server's own host/port (and no --origin list is consulted first to save it), the upgrade is rejected with this error and gorilla/websocket answers the handshake with HTTP 403. It exists to prevent other websites' JavaScript from driving your websocketd scripts with the user's credentials.

Solutions

  1. Add the frontend's exact origin to --origin, e.g. --origin=http://app.example.com:8080 (repeatable or comma-separated per matchOrigin semantics).
  2. Serve the client page from the same host and port as websocketd so the same-origin check passes automatically.
  3. If behind a proxy, ensure the Host header forwarded to websocketd matches the origin your page uses, or disable the proxy's host rewriting.
  4. For non-browser clients (curl, wscat) the Origin header is irrelevant or absent — this only blocks browser handshakes that send a mismatching Origin.

Example fix

// before
websocketd --port=8080 ./chat.sh
// after
websocketd --port=8080 --origin=http://localhost:3000 ./chat.sh
Defensive patterns

Strategy: validation

Validate before calling

const url = new URL('ws://localhost:8080/echo');
if (url.host !== location.host) throw new Error(`origin ${url.host} != server ${location.host}; pass --origin=${location.origin}`);

Try / catch

try {
  const ws = new WebSocket('ws://localhost:8080/echo');
  ws.onerror = () => console.error('handshake rejected (likely 403 origin mismatch)');
} catch (e) { console.error('ws setup failed', e); }

Prevention

When it happens

Trigger: A browser page served from http://app.example.com:8080 opens a WebSocket to ws://otherhost:9999/ while the server runs without --origin; or the page uses localhost vs 127.0.0.1 mismatch; or a reverse proxy rewrites the Host so the server compares it against a different origin than the browser sent.

Common situations: Frontend on a different port than the dev server; serving the HTML from a CDN/static host while websocketd runs elsewhere; forgetting the --origin flag when splitting frontend and backend; tests (TestCheckOrigin) exercising mismatched host/port pairs.

Related errors


AI-assisted analysis of joewalnes/websocketd@7a8683dc7f (2026-09-03). Data as JSON: /api/errors/182795634ba8d884. Report an issue: GitHub.

Appendix: source

Thrown at libwebsocketd/http.go:379

	}

	log.Associate("origin", originParsed.String())

	if config.SameOrigin || config.AllowOrigins != nil {
		originServer, originPort, err := tellHostPort(originParsed.Host, originParsed.Scheme == "https")
		if err != nil {
			log.Access("session", "Origin hostname parsing error: %s", err)
			return err
		}
		if config.SameOrigin {
			localServer, localPort, err := tellHostPort(req.Host, req.TLS != nil)
			if err != nil {
				log.Access("session", "Request hostname parsing error: %s", err)
				return err
			}
			if originServer != localServer || originPort != localPort {
				log.Access("session", "Same origin policy mismatch")
				return fmt.Errorf("same origin policy violated")
			}
		}
		if config.AllowOrigins != nil {
			if !matchOrigin(originServer, originPort, originParsed.Scheme, config.AllowOrigins) {
				log.Access("session", "Origin is not listed in allowed list")
				return fmt.Errorf("origin list matches were not found")
			}
		}
	}
	return nil
}

// matchOrigin checks if the given origin server/port/scheme matches any entry
// in the allowed origins list. Extracted for testability.
//
// Port semantics (issue #473): an entry with an explicit port matches that
// port only. A portless entry matches only the scheme's default port (80 for
// http, 443 for https — both, if the entry carries no scheme). Appending

View on GitHub (pinned to 7a8683dc7f)