Skip to content

FIX: Stream Auth through protocol bug fixed#1463

Open
Atlas1225 wants to merge 1 commit intoiii-hq:mainfrom
Atlas1225:slu/streamAuth
Open

FIX: Stream Auth through protocol bug fixed#1463
Atlas1225 wants to merge 1 commit intoiii-hq:mainfrom
Atlas1225:slu/streamAuth

Conversation

@Atlas1225
Copy link
Copy Markdown
Contributor

@Atlas1225 Atlas1225 commented Apr 11, 2026

Per RFC 6455, if a WebSocket client sends a Sec-WebSocket-Protocol header during the handshake, the server must include a matching Sec-WebSocket-Protocol in its response — or the browser will immediately close the connection with code 1006.

The iii-stream worker was not echoing back the requested subprotocol, which made it impossible for browser clients to use the Sec-WebSocket-Protocol header as an authentication transport. The connection was silently dropped on every attempt.

FIX:
Based on your documentation the motia client in front should pass the token in websocket with this format
HEADER: Sec-WebSocket-Protocol: Authorization, <jwt>.

The server must echo: HEADER Sec-WebSocket-Protocol: Authorization. Without that, the ws is terminated automatically.

The server extract the token from the header value in the authenticateStream function for verification.

Summary by CodeRabbit

  • Bug Fixes
    • Improved WebSocket protocol negotiation to properly handle authorization protocol requests during connection handshakes.

…d not terminate the stream automatically (by return the Authorization protocol)
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 11, 2026

Someone is attempting to deploy a commit to the motia Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Updated the WebSocket handler to conditionally request the "Authorization" subprotocol during handshake. The implementation checks the sec-websocket-protocol header for the presence of "Authorization" and wraps the upgrade response accordingly when detected.

Changes

Cohort / File(s) Summary
WebSocket Subprotocol Handling
engine/src/workers/stream/stream.rs
Added conditional logic to parse incoming sec-websocket-protocol header and wrap WebSocketUpgrade with ws.protocols(["Authorization"]) if the header contains "Authorization"; otherwise preserves existing behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested reviewers

  • sergiofilhowz

Poem

🐰 Through WebSocket gates I hop with glee,
Parsing protocols, one-two-three!
Authorization dances in the handshake thread,
Conditional wraps where headers are read. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: fixing a bug in Stream authentication through the WebSocket protocol subprotocol handling.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@engine/src/workers/stream/stream.rs`:
- Around line 77-81: The code that sets has_authorization_protocol currently
uses headers.get(...) which only returns the first Sec-WebSocket-Protocol header
instance; change the logic to iterate over
headers.get_all("sec-websocket-protocol"), for each header value try
to_str().ok(), split by ',' and trim each token, and set
has_authorization_protocol = true if any token equals "Authorization"
(short-circuiting once found). Update the usage around the
has_authorization_protocol variable to rely on this aggregated check so repeated
header instances or comma-separated lists are both handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 75443384-1c52-4a49-958a-228ef7a00a3f

📥 Commits

Reviewing files that changed from the base of the PR and between 3582c94 and 3177d0c.

📒 Files selected for processing (1)
  • engine/src/workers/stream/stream.rs

Comment on lines +77 to +81
let has_authorization_protocol = headers
.get("sec-websocket-protocol")
.and_then(|v| v.to_str().ok())
.map(|s| s.split(',').any(|p| p.trim() == "Authorization"))
.unwrap_or(false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and the specific lines mentioned
head -n 100 engine/src/workers/stream/stream.rs | tail -n 50

Repository: iii-hq/iii

Length of output: 1505


🏁 Script executed:

# Check the full imports to understand what HeaderMap is being used
head -n 30 engine/src/workers/stream/stream.rs

Repository: iii-hq/iii

Length of output: 949


🏁 Script executed:

# Look at the context around lines 77-81 more carefully
sed -n '70,95p' engine/src/workers/stream/stream.rs

Repository: iii-hq/iii

Length of output: 890


🏁 Script executed:

# Search for HeaderMap imports and types
rg "use.*HeaderMap\|use.*headers" engine/src/workers/stream/stream.rs

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Check if there are any tests for this specific logic
rg -A 10 "sec-websocket-protocol\|SecWebSocketProtocol" engine/src/workers/stream/

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Check if axum's HeaderMap has get_all method and what it supports
rg "HeaderMap" engine/src/workers/stream/stream.rs -A 5 -B 5

Repository: iii-hq/iii

Length of output: 559


🏁 Script executed:

# Look for Cargo.toml to see axum version
fd Cargo.toml engine/ | head -5 | xargs head -n 50

Repository: iii-hq/iii

Length of output: 1106


🏁 Script executed:

web_search
axum http HeaderMap get_all method API

Repository: iii-hq/iii

Length of output: 147


🏁 Script executed:

web_search
HTTP Sec-WebSocket-Protocol header multiple instances RFC 6455

Repository: iii-hq/iii

Length of output: 147


🏁 Script executed:

# Check if there are any SDK or documentation files that document this protocol behavior
find . -type f \( -name "*.md" -o -name "*.rs" \) | xargs rg -l "Sec-WebSocket-Protocol|sec-websocket-protocol" | grep -E "sdk/|docs/" | head -10

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Check the actual axum version in dependencies
rg "^axum" engine/Cargo.toml

Repository: iii-hq/iii

Length of output: 109


🌐 Web query:

axum 0.8 HeaderMap get_all method documentation

💡 Result:

In axum 0.8, HeaderMap is re-exported from the http crate (version 1.0+). The get_all method returns a view over all values associated with a given header key without allocations. Signature: pub fn get_all(&self, key: K) -> GetAll<'_, T> where K: AsHeaderName Description: Returns a view of all values associated with a key. The returned GetAll allows iterating the values in insertion order. Returns None if no values are associated with the key. Example: let mut map = HeaderMap::new; map.insert(HOST, "hello".parse.unwrap); map.append(HOST, "goodbye".parse.unwrap); let view = map.get_all("host"); let mut iter = view.iter; assert_eq!(&"hello", iter.next.unwrap); assert_eq!(&"goodbye", iter.next.unwrap); assert!(iter.next.is_none); GetAll provides iter which returns ValueIter<'a, T>, an iterator over &'a T in insertion order. This API is unchanged from previous versions and matches the standard http::header::HeaderMap documentation.

Citations:


🌐 Web query:

HTTP RFC 6455 Sec-WebSocket-Protocol header multiple values repeated header

💡 Result:

In RFC 6455 (The WebSocket Protocol), the Sec-WebSocket-Protocol header handles multiple subprotocol values differently for client requests and server responses. For client requests (Sec-WebSocket-Protocol-Client): The ABNF is 1#token, allowing multiple values either as a comma-separated list in a single header (e.g., Sec-WebSocket-Protocol: chat, superchat) or as repeated headers (e.g., Sec-WebSocket-Protocol: chat Sec-WebSocket-Protocol: superchat). This lists subprotocols in order of preference. The server selects at most one from this list. For server responses (Sec-WebSocket-Protocol-Server): The ABNF is token (a single token), so only one value is permitted—exactly one subprotocol selected from the client's list, sent in a single header instance (e.g., Sec-WebSocket-Protocol: chat). Sending multiple values or repeated headers is invalid and causes handshake failure, as confirmed by implementations and docs stating "the server can't send more than one Sec-WebSocket-Protocol header" and "server must return exactly one value". This contrasts with Sec-WebSocket-Extensions, which explicitly allows multiple instances or comma lists for extensions. HTTP/1.1 semantics support repeated headers for list-valued fields, but WebSocket restricts the server response to a single token. No updates in later RFCs (e.g., 7936, 8441) change this behavior.

Citations:


🏁 Script executed:

# Check the dependencies to see what http crate version is used
rg "^http\s*=" engine/Cargo.toml

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Look for any existing patterns in the codebase of handling multiple headers
rg "get_all\|iter\(\)" engine/src/ | grep -i header | head -20

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Check for cargo fmt compliance by looking at the file formatting
head -n 90 engine/src/workers/stream/stream.rs | tail -n 20

Repository: iii-hq/iii

Length of output: 642


🏁 Script executed:

# Verify cargo fmt compliance - check if file passes formatting
cd engine && cargo fmt --check -- src/workers/stream/stream.rs 2>&1 || true

Repository: iii-hq/iii

Length of output: 300


🏁 Script executed:

# Search more broadly for SDK changes or documentation that might reference WebSocket protocol handling
find . -type f -name "*.rs" -path "*/sdk/*" | xargs rg -l "protocol\|subprotocol" 2>/dev/null | head -5

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Check if there are framework-level references to WebSocket subprotocols
find . -type f \( -name "*.md" -o -name "*.rs" \) -path "*/frameworks/*" | xargs rg -l "WebSocket\|subprotocol" 2>/dev/null | head -5

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Check if there are any WebSocket-related tests or integration tests that might show expected behavior
find . -type f -name "*.rs" | xargs rg -l "WebSocketUpgrade\|ws_handler\|protocols" | grep test | head -5

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Look for any protocol-related documentation or comments in the codebase
rg "Authorization.*protocol\|protocol.*Authorization" engine/src/ -B 2 -A 2

Repository: iii-hq/iii

Length of output: 36


Handle all Sec-WebSocket-Protocol header instances, not just one.

RFC 6455 allows clients to send Sec-WebSocket-Protocol as either comma-separated values or repeated header instances. The current code using headers.get() retrieves only the first header value and will miss the Authorization protocol if it appears in a subsequent repeated header, causing protocol negotiation to fail.

Use headers.get_all() to iterate over all header instances:

Proposed fix
-    let has_authorization_protocol = headers
-        .get("sec-websocket-protocol")
-        .and_then(|v| v.to_str().ok())
-        .map(|s| s.split(',').any(|p| p.trim() == "Authorization"))
-        .unwrap_or(false);
+    let has_authorization_protocol = headers
+        .get_all("sec-websocket-protocol")
+        .iter()
+        .filter_map(|v| v.to_str().ok())
+        .flat_map(|s| s.split(','))
+        .any(|p| p.trim() == "Authorization");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let has_authorization_protocol = headers
.get("sec-websocket-protocol")
.and_then(|v| v.to_str().ok())
.map(|s| s.split(',').any(|p| p.trim() == "Authorization"))
.unwrap_or(false);
let has_authorization_protocol = headers
.get_all("sec-websocket-protocol")
.iter()
.filter_map(|v| v.to_str().ok())
.flat_map(|s| s.split(','))
.any(|p| p.trim() == "Authorization");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/src/workers/stream/stream.rs` around lines 77 - 81, The code that sets
has_authorization_protocol currently uses headers.get(...) which only returns
the first Sec-WebSocket-Protocol header instance; change the logic to iterate
over headers.get_all("sec-websocket-protocol"), for each header value try
to_str().ok(), split by ',' and trim each token, and set
has_authorization_protocol = true if any token equals "Authorization"
(short-circuiting once found). Update the usage around the
has_authorization_protocol variable to rely on this aggregated check so repeated
header instances or comma-separated lists are both handled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant