Skip to content

Commit 6f2b43e

Browse files
authored
Re-enable windows-permission-protections (#627)
* Remove harmfulApis from the Windows build * Re-enable windows permission protections
1 parent 78d72db commit 6f2b43e

File tree

6 files changed

+70
-5
lines changed

6 files changed

+70
-5
lines changed

integration-test/playwright/harmful-apis.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { readFileSync } from 'fs'
33
import { mockWindowsMessaging, wrapWindowsScripts } from '@duckduckgo/messaging/lib/test-utils.mjs'
44
import { perPlatform } from './type-helpers.mjs'
55

6-
test('Harmful APIs protections', async ({ page }, testInfo) => {
6+
test.skip('Harmful APIs protections', async ({ page }, testInfo) => {
77
const protection = HarmfulApisSpec.create(page, testInfo)
88
await protection.enabled()
99
const results = await protection.runTests();

integration-test/playwright/windows-permissions.spec.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,42 @@ export class WindowsPermissionsSpec {
3131
}
3232

3333
async enabled () {
34+
await this.installPolyfills()
3435
const config = JSON.parse(readFileSync(this.config, 'utf8'))
3536
await this.setup({ config })
3637
await this.page.goto(this.htmlPage)
3738
}
3839

40+
/**
41+
* In CI, the global objects such as USB might not be installed on the
42+
* version of chromium running there.
43+
*/
44+
async installPolyfills () {
45+
await this.page.addInitScript(() => {
46+
// @ts-expect-error - testing
47+
if (typeof Bluetooth === 'undefined') {
48+
globalThis.Bluetooth = {}
49+
globalThis.Bluetooth.prototype = { requestDevice: async () => { /* noop */ } }
50+
}
51+
// @ts-expect-error - testing
52+
if (typeof USB === 'undefined') {
53+
globalThis.USB = {}
54+
globalThis.USB.prototype = { requestDevice: async () => { /* noop */ } }
55+
}
56+
57+
// @ts-expect-error - testing
58+
if (typeof Serial === 'undefined') {
59+
globalThis.Serial = {}
60+
globalThis.Serial.prototype = { requestPort: async () => { /* noop */ } }
61+
}
62+
// @ts-expect-error - testing
63+
if (typeof HID === 'undefined') {
64+
globalThis.HID = {}
65+
globalThis.HID.prototype = { requestDevice: async () => { /* noop */ } }
66+
}
67+
})
68+
}
69+
3970
/**
4071
* @param {object} params
4172
* @param {Record<string, any>} params.config

integration-test/test-pages/harmful-apis/config/apis.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"unprotectedTemporary": [],
33
"features": {
4+
"windowsPermissionUsage": {
5+
"state": "disabled"
6+
},
47
"harmfulApis": {
58
"state": "enabled",
69
"settings": {

integration-test/test-pages/permissions/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,19 @@
1414
<script>
1515
test('Disabled Windows Permissions', async () => {
1616
const registerProtocolHandler = await captureError(() => Navigator.prototype.registerProtocolHandler())
17+
const requestDevice = await captureError(() => Bluetooth.prototype.requestDevice())
18+
const usbDevice = await captureError(() => USB.prototype.requestDevice())
19+
const serialPort = await captureError(() => Serial.prototype.requestPort())
20+
const hidDevice = await captureError(() => HID.prototype.requestDevice())
21+
const midi = await captureError(() => Navigator.prototype.requestMIDIAccess())
1722

1823
return [
24+
{ name: 'Bluetooth.prototype.requestDevice()', result: requestDevice.toString(), expected: 'Error: Permission denied' },
25+
{ name: 'USB.prototype.requestDevice', result: usbDevice.toString(), expected: 'Error: Permission denied' },
26+
{ name: 'Serial.prototype.requestPort', result: serialPort.toString(), expected: 'Error: Permission denied' },
27+
{ name: 'HID.prototype.requestDevice', result: hidDevice.toString(), expected: 'Error: Permission denied' },
1928
{ name: 'Protocol handler: Navigator.prototype.registerProtocolHandler', result: registerProtocolHandler.toString(), expected: 'Error: Permission denied' },
29+
{ name: 'MIDI: Navigator.prototype.requestMIDIAccess', result: midi.toString(), expected: 'Error: Permission denied' },
2030
];
2131
})
2232

src/features.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ export const platformSupport = {
4040
windows: [
4141
...baseFeatures,
4242
'windowsPermissionUsage',
43-
'duckPlayer',
44-
'harmfulApis'
43+
'duckPlayer'
4544
],
4645
firefox: [
4746
...baseFeatures,

src/features/windows-permission-usage.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global Geolocation */
1+
/* global Bluetooth, Geolocation, HID, Serial, USB */
22
import { DDGProxy, DDGReflect } from '../utils'
33
import { defineProperty } from '../wrapper-utils'
44
import ContentFeature from '../content-feature'
@@ -381,7 +381,16 @@ export default class WindowsPermissionUsage extends ContentFeature {
381381

382382
// these permissions cannot be disabled using WebView2 or DevTools protocol
383383
const permissionsToDisable = [
384-
{ name: 'Protocol handler', prototype: () => Navigator.prototype, method: 'registerProtocolHandler', isPromise: false }
384+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
385+
{ name: 'Bluetooth', prototype: () => Bluetooth.prototype, method: 'requestDevice', isPromise: true },
386+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
387+
{ name: 'USB', prototype: () => USB.prototype, method: 'requestDevice', isPromise: true },
388+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
389+
{ name: 'Serial', prototype: () => Serial.prototype, method: 'requestPort', isPromise: true },
390+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
391+
{ name: 'HID', prototype: () => HID.prototype, method: 'requestDevice', isPromise: true },
392+
{ name: 'Protocol handler', prototype: () => Navigator.prototype, method: 'registerProtocolHandler', isPromise: false },
393+
{ name: 'MIDI', prototype: () => Navigator.prototype, method: 'requestMIDIAccess', isPromise: true }
385394
]
386395
for (const { name, prototype, method, isPromise } of permissionsToDisable) {
387396
try {
@@ -399,5 +408,18 @@ export default class WindowsPermissionUsage extends ContentFeature {
399408
console.info(`Could not disable access to ${name} because of error`, error)
400409
}
401410
}
411+
412+
// these permissions can be disabled using DevTools protocol but it's not reliable and can throw exception sometimes
413+
const permissionsToDelete = [
414+
{ name: 'Idle detection', permission: 'IdleDetector' },
415+
{ name: 'NFC', permission: 'NDEFReader' },
416+
{ name: 'Orientation', permission: 'ondeviceorientation' },
417+
{ name: 'Motion', permission: 'ondevicemotion' }
418+
]
419+
for (const { permission } of permissionsToDelete) {
420+
if (permission in window) {
421+
Reflect.deleteProperty(window, permission)
422+
}
423+
}
402424
}
403425
}

0 commit comments

Comments
 (0)