Skip to content

fix(llc): sanitize header string - #1289

Open
Brazol wants to merge 3 commits into
mainfrom
fix/sanitize-header-string
Open

fix(llc): sanitize header string#1289
Brazol wants to merge 3 commits into
mainfrom
fix/sanitize-header-string

Conversation

@Brazol

@Brazol Brazol commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #1283
Resolves FLU-627

Fixed a FormatException when sending requests if the application name (or other device/app info) contains non-ASCII characters. Values included in the X-Stream-Client header are now sanitized to valid header characters.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed request failures caused by non-ASCII or invalid characters in application and device information.
    • Improved WebSocket connection reliability by safely encoding client information in connection URLs.
    • Sanitized client metadata to produce valid header values.
  • Tests

    • Added coverage for metadata formatting, non-ASCII characters, and separator handling.

@Brazol
Brazol requested a review from a team as a code owner July 23, 2026 08:01
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR sanitizes X-Stream-Client metadata to printable US-ASCII, removes separator characters, tests the resulting header, and URL-encodes the header when constructing coordinator and SFU WebSocket URLs.

Changes

Client metadata transport

Layer / File(s) Summary
Sanitize client header metadata
packages/stream_video/lib/src/video_environment.dart, packages/stream_video/test/src/video_environment_test.dart, packages/stream_video/CHANGELOG.md
X-Stream-Client segments are composed from sanitized values, with tests for non-ASCII characters, separators, and complete metadata output.
Encode WebSocket query values
packages/stream_video/lib/src/coordinator/open_api/coordinator_ws.dart, packages/stream_video/lib/src/sfu/ws/sfu_ws.dart
Coordinator and SFU WebSocket URLs URL-encode the X-Stream-Client query parameter.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: renefloor, xsahil03x

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description mentions the fix, but it omits the template's Goal, Implementation details, Testing, and checklist sections. Add the required template sections: Goal, Implementation details, UI Changes if any, Testing, and the contributor/reviewer checklists.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main change: sanitizing the X-Stream-Client header string.
Linked Issues check ✅ Passed The code sanitizes X-Stream-Client values for the affected metadata and encodes the WebSocket query parameter, matching issue #1283.
Out of Scope Changes check ✅ Passed All file changes are directly related to fixing invalid X-Stream-Client values; no unrelated changes stand out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sanitize-header-string

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.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 11.22%. Comparing base (d0b3a62) to head (8abf416).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1289      +/-   ##
==========================================
+ Coverage   11.20%   11.22%   +0.02%     
==========================================
  Files         686      686              
  Lines       50350    50361      +11     
==========================================
+ Hits         5640     5652      +12     
+ Misses      44710    44709       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@renefloor renefloor left a comment

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.

🔴 Sanitizing to ASCII isn't enough — the value is also interpolated raw into WebSocket URLs

xStreamClientHeader is used unencoded as a query parameter in two places:

  • packages/stream_video/lib/src/sfu/ws/sfu_ws.dart:51
  • packages/stream_video/lib/src/coordinator/open_api/coordinator_ws.dart:37

The sanitizer keeps #, &, ?, % — all URL-significant. I verified the impact with Uri.parse on the real SFU URL shape:

appName "Tag #1" → query={X-Stream-Client: ...|app=Tag }
⚠️ attempt, cid, user_id, api_key, user_session_id ALL DROPPED
appName "Tom & Jerry" → query={X-Stream-Client: ...|app=Tom , " Jerry": "", attempt: 1}
⚠️ client header truncated + junk param injected

A # in the app name silently breaks the SFU handshake entirely. This is the same class of bug the PR is fixing (unusual app names), so it belongs in this PR rather than a follow-up. Preferred fix at the call sites:

'&X-Stream-Client=${Uri.encodeQueryComponent(xStreamClientHeader)}'

That also fixes the pre-existing unencoded space in os=android 16. If you'd rather keep the change local to the sanitizer, restrict the allowlist instead of using a broad printable range — but encoding at the URL boundary is the correct layer.

🟡 CHANGELOG heading should be ## Upcoming

The repo convention is ## Upcoming (see 0849531, 32a1181, 2b87908); release commits rewrite that heading to the version number. ## Unreleased will be missed by that flow.

🟡 Empty-after-sanitization values leave malformed segments

Nothing handles a value that sanitizes to '':

  • App named e.g. 日本語アプリ → app= (empty segment, no signal, still sent)
  • osVersion sanitizing to empty → 'os=${name} ' with a trailing space, since the os= branch interpolates unconditionally and .trim() is applied per-value, not to the segment

Suggest treating empty-after-sanitize as absent:

String? _sanitized(String? value) {
if (value == null) return null;
final result = /* filter */ .trim();
return result.isEmpty ? null : result;
}
…then the existing if (x case final v?) patterns handle omission naturally, and the os switch can key on the sanitized version.

🔵 Minor

  • Per-call cost / repetition: the getter is recomputed on every HTTP request and WS connect, re-scanning up to 5 strings each time, and _sanitizeHeaderValue appears at 6 call sites. Sanitizing once — in VideoEnvironmentCollector._collect() or the VideoEnvironment constructor — would be cheaper, remove the repetition, and benefit non-header consumers too. Negligible perf impact either way; mostly a readability win.
  • codeUnits vs runes: correct as written (surrogate halves are both > 0x7E), just worth a comment so it doesn't look accidental.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/stream_video/test/src/video_environment_test.dart (1)

6-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover browserName in the all-fields test.

The test named “builds header from all fields” does not set or assert the new browser segment. Add a browser value so this regression test covers every header component.

Proposed test update
         appVersion: '2.5.1',
         deviceModel: 'Nothing A142',
+        browserName: 'Chrome',
       );

       expect(
         environment.xStreamClientHeader,
         'stream-video-flutter-v1.4.1|app=MyApp|app_version=2.5.1|'
-        'os=android 16|device_model=Nothing A142',
+        'os=android 16|device_model=Nothing A142|browser=Chrome',
       );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stream_video/test/src/video_environment_test.dart` around lines 6 -
20, Update the “builds header from all fields” test around VideoEnvironment to
provide a browserName value and include the corresponding browser segment in the
expected xStreamClientHeader assertion, ensuring every header component is
covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/stream_video/test/src/video_environment_test.dart`:
- Around line 6-20: Update the “builds header from all fields” test around
VideoEnvironment to provide a browserName value and include the corresponding
browser segment in the expected xStreamClientHeader assertion, ensuring every
header component is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6007591a-25f6-4dbd-8bf7-d1b23e66e63a

📥 Commits

Reviewing files that changed from the base of the PR and between d0b3a62 and 8abf416.

📒 Files selected for processing (5)
  • packages/stream_video/CHANGELOG.md
  • packages/stream_video/lib/src/coordinator/open_api/coordinator_ws.dart
  • packages/stream_video/lib/src/sfu/ws/sfu_ws.dart
  • packages/stream_video/lib/src/video_environment.dart
  • packages/stream_video/test/src/video_environment_test.dart

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.

Non-ASCII app name causes Invalid HTTP header field value in X-Stream-Client

2 participants