Configurable Screen Capture Enterprise Policies
This Document is Public
Authors: alcooper@chromium.org
August 2021
Enterprise customers have been asking for a way to restrict choices shown to users in the picker that is shown by getDisplayMedia (and similar screen capture extension APIs). One of the top requested scenarios revolves around enabling additional functionality/performance in the Chrome Apps of Virtual Desktop Infrastructure (VDI) providers (like Citrix), by allowing them to offload their screen capture for video conferencing scenarios on to the host OS via getDisplayMedia; however, this exposes the ability to potentially capture windows that should not be accessible to the virtual desktop. Thus, we need a set of enterprise policies to allow IT administrators to restrict the sharing choices available to their users.
Mac, Windows, Linux, Chrome OS.
alcooper@chromium.org, powerb@chromium.org, rbeard@chromium.org
Screen Capture Permission Checks (Callers of: 1, 2) , Picker UX, End-Capture Dialog (1, 2), Tab Capture Ribbon
VDI [Provider] - Virtual Desktop Infrastructure Providers are companies that allow their customers to connect to a virtualized desktop; in this case through a Chrome App, Progressive Web App, or Extension.
Host OS - The OS that is running Chrome, and “Hosting” the VDI App Window (which is running the remote/virtual desktop/apps)
Capturer [Origin] - The site [or origin of the site] that is requesting capture (e.g. calling getDisplayMedia)
Captured Site, Target - The site that is being captured by the capturer
Capture Source(s) - The primary types of items that can be captured, namely the main “panes” of the capture picker: desktops, native windows, and tabs
Restricting capture is inherently hierarchical. It makes no sense to disallow window capture while still allowing desktop capture, as desktop capture could still capture the window by capturing the desktop that contains the window. Similar arguments can be made for other desktop/window/tab/same origin tab capture combinations. We represent the different levels of restriction via an enum, which allows for easily making comparisons such as allowed_level >= required_level. We create an alias for Desktop capture as the most permissive level to indicate that it is not restricted.
enum class AllowedScreenCaptureLevel { kDisallowed = 0, kSameOrigin = 1, kTab = 2, kWindow = 3, kDesktop = 4, kUnrestricted = kDesktop, }; |
When combined with the existing policy that toggles all sites between kDisallowed and kUnrestricted, these policy levels naturally lend themselves to four new separate policies restricting sites to kSameOrigin, kTab, kWindow, or kDesktop (though the latter is primarily an override when the existing policy would set all sites to kDisallowed, and doesn’t mean anything if it would set them to kUnrestricted). Each policy will accept a list of standard URL patterns (which will each be validated/compared to the initiating origin [e.g. the site that called getDisplayMedia] via a ContentSettingsPattern object), but will only look at the origin of the requesting site. As a result, any URL patterns supplied to the policy with a path component will never match. These policies will follow the existing pattern of being plumbed into a browser preference to be looked up within the browser process where/when needed. If a site matches multiple lists it will have to follow the rules of its most restrictive list; however, an exception will be made for the existing ScreenCaptureAllowed boolean policy, which will only be used if an origin does not match a pattern in any of the four new policies. Various policy configurations and their observed behavior are discussed in deeper detail in the Appendix.
These four new policies are (in order of restriction):
SameOriginTabCaptureAllowedByOrigins TabCaptureAllowedByOrigins WindowCaptureAllowedByOrigins ScreenCaptureAllowedByOrigins |
One of the most common scenarios driving this feature request come from customers who use Chrome (either via Chrome Apps, Progressive Web Apps (PWAs) or extensions) to access virtual workspaces. Some of the VDI Providers primarily use Chrome Apps for their users to connect to their virtual workspaces and often spawn multiple app windows during the course of its execution. As an enhancement to some of the video conferencing solutions hosted by these providers, they want to offload some of that execution to the host OS, but any “screen sharing” picker then potentially exposes windows/desktops on the host OS. To avoid these potential data leaks, enterprises want to restrict these instances to only capture other instances of tabs or Chrome App windows from the same provider. While PWAs show up in both the “Windows” and “Tabs” pane of the screen capture picker, Chrome Apps currently only show up in the “Windows” pane of the picker. In order to better facilitate these enterprise scenarios, if a restriction is applied that the “Windows” pane of the picker would not appear (e.g. the Site is restricted to Tab Capture or Same Origin Tab Capture), then the “Tabs” pane of the picker (if visible), will be augmented to also include Chrome Apps. The same BrowserContext which is used to determine the browsers to iterate over, can also be used to get the Chrome App Window registry to iterate over. These App Windows are inherently backed by WebContents, and thus can be captured as such. While investigating this functionality it was discovered that Citrix (and presumably other Chrome Apps) creates a few hidden windows that shouldn’t be capturable, so those will be ignored.
std::vector<content::WebContents*> contents_list; // Enumerate all tabs for a user profile. … // Find all AppWindows for the given profile. const extensions::AppWindowRegistry::AppWindowList& window_list = extensions::AppWindowRegistry::Get(profile)->app_windows(); for (const auto* app_window : window_list) { if (!app_window->is_hidden()) contents_list.push_back(app_window->web_contents()); } |
There are a few phases of screen capture to consider in applying the new policies. The first is that existing checks of the ScreenCaptureAllowed policy need to be modified to account for the fact that though screen capture may be generally disallowed, an exception may be present for this site/origin. The second is that the list of sources generated for the picker (and indeed the items within those lists in the case of the SameOrigin restriction), need to be filtered. The third and final check needs to happen when a target site is navigated while being captured by a site under the SameOrigin restriction. If the target site is navigated away from the origin, capture needs to be terminated.
The ScreenCaptureAllowed policy is currently checked in the DisplayMediaAccessHandler (for getDisplayMedia/similar), in the DesktopCaptureAccessHandler (for the desktop sharing extension), and in TabCaptureAccessHandler (for the tab sharing extension). All of these existing checks need to be updated to check if the requesting site has permission to request capture. Further, the tab capture and desktop capture extension handlers need to be updated as well to avoid potentially circumventing the latter checks, since in some cases they directly facilitate the sharing or show a picker themselves.
In addition to blocking requests if a given handler requires more permissions than a site has, the picker itself needs to be filtered. Both the DisplayMediaAccessHandler and desktop capture extension directly build and show a picker with a list of requested sources (i.e. desktop capture, window capture, or tab capture), in the former case the sources are mostly hard coded, while in the latter case the requested sources come from the page. Both of these lists need to be filtered so that only allowed types of captures are shown (e.g. Desktops, Windows, Tabs). It is at this point that the determination of whether or not to include Chrome App Windows or if further filtering needs to be applied to the tabs list is made.
Rather than simply modify TabDesktopMediaList (which is used to display the tabs available to capture) to take in or compute an allowed capture level, it will be modified to take a RepeatingCallback that will indicate whether or not a given WebContents can be shown.
using WebContentsFilter = base::RepeatingCallback<bool(content::WebContents*)>; |
This slightly more generic pattern will also be helpful for future plans from the WebRTC team where they may need to filter WebContents objects based on other requirements. From an enterprise policy restriction perspective, this callback will generally be a no-op (allowing all WebContents), except in the case of the allowed capture level being kSameOrigin or kDisallowed, where the callback will compare the origins of the potential target and requesting site (for same origin), or reject all sites for kDisallowed.
It’s worth noting that MediaRouter, which drives Cast, also potentially summons the desktop picker. These policies are not meant to restrict cast (as the data shared via a cast session is limited to a display on the local network, and not blocked by the existing capture policy).
In addition to blocking non-SameOrigin tabs from the tabs list, there are a few other cases where the SameOrigin policy needs to be applied. The first is on the UX Ribbon that appears during a capture to “Capture this tab instead.” There is already logic to indicate to the infobar that a tab is not a valid capture target, and thus the button is suppressed. This logic can easily be expanded to account for the SameOrigin restriction. However, this also needs to be updated when a tab is navigated. A helper class SameOriginObserver will be created and used to determine if the infobar needs to be re-created. It will take a reference origin, and extends WebContentsObserver to override DidFinishNavigation. If the SameOriginObserver detects that a tab’s origin has changed relative to the reference origin (i.e. its origin either is now or is no longer the same as the reference origin), it will trigger a passed-in callback, which will (in this case) be used to re-create the infobar, as the existing infobar doesn’t have support for toggling this button on/off (as the button may also be suppressed due to the presence of another button).
The second enforcement of the SameOrigin restriction needs to be applied when the target is navigated. For this, the SameOriginObserver will also be used, though in this case, the first instance of the callback being triggered will mean that capture needs to be terminated (since the SameOriginObserver was created while the captured tab had the same origin as the reference origin). While the SameOriginObserver listens to DidFinishNavigation, this is fine, because at this point, any potential intermediate redirects are finished (meaning that we are on the tab/origin we will be settling on); but loading has not yet begun, so there’s no potential data leak.
The final SameOrigin enforcement needs to apply to the tab capture extension; since it never actually goes through any UI. Fortunately, all data about the requesting origin and the intended target are available in the MediaStreamRequest object passed into the TabCaptureAccessHandler. Thus, the SameOrigin enforcement needs to further be applied when initiating that capture, both to ensure the capture can start and to attach an observer to terminate the capture if the tab is navigated to a disallowed origin.
The SameOriginObserver to terminate capture after a disallowed navigation needs to be associated with the captured tab (to know that the navigation has occurred), and needs to be aware of the lifetime of the capture (so that it does not trigger the Dialog/attempt to stop capture after capture has already been stopped). It further needs to be initialized with the capture by an object that has access to the requesting web contents and the browser prefs store (to know if the observer needs to be created). It also needs some way to terminate the capture, and trigger a UX dialog to inform the user that capture was terminated due to enterprise policy. Because of this, the MediaStreamUI, which gets registered with the WebContentsDeviceUsage, is the best fit for owning this observer. It is tied to the lifetime of the capture (as it needs to be created at the start of and dismissed at the end of the capture), receives a OnceCallback that allows it to terminate the capture, and can spawn the UI after terminating capture. For the typical getDisplayMedia case, this OnceCallback is also needed by TabSharingUiViews for the “Stop capturing” button. As such, two approaches will need to be taken to enforce the same origin requirement on navigation. For a standard (getDisplayMedia) tab sharing case, the TabSharingUiViews object will own a SameOriginObserver that, upon being triggered, will act the same as if the “Stop Sharing” infobar button was pressed, except for spawning the capture terminated dialog. For the tab capture extension, a small MediaStreamUI wrapper will be used to receive the “stop” callback, and apply the relevant enforcement. It’s worth noting that if the requested dialog is spawned before navigation has reached the DidFinishNavigation phase, then the DidFinishNavigation phase will dismiss the dialog.
There are two requested UI modifications for this work. The first is a simple line beneath the picker indicating that the available sharing choices (whether that be sharing panes or the items in the tab list), have been limited. The second is a small dialog to appear on the captured tab when the capture was terminated due to a navigation causing the violation of the same origin restriction. Those are both shown below.
A new boolean will be added to DesktopMediaPicker::Params to indicate that this text should be shown; to avoid forcing the picker to query about the state of any enterprise policy. To avoid conflicts with existing usages, this param will default to false.
This dialog will be created by the SameOriginObserver which terminates the capture, and appear on the page that was being captured. (Because we will be re-using the existing Tab Dialog infrastructure, a small notification dot will appear on the tab if it is not focused when this dialog is created).
In order to evaluate how successful the enterprise policies are, we will track deployment of the policies via the existing UMA data collected by the Chrome Enterprise team.
This will be rolled out in a high-touch limited release to enterprise partners first, thus we will have an avenue to address negative feedback and stop the experiment immediately.
Enterprise policies are not allowed to be launched via Finch.
This is a top ask and pain point for several enterprise customers. Work will be rolled out in M94 as a high-touch limited rollout to ChromeOS, and in M95 for the remaining Desktop OS’s, as the high-touch rollout provides the opportunity to make backwards compatibility breaking changes if needed that a full rollout could not.
Everything we do should be aligned with and consider Chrome’s core principles. If there are any specific stability concerns, be sure to address them with appropriate experiments.
Nothing for this feature will be checked upon page load, but only after a user activation allows for a capture to be initiated. At that time, we will query the enterprise policy and compare the requesting origin against all supplied patterns in the four policies, which could be extensive. However, the size of that list is entirely under the administrators control. We will mitigate this by trying to minimize the number of times we query the policy, as well as ensuring early returns from pattern matches where possible.
The URL patterns accepted by the policies match those used by other enterprise policies, with the slight rarity of additionally accepting a full wildcard path. The four new levels may at first seem confusing, but provide a simpler interface than requiring administrators to build a custom dictionary to meet the four different capture level requirements; especially as it seems likely that administrators may simply pick a level to restrict all sites to.
The enterprise policy restrictions will be a list of URL patterns representing origins set by IT administrators. We are plugging in to existing infrastructure to read and set these as browser preferences, and then an existing ContetSettingsPattern class is used to look for a match with the origin, mimicking other enterprise policies that take URL patterns. The check for this policy is done in the browser process, though some mechanisms do come through the tab and desktop capture extension API’s.
N/a
Unit Tests and Browser Tests will be added around SameOrigin and Chrome App Window scenarios, which are also the most complex portions of the feature. SameOrigin needs to ensure that we appropriately terminate, and Chrome App Windows have not been captured as WebContents prior to this work. Unit tests will also be added to ensure that the ordering/restrictiveness of the policies is respected (e.g. tests similar to the prose in Appendix 1 will be added), and that media type lists are appropriately filtered.
Instructions for how to set the policies can be found under the “manual testing” step of this doc.
Per the rollout plan above, the rollout will be phased. No additional followup work is anticipated.
The following scenarios demonstrate a few policy configurations, as well as the results of those configurations. Note that these scenarios are largely intended to walk through edge cases/test cases, and are likely not indicative of real world configurations.
Policy Name | Value |
ScreenCaptureAllowed | True |
ScreenCaptureAllowedByOrigins | (empty/unset) |
WindowCaptureAllowedByOrigins | a.com,b.com |
TabCaptureAllowedByOrigins | b.com,c.com |
SameOriginTabCaptureAllowedByOrigins | c.com, d.com |
Site | Restrictions |
a.com | Can only capture Windows/Tabs |
b.com | Can only Capture Tabs (any tabs) |
c.com | Can only Capture Same-origin Tabs |
d.com | Can only Capture same-origin Tabs |
e.com | Can capture Desktop/Windows/Tabs (though not on any allow list, falls under blanket “ScreenCaptureAllowed”) |
The policies which take Origins take precedence over the default “ScreenCaptureAllowed”, allowing these sites to only perform their allowed level of Screen Capture, while all other sites are unrestricted.
Policy Name | Value |
ScreenCaptureAllowed | False |
ScreenCaptureAllowedByOrigins | a.com |
WindowCaptureAllowedByOrigins | b.com,c.com |
TabCaptureAllowedByOrigins | c.com |
SameOriginTabCaptureAllowedByOrigins | d.com |
Site | Restrictions |
a.com | Can Capture Desktop/Windows/Tabs |
b.com | Can Capture Windows/Tabs |
c.com | Can only Capture Tabs |
d.com | Can only Capture same-origin Tabs |
e.com | Can not capture anything (not on any allow list). |
f.a.com | Can not capture anything (per this, a domain specified without a wildcard will not match any subdomains). |
The policies which take Origins take precedence over the default “ScreenCaptureAllowed”, allowing these sites to still perform their allowed level of Screen Capture, while all other sites are restricted.
Policy Name | Value |
ScreenCaptureAllowed | True |
ScreenCaptureAllowedByOrigins | (empty/unset) |
WindowCaptureAllowedByOrigins | a.com,b.com |
TabCaptureAllowedByOrigins | b.com,c.com, * |
SameOriginTabCaptureAllowedByOrigins | c.com, d.com |
Site | Restrictions |
a.com | Can only Capture Tabs (matched by wildcard to be more restrictive) |
b.com | Can only Capture Tabs (explicitly matched to be more restricted) |
c.com | Can only Capture Same-origin Tabs |
d.com | Can only Capture same-origin Tabs |
e.com | Can only capture tabs (matched by wildcard) |
Because of the absolute wildcard match, the “lower priority” policies (i.e. WindowCaptureAllowedByOrigins, ScreenCaptureAllowedByOrigins, and ScreenCaptureAllowed) should never actually end up being processed.
Policy Name | Value |
ScreenCaptureAllowed | True |
ScreenCaptureAllowedByOrigins | * |
WindowCaptureAllowedByOrigins | (empty/unset) |
TabCaptureAllowedByOrigins | (empty/unset) |
SameOriginTabCaptureAllowedByOrigins | (empty/unset) |
All sites can capture Desktop/Windows/Tabs. Note that in this case the wildcard is functionally a no-op, as if “ScreenCaptureAllowedByOrigins” was empty, the same behavior would be observed.
Because of the absolute wildcard match, the value of ScreenCaptureAllowed doesn’t actually matter for this scenario.
Policy Name | Value |
ScreenCaptureAllowed | True |
ScreenCaptureAllowedByOrigins | * |
WindowCaptureAllowedByOrigins | (empty/unset) |
TabCaptureAllowedByOrigins | * |
SameOriginTabCaptureAllowedByOrigins | (empty/unset) |
All sites may only capture Tabs (as the TabCaptureAllowedByOrigins match overrides the ScreenCaptureAllowedByOrigins match).
Because of the absolute wildcard match, the “lower priority” policies (i.e. WindowCaptureAllowedByOrigins, ScreenCaptureAllowedByOrigins, and ScreenCaptureAllowed) should never actually end up being processed.
Policy Name | Value |
ScreenCaptureAllowed | False |
ScreenCaptureAllowedByOrigins | * |
WindowCaptureAllowedByOrigins | (empty/unset) |
TabCaptureAllowedByOrigins | * |
SameOriginTabCaptureAllowedByOrigins | (empty/unset) |
All sites may capture tabs, because the * matches all sites allowing override of “ScreenCaptureAllowed” being false.
Because of the absolute wildcard match, the “lower priority” policies (i.e. WindowCaptureAllowedByOrigins, ScreenCaptureAllowedByOrigins, and ScreenCaptureAllowed) should never actually end up being processed.
Policy Name | Value |
ScreenCaptureAllowed | False |
ScreenCaptureAllowedByOrigins | * |
WindowCaptureAllowedByOrigins | (empty/unset) |
TabCaptureAllowedByOrigins | a.com |
SameOriginTabCaptureAllowedByOrigins | (empty/unset) |
All sites except a.com may capture Desktop/Windows/Tabs. a.com may only capture tabs.
Because of the absolute wildcard match, the value of ScreenCaptureAllowed doesn’t actually matter for this scenario.