Skip to content

Javascript Binding - Add ability to limit access to JavaScript Bound objects to specific origins #5085

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

amaitland
Copy link
Member

@amaitland amaitland commented Apr 12, 2025

Fixes:
#5001

Summary:

  • Add new JavascriptBindingSettings.JavascriptBindingApiAllowOrigins property

Changes:

  • Don't create CefSharp objects when non matching origin
  • Doesn't currently impact Legacy binding
  • Added xunit tests

How Has This Been Tested?

  • New xUnit tests have been added

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Updated documentation

Checklist:

  • Tested the code(if applicable)
  • Commented my code
  • Changed the documentation(if applicable)
  • New files have a license disclaimer
  • The formatting is consistent with the project (project supports .editorconfig)

Summary by CodeRabbit

  • New Features
    • Added support to restrict the creation of global JavaScript binding objects to specified allowed origins. When enabled, the JavaScript binding API will only be available on origins explicitly listed in the settings.
  • Tests
    • Introduced new tests to verify JavaScript binding API availability based on allowed origins.
  • Chores
    • Added internal settings and cleanup related to allowed origins for JavaScript binding.

@amaitland amaitland self-assigned this Apr 12, 2025
cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);

global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY);
createObjects = false;
Copy link
Member Author

Choose a reason for hiding this comment

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

Extract this into a helper method would probably make this a lot cleaner

@@ -33,6 +35,24 @@ public bool JavascriptBindingApiEnabled
}
}

/// <summary>
/// When <see cref="JavascriptBindingApiEnabled"/> is set to true, set a collection
Copy link
Member Author

Choose a reason for hiding this comment

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

Needs review

@@ -95,5 +115,17 @@ public bool AlwaysInterceptAsynchronously
alwaysInterceptAsynchronously = value;
}
}

/// <summary>
/// HasJavascriptBindingApiAllowOrigins
Copy link
Member Author

Choose a reason for hiding this comment

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

Needs review.

@amaitland
Copy link
Member Author

Could probably use some more tests, specifically for multiple origins in the list

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@amaitland amaitland force-pushed the feature/jsbindingalloworiginlist branch 2 times, most recently from 218de9c to 60203e5 Compare April 12, 2025 04:58

auto clrOrigin = StringUtils::ToClr(origin);

auto originEqual = String::Compare(clrframeUrlOrigin, clrOrigin, StringComparison::InvariantCultureIgnoreCase);
Copy link
Member Author

Choose a reason for hiding this comment

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

Need to find a more efficient string comparison, ideally compare the native strings

@AppVeyorBot
Copy link

/// <remarks>
/// If you wish to create the CefSharp object for a limited set of origins then set this property
/// </remarks>
public string[] JavascriptBindingApiAllowOrigins
Copy link
Member Author

Choose a reason for hiding this comment

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

The origin returned from CEF when it's parsed always has a trailing /, we should ensure that there's always a trailing slash programmatically

@@ -29,6 +29,8 @@ namespace CefSharp
bool _focusedNodeChangedEnabled;
bool _legacyBindingEnabled;
bool _jsBindingApiEnabled = true;
Copy link
Member Author

Choose a reason for hiding this comment

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

Storing these on a per browser basis would provide greater flexibility, we could create a class that has a lookup by browser id.

@amaitland amaitland force-pushed the feature/jsbindingalloworiginlist branch from 60203e5 to 9aeb5cb Compare May 14, 2025 20:32
Copy link

coderabbitai bot commented May 14, 2025

Walkthrough

The changes implement origin-based restrictions for JavaScript binding APIs in CefSharp. New settings and logic allow specifying allowed origins, and JavaScript binding objects are only created if the current page's origin matches these. Tests are added to verify this behavior, and internal state is updated to support the new restrictions.

Changes

File(s) Change Summary
CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp, CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h Added logic and private members to support checking allowed origins for JavaScript binding API object creation.
CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp Extended browser creation to pass allowed origins and related flags via extraInfo.
CefSharp/JavascriptBinding/JavascriptBindingSettings.cs Added JavascriptBindingApiAllowOrigins property and HasJavascriptBindingApiAllowOrigins() method to settings.
CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs Added tests to verify JavaScript binding API enablement/disablement based on allowed origins.
CefSharp.Test/CefSharpFixture.cs Added (commented-out) line for possible command-line argument; no effect on logic.

Sequence Diagram(s)

sequenceDiagram
    participant BrowserProcess
    participant RendererProcess
    participant CefAppUnmanagedWrapper

    BrowserProcess->>RendererProcess: Create browser (with extraInfo: allowed origins)
    RendererProcess->>CefAppUnmanagedWrapper: OnContextCreated (frame URL)
    CefAppUnmanagedWrapper->>CefAppUnmanagedWrapper: Parse frame URL, extract origin
    CefAppUnmanagedWrapper->>CefAppUnmanagedWrapper: Check if origin is in allowed list
    alt Origin allowed or no restrictions
        CefAppUnmanagedWrapper->>RendererProcess: Create JS binding objects
    else Origin not allowed
        CefAppUnmanagedWrapper-->>RendererProcess: Do not create JS binding objects
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Restrict access to JavaScript bound objects to specific allowed origins (domains/URLs) [#5001]
Add setting/property for specifying allowed domains in JavaScript binding settings [#5001]
Ensure CefSharp enforces validation and restricts object creation based on origin [#5001]
Add tests to verify enablement/disablement of JS binding API based on allowed origins [#5001]

Poem

In the warren of code where the browsers hop,
Now origins are checked before JS can pop!
If your domain is trusted, the bindings appear,
If not, the objects just vanish—no fear!
With settings and tests, this rabbit approves,
Securely, our browser now nimbly moves! 🐇✨

[!ANNOUNCEMENT]

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


[!ANNOUNCEMENT]

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@amaitland
Copy link
Member Author

@coderabbitai review

Copy link

coderabbitai bot commented May 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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

♻️ Duplicate comments (1)
CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp (1)

165-195: 🛠️ Refactor suggestion

Avoid round-tripping through managed strings for origin comparison

The loop currently converts every allowed origin and the frame origin to managed
System::String before doing an invariant-culture compare:

auto clrframeUrlOrigin = StringUtils::ToClr(frameUrlOrigin);
//
auto clrOrigin = StringUtils::ToClr(origin);
auto originEqual = String::Compare(clrframeUrlOrigin, clrOrigin,
                                   StringComparison::InvariantCultureIgnoreCase);

This adds two allocations per iteration, crosses the managed/native boundary,
and duplicates previous logic flagged in an earlier review.
A more efficient/native-only approach would be:

- auto frameUrlOrigin = CefString(frameUrlParts.origin.str,
-                                 frameUrlParts.origin.length);
+ // frameUrlParts.origin already is a |cef_string_t|
+ const CefString& frameUrlOrigin = frameUrlParts.origin;

- auto originEqual = String::Compare(clrframeUrlOrigin, clrOrigin,
-                                    StringComparison::InvariantCultureIgnoreCase);
+ auto originEqual =
+     frameUrlOrigin.Compare(origin, /*case_insensitive=*/true) == 0;

This:

  1. Eliminates managed allocations and GC pressure.
  2. Keeps comparison purely in the native layer.
  3. Leaves behaviour (case-insensitive compare) unchanged.

Consider extracting the whole “origin matches allow-list” logic into a small
helper to make OnContextCreated easier to read and unit-test.

🧹 Nitpick comments (4)
CefSharp.Test/CefSharpFixture.cs (1)

67-67: Consider adding a comment explaining this line's purpose.

This commented-out line adds a command-line argument to disable the "SpareRendererForSitePerProcess" feature. Since it's commented out and not explained, it's unclear why it was added or under what conditions it might be enabled.

CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h (1)

32-33: Consider implementing a per-browser approach instead of global state.

The implementation currently stores allowed origins as global state in the CefAppUnmanagedWrapper. This works for the current use case but may be less flexible when different browsers need different origin restrictions.

A more flexible approach would be to store these settings on a per-browser basis using a lookup by browser ID, which would allow different origin restrictions for different browser instances.

CefSharp/JavascriptBinding/JavascriptBindingSettings.cs (1)

119-129: Improve XML documentation for the HasJavascriptBindingApiAllowOrigins method.

The current XML documentation only repeats the method name without providing additional context. Consider expanding the documentation to explain when and why this method would be used.

/// <summary>
-/// HasJavascriptBindingApiAllowOrigins 
+/// Checks if there are any allowed origins specified for JavaScript binding API.
/// </summary>
+/// <remarks>
+/// This method is used internally to determine if origin-based restrictions 
+/// should be applied when creating JavaScript binding API objects.
+/// </remarks>
/// <returns>bool true if <see cref="JavascriptBindingApiAllowOrigins"/> is non empty collection.</returns>
CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs (1)

160-185: Assert the load result for consistency

In the preceding tests you verify loadResponse.Success after the initial
WaitForInitialLoadAsync call. Doing the same here guards against silent HTTP
errors that would otherwise surface only as “undefined” object checks.

- await browser.WaitForInitialLoadAsync();
+ var loadResponse = await browser.WaitForInitialLoadAsync();
+ Assert.True(loadResponse.Success);

(Not critical for correctness of the feature, but keeps the test suite’s style
and diagnostics consistent.)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c944774 and 9aeb5cb.

📒 Files selected for processing (6)
  • CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp (3 hunks)
  • CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h (2 hunks)
  • CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp (1 hunks)
  • CefSharp.Test/CefSharpFixture.cs (1 hunks)
  • CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs (1 hunks)
  • CefSharp/JavascriptBinding/JavascriptBindingSettings.cs (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs (5)
CefSharp.Test/JavascriptBinding/JavaScriptObjectRepositoryTests.cs (3)
  • Fact (32-50)
  • Fact (52-60)
  • Fact (63-89)
CefSharp.Test/JavascriptBinding/JavascriptBindingSimpleTest.cs (1)
  • Fact (27-53)
CefSharp/Internals/JavascriptObjectRepository.cs (4)
  • Task (419-422)
  • Task (424-568)
  • JavascriptObjectRepository (35-795)
  • JavascriptObjectRepository (114-118)
CefSharp.Test/WebBrowserTestExtensions.cs (2)
  • Task (57-119)
  • Task (121-149)
CefSharp.Example/CefExample.cs (1)
  • CefExample (15-274)
🪛 Cppcheck (2.10-2)
CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp

[error] 90-90: syntax error

(syntaxError)

🔇 Additional comments (5)
CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h (1)

75-75: Good cleanup of the CefRefPtr.

Properly setting the _jsBindingApiAllowOrigins to nullptr in the destructor prevents potential memory leaks.

CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp (2)

87-102: Implementation correctly propagates origin restrictions.

The code properly checks for allowed origins and adds them to the extra info dictionary along with a flag indicating their presence. This allows the native code to enforce origin-based restrictions for JavaScript binding.

🧰 Tools
🪛 Cppcheck (2.10-2)

[error] 90-90: syntax error

(syntaxError)


90-90: False positive from static analysis.

The static analysis tool reported a syntax error here, but the code is syntactically correct. This is a false positive and can be ignored.

🧰 Tools
🪛 Cppcheck (2.10-2)

[error] 90-90: syntax error

(syntaxError)

CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp (2)

103-114: Extraction and lifetime management look correct

The flag/array are read once and copied, mirroring the handling of other settings.
No correctness, ownership, or thread-safety issues spotted here.


198-245: Conditional creation path integrates cleanly

Moving the binding-object creation behind the createObjects gate keeps existing
behaviour when no restrictions are configured while avoiding object exposure
for disallowed origins. 👍

Comment on lines +38 to +54
/// <summary>
/// When <see cref="JavascriptBindingApiEnabled"/> is set to true, set a collection
/// of origins to limit which origins CefSharp will create it's global (window) object.
/// </summary>
/// <remarks>
/// If you wish to create the CefSharp object for a limited set of origins then set this property
/// </remarks>
public string[] JavascriptBindingApiAllowOrigins
{
get { return javascriptBindingApiAllowOrigins; }
set
{
ThrowIfFrozen();

javascriptBindingApiAllowOrigins = value;
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure origins have trailing slashes.

The origins returned by CEF always include a trailing slash when parsed. To ensure consistent matching, the code should normalize all origins by adding trailing slashes if they don't exist.

public string[] JavascriptBindingApiAllowOrigins
{
    get { return javascriptBindingApiAllowOrigins; }
    set
    {
        ThrowIfFrozen();

-       javascriptBindingApiAllowOrigins = value;
+       // Ensure all origins have trailing slashes to match CEF's format
+       if (value != null)
+       {
+           javascriptBindingApiAllowOrigins = Array.ConvertAll(value, origin => 
+               origin.EndsWith("/") ? origin : origin + "/");
+       }
+       else
+       {
+           javascriptBindingApiAllowOrigins = null;
+       }
    }
}

You'll need to add using System; at the top of the file for this change.


🏁 Script executed:

#!/bin/bash
# Locate the settings file
file=$(fd JavascriptBindingSettings.cs)
echo "File located at: $file"
echo
echo "=== Using directives ==="
grep -n "^using" "$file"
echo
echo "=== File header (lines 1–30) ==="
sed -n '1,30p' "$file"
echo
echo "=== Property snippet (lines 30–90) ==="
sed -n '30,90p' "$file"

Length of output: 4593


🏁 Script executed:

#!/bin/bash
# Search for references to javascriptBindingApiAllowOrigins and origin matching logic
echo "References to javascriptBindingApiAllowOrigins:"
rg -n 'javascriptBindingApiAllowOrigins' -A3 -B3 CefSharp/JavascriptBinding/JavascriptBindingSettings.cs

echo
echo "Search for origin matching in repository:"
rg -n 'AllowOrigins' -A3 -B3 -t cs .

Length of output: 6462


🏁 Script executed:

#!/bin/bash
# Inspect how AllowOrigins is used in the JavascriptBinding tests
echo "=== References to AllowOrigins in tests ==="
rg -n 'AllowOrigins' -A5 -B5 CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs

echo
echo "=== Test file snippet (lines 120–200) ==="
sed -n '120,200p' CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs

Length of output: 5139


Normalize JavascriptBindingApiAllowOrigins to Always Include a Trailing Slash

To prevent mismatches against CEF’s parsed origins (which always end with “/”), normalize each entry in the setter so that it always has a trailing slash:

File: CefSharp/JavascriptBinding/JavascriptBindingSettings.cs

 public string[] JavascriptBindingApiAllowOrigins
 {
     get { return javascriptBindingApiAllowOrigins; }
     set
     {
         ThrowIfFrozen();
-        javascriptBindingApiAllowOrigins = value;
+        if (value != null)
+        {
+            javascriptBindingApiAllowOrigins = Array.ConvertAll(
+                value,
+                origin => origin.EndsWith("/") ? origin : origin + "/"
+            );
+        }
+        else
+        {
+            javascriptBindingApiAllowOrigins = null;
+        }
     }
 }

Don’t forget to add the namespace at the top of the file:

using System;

This change ensures that origins supplied without a trailing slash will correctly match CEF’s internal origin strings.

@AppVeyorBot
Copy link

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.

2 participants