Skip to content
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

Should enumerateDevices by default return an empty list? #646

Closed
youennf opened this issue Nov 28, 2019 · 10 comments · Fixed by #758
Closed

Should enumerateDevices by default return an empty list? #646

youennf opened this issue Nov 28, 2019 · 10 comments · Fixed by #758
Assignees
Labels
privacy-needs-resolution Issue the Privacy Group has raised and looks for a response on. Ready for PR TPAC 2020

Comments

@youennf
Copy link
Contributor

youennf commented Nov 28, 2019

That would prevent all enumerateDevices fingerprinting for pages without camera/microphone permission.

@alvestrand
Copy link
Contributor

My personal thought:
returning an empty list is an active false statement ("I have no devices") for all code that depends on the current behavior, and is likely to trigger "you can't use this feature until you plug in..." UI.
If we want to break stuff that depends on listing devices without having one open, we should return an error / rejected promise.

@youennf
Copy link
Contributor Author

youennf commented Nov 29, 2019

enumerateDevices already returns an empty list for third-party iframes that do not have permission to access cameras/microphones. This is not false, it just means that the page is not granted access to more information.

Rejecting might have bigger backward compatibility risk since navigator.mediaDevices.enumerateDevices() never rejects currently, this might be a good experiment but I don't think we should mandate that.

@guest271314
Copy link

As noted by @pes10k at #690 (comment)

Sorry, i just don't understand what you're asking, other than to say that the values exposed by enumerateDevices are used as inputs to generating fingerprint-derived identifiers, and for some users will be highly identifying. Hope that helps

One solution would be to not expose devices to websites at all. Devices can be exposed to users. The user can then select devices that the website cannot "see" in the form they are now, and grant website permission to generic "audio" or "video" device.

A rough concept can take several forms. Consider a Blob URL, where, in general, the lifetime of the Blob URL is the lifetime of the document which created it. When the user executes getUserMedia() a prompt is displayed save for the case of enabling fake media streams using flags or preferences.

When enumerateDevices() is executed the result can be displayed, or accessed only by the user, not the website. The user must always have access to their devices as a list, unless they explicitly set otherwise, which should also be an option. Similar to current getUserMedia() and getDisplayMedia() prompt.

E.g., before any calls to enumateDevices() or getUserMedia(), etc., the user can execute setExposedDevicesNames() which, like getUserMedia(), only the user can "see" the prompt, set the selected device(s) to corresponding name or a UUID can be generated, which maps to the real device. The site never gets any of the information currently possible with https://github.com/Valve/fingerprintjs2/blob/master/fingerprint2.js#L332 as there is no reason for a website to have any of that information at all: the website just needs to know the user selected and permission granted-for device(s) are exposed, not their type or configuration - unless the user decides to expose that information explicitly. Though there is little reason to do so. The device will not change, the website has to process whatever media the device is capable of capturing and transmitting anyway.

The website only gets "audio", "audioN", "video", "videoN" in the form of a set, "videoN" becomes "video" when "video" is detached, unplugged, permissions revoked, etc. The user gets the complete device names, etc. Or, further, complete device capabilities can be behind a flag; all the site gets is "audio" or "video" device.

setExposedDeviceNames() => prompt for permission => local dialog/context menu (in general, not captured by getDisplayMedia()) => all devices listed => user selects devices to be exposed for session (with option for devices not online, though potentially will be online during session, a reservation of permission grant and possible, though not mandatory exposure or usage) => devices mapped to generic "names" defaulting to "audio", "videoN" or other unique symbols, or custom user name, or the current values, if the user decides to do so, for whatever reason waiving their idea of or presumption of "privacy" and disregarding "tracking", e.g., a Set => website only gets "audioN", "video" => enumerateDevices() returns "audioN", "video", the user knows the mapping to real devices, there is no reason for the website to know the real names or capabilities by default.

@guest271314
Copy link

If "privacy" and "tracking" are the primary concern, then backwards compatibility for site that are expecting media devices names need to adapt, as those are secondary concerns, not the other way around.

Note, if the concept outlined above requires too much "new" work, then the existing implementations of fake media devices and fake media streams can be used, that is, set all devices to Fake and if id,groupId are an issue, those values can be set to an empty string.

Why would a website need id and groupId for anything? The set of media devices can literally be a set where the only relevant key is index of the item in the set.

0: MediaStreamTrack
contentHint: ""
enabled: true
id: "4a1a5d41-9ba9-4a54-92cc-8212ff44c43d"
kind: "audio"
label: "Fake Default Audio Input"
muted: false
onended: null
onmute: null
onunmute: null
readyState: "live"

0: MediaStreamTrack
contentHint: ""
enabled: true
id: "36ab6b04-1a96-42ef-8ab2-794718da1203"
kind: "video"
label: "fake_device_0"
muted: false
onended: null
onmute: null
onunmute: null
readyState: "live"

navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then(stream => {
  navigator.mediaDevices.enumerateDevices()
  .then(devices => console.log(JSON.stringify(devices, null, 2)));
  console.log(stream.getTracks())
})
Promise {<pending>}
VM624:5 (2) [MediaStreamTrack, MediaStreamTrack]0: MediaStreamTrackcontentHint: ""enabled: trueid: "4a1a5d41-9ba9-4a54-92cc-8212ff44c43d"kind: "audio"label: "Fake Default Audio Input"muted: falseonended: nullonmute: nullonunmute: nullreadyState: "live"__proto__: MediaStreamTrackapplyConstraints: ƒ applyConstraints()clone: ƒ clone()contentHint: (...)enabled: (...)getCapabilities: ƒ getCapabilities()getConstraints: ƒ getConstraints()getSettings: ƒ getSettings()id: (...)kind: (...)label: (...)muted: (...)onended: (...)onmute: (...)onunmute: (...)readyState: (...)stop: ƒ stop()constructor: ƒ MediaStreamTrack()Symbol(Symbol.toStringTag): "MediaStreamTrack"get contentHint: ƒ contentHint()set contentHint: ƒ contentHint()get enabled: ƒ enabled()set enabled: ƒ enabled()get id: ƒ id()get kind: ƒ kind()get label: ƒ label()get muted: ƒ muted()get onended: ƒ onended()set onended: ƒ onended()get onmute: ƒ onmute()set onmute: ƒ onmute()get onunmute: ƒ onunmute()set onunmute: ƒ onunmute()get readyState: ƒ readyState()__proto__: EventTarget1: MediaStreamTrackcontentHint: ""enabled: trueid: "36ab6b04-1a96-42ef-8ab2-794718da1203"kind: "video"label: "fake_device_0"muted: falseonended: nullonmute: nullonunmute: nullreadyState: "live"__proto__: MediaStreamTracklength: 2__proto__: Array(0)
VM624:4 [
  {
    "deviceId": "default",
    "kind": "audioinput",
    "label": "Fake Default Audio Input",
    "groupId": "6e9bbc2c2fa2cf19aa0499ecbbb37fded7a4c71b604e29a7754169f3990c641d"
  },
  {
    "deviceId": "fff840c638bff6368f79b4239218360eb85606ae8cbf8f10dfaf5c988984d3a3",
    "kind": "audioinput",
    "label": "Fake Audio Input 1",
    "groupId": "6def593db7c2b05d9f928357778cf7d95f1754318cf03af059d128ec4418944d"
  },
  {
    "deviceId": "4d0dd7f641eb908da16231f233cee068a136361d249141efa14aaea168387588",
    "kind": "audioinput",
    "label": "Fake Audio Input 2",
    "groupId": "70e48e9d72f7a813c2ec4afadaca47fffb8bb1792c3b81eaf3d894aaa1c5285a"
  },
  {
    "deviceId": "01f0bf7cf85b086c617e80d5e3311e058b10aa63216d441838352869533e4e1e",
    "kind": "videoinput",
    "label": "fake_device_0",
    "groupId": "9f48238c7b9b5513683ddab4ddf54b4ad238c59738e7486bbfe070f49c0041cb"
  },
  {
    "deviceId": "default",
    "kind": "audiooutput",
    "label": "Fake Default Audio Output",
    "groupId": "e17614d28190592ec62a661d89b851bd8746d782df1b7eafb5b76fae880b4094"
  },
  {
    "deviceId": "ea692280ba9c70eea8eccf2522c13282d3f8e3c8ba98e82c126715759f2d2e75",
    "kind": "audiooutput",
    "label": "Fake Audio Output 1",
    "groupId": "1af313e5b455f7749d38f48635b0eb88bf88b5bc5e90d836a42d885b1bce309e"
  },
  {
    "deviceId": "24306d3edf988cf4c0920bea5dcff1f769f0faf2e007424ff332392ec1485034",
    "kind": "audiooutput",
    "label": "Fake Audio Output 2",
    "groupId": "9999b328ff18855e6b016e53f2ccb264b133ae2cac0766d75aa257311c0e8881"
  }
]

@w3cbot w3cbot added privacy-needs-resolution Issue the Privacy Group has raised and looks for a response on. and removed privacy-tracker Group bringing to attention of Privacy, or tracked by the Privacy Group but not needing response. labels May 22, 2020
@youennf
Copy link
Contributor Author

youennf commented Jun 18, 2020

this might be a good experiment

I tried this locally. This broke several major videoconferencing websites I tested (I can provide the names offline). Basically, if no camera is detected, these websites will not ask getUserMedia and/or will not provide UI to request access. This change is therefore currently not web-compatible as is.

I propose we discuss this issue at next interim. My recommendation is to not pursue this particular mitigation.

The specification allows to return a list of one camera and one microphone, even though there is no such devices. This mitigation can be implemented without any spec change and is probably more web compatible.

@pes10k
Copy link

pes10k commented Jun 30, 2020

@youennf Like i mentioned on other thread, I know there are several related topics at play here, so if I'm putting this comment in the wrong place, my apologies; happy to move wherever is best for the WG

My understanding from other issues that the current plan is to pre-permission, not return an empty list, but to instead return one entry per type of device supported. So if my device has 2 cameras, one speaker, and a dozen microphones, the pre-permission returned value from enumerateDevices would have three items in it. Is this correct?

If so, what would the labels be for those returned values? And what would the device ids be?

@q-alex-zhao
Copy link

q-alex-zhao commented Jul 3, 2020

@pes10k I think you'll get:

[
  {deviceId: "", grouId: "uuid1", kind: "audioinput", label: ""},
  {deviceId: "", grouId: "uuid2", kind: "videoinput", label: ""},
  {deviceId: "", grouId: "uuid3", kind: "audiooutput", label: ""}
]

On the other hand with the in-chrome device selection proposal and

not expose devices to websites at all

Why would an application call enumerateDevices at all?

@pes10k
Copy link

pes10k commented Jul 6, 2020

@q-alex-zhao Thank you!

That output sounds good to me (setting aside the distinct issues i've raised regarding using uuids). Terrific!

Why would an application call enumerateDevices at all?

You all and others in the group could say better than me, but i believe the former is just meant to be a (semi?) temporary bridget to the latter, no?

@youennf
Copy link
Contributor Author

youennf commented Oct 1, 2020

WebRTC WG consensus at the interim is to keep current approach.

@aboba aboba added the TPAC 2020 label Oct 1, 2020
@jan-ivar jan-ivar added the privacy-tracker Group bringing to attention of Privacy, or tracked by the Privacy Group but not needing response. label Oct 9, 2020
@w3cbot w3cbot removed the privacy-tracker Group bringing to attention of Privacy, or tracked by the Privacy Group but not needing response. label Oct 13, 2020
@jan-ivar
Copy link
Member

jan-ivar commented Nov 9, 2020

Presented consensus is to continue returning booleans for cam/mic for web compat. Clarify UAs can mitigate by faking camera/mic presence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
privacy-needs-resolution Issue the Privacy Group has raised and looks for a response on. Ready for PR TPAC 2020
Projects
Development

Successfully merging a pull request may close this issue.

9 participants