GitHub Security Advisory: GHSA-cxj8-ggf2-p57c
Package: signalk-server (NPM)
Severity: MODERATE (CVSS 6.1)
CWE: CWE-346: Origin Validation Error, CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
Vulnerable versions: >= 2.20.0, < 2.24.0
Patched in: 2.24.0

Summary: Signal K Server: OAuth Authorization Code Theft via Unvalidated Host Header in OIDC Flow

Details:
## Summary

SignalK Server contains a code-level vulnerability in its OIDC login and logout handlers where the unvalidated HTTP Host header is used to construct the OAuth2 redirect_uri. Because the redirectUri configuration is silently unset by default, an **attacker spoof the Host header** to steal OAuth authorization codes and hijack user sessions in realistic deployments as The OIDC provider will then send the authorization code to whatever domain was injected.

_The OIDC specification requires redirect_uri to be pre-registered and not derived from untrusted input. Constructing it from the Host header violates this requirement and introduces a trust boundary break._
This risk is actively amplified by SignalK's official documentation, which instructs administrators to deploy an Nginx configuration that forwards the vulnerable Host header, exposing production environments.

## Vulnerability Root Cause

Two factors combine to create this vulnerability:

**Factor 1: redirectUri is optional with an unsafe fallback**
In types.ts:30, redirectUri is declared as optional
```
export interface OIDCConfig {
  // ...
  redirectUri?: string   // ← Optional, no default value
  // ...
}
```

The defaults in types.ts:175-185 do not include a redirectUri: never checks or warns about a missing redirectUri. This means a fully "valid" OIDC configuration can exist without redirectUri, silently activating the vulnerable fallback path.
```
export const OIDC_DEFAULTS: Omit<OIDCConfig, 'issuer' | 'clientId' | 'clientSecret'> = {
  enabled: false,
  scope: 'openid email profile',
  defaultPermission: 'readonly',
  autoCreateUsers: true,
  providerName: 'SSO Login',
  autoLogin: false
  // ← No redirectUri default
}
```

**Factor 2: Unsafe Host header usage in two locations**
Location 1 — Login handler in oidc-auth.ts:278-282:
```
const protocol = req.secure ? 'https' : 'http'
const host = req.get('host')                          // ← Attacker-controlled
const redirectUri =
  oidcConfig.redirectUri ||                            // ← Only safe if explicitly set
  `${protocol}://${host}${skAuthPrefix}/oidc/callback` // ← Uses attacker's Host

```
This redirectUri flows into createAuthState() → buildAuthorizationUrl() → OIDC provider's redirect_uri parameter. The OIDC provider will then send the authorization code to whatever domain was injected.

Location 2 — Logout handler in oidc-auth.ts:513-515:
```
const protocol = req.secure ? 'https' : 'http'
const host = req.get('host')                            // ← Same pattern
const fullPostLogoutUri = `${protocol}://${host}${postLogoutRedirect}`
```
This constructs the post_logout_redirect_uri sent to the OIDC provider's end_session_endpoint, allowing an attacker to redirect the user to an attacker controlled domain after logout.

### Official Documentation Enables the Attack

SignalK's own security documentation at docs/security.md:222-228 provides the recommended nginx reverse proxy configuration:
The proxy_set_header Host $h