Skip to content

[BUG] duplicate properties in proxied window object #2420

@PYUDNG

Description

@PYUDNG

Sequence of actions:

  1. Install this script (In non-firefox browser)
// ==UserScript==
// @name        New script
// @namespace   Violentmonkey Scripts
// @match       *://*/*
// @version     1.0
// @author      -
// @grant       GM_getValue
// @description Voilentmonkey bug
// ==/UserScript==

window.setInterval;
// or
//window.setTimeout;
console.log(Reflect.ownKeys(window));
  1. open any page (such as Google)
  2. check devtools for error

Problem:

A TypeError: 'ownKeys' on proxy: trap returned duplicate entries is thrown when calling Reflect.ownKeys(window) or Object.getOwnPropertyNames(window) if standard global functions like setInterval or setTimeout have been accessed previously.

Expected result:

Reflect.ownKeys(window) should return a unique list of property names without duplicates. The proxy trap should ensure that "materialized" built-in functions do not collide with the static global keys snapshot.

Devtools console contents:

New script.user.js:14 
 Uncaught TypeError: 'ownKeys' on proxy: trap returned duplicate entries
    at Reflect.ownKeys (<anonymous>)
    at New script.user.js:14:21
    at Proxy.VMhoyus2mzbo6 (New script.user.js:15:3)
    at ln (injected-web.js:1:16297)
    at New script.user.js:1:21
    at async en (injected.js:1:9149)
    at async injected.js:1:15507
(anonymous)	@	New script.user.js:14
VMhoyus2mzbo6	@	New script.user.js:15
ln	@	injected-web.js:1
(anonymous)	@	New script.user.js:1

Technical Analysis:
This bug occurs in compiled injected-web.js.
In source code, it occurs in makeOwnKeys within gm-global-wrapper.js due to a synchronization gap between the static global snapshot and materialized properties:

  1. In makeGlobalKeys, when ok === true, globalKeys returns the full names array from builtinGlobals[0], which includes setInterval and setTimeout.
  2. However, these two keys are explicitly skipped during the initialization of globalKeysSet because they exist in builtinFuncs. As a result, globalKeysSet.get('setInterval') returns undefined.
  3. Once a script accesses window.setInterval or window.setTimeout, proxyDescribe "materializes" the property by calling defineProperty(local, name, desc). Now, these keys exist on the local object.
  4. When Reflect.ownKeys(window) triggers makeOwnKeys, it concatenates:
    • frameIndexes
    • globalKeys (Static snapshot: already contains setInterval)
    • reflectOwnKeys(local) filtered by notIncludedIn(globals.get)
  5. Because globals.get('setInterval') is undefined (skipped in step 2), the notIncludedIn filter returns true for the materialized key in local. Consequently, setInterval is included from both globalKeys and the filtered local keys, leading to duplicate entries.

Suggested Fix:

Use a Set to deduplicate the property array before returning it in makeOwnKeys to prevent collisions between the static snapshot and materialized properties.

function makeOwnKeys(local, globals) {
  // ... existing logic ...
  const keys = safeConcat(
    frameIndexes,
    globals === globalKeysSet ? globalKeys : globals.toArray(),
    reflectOwnKeys(local)::filter(notIncludedIn, globals.get),
  );
  return [...new Set(keys)]; // Deduplicate before returning to the Proxy trap
}

Environment:

  • OS: Windows
  • Browser: Edge 144.0.3719.82 (64)
  • Violentmonkey Version: v2.32.0

Related: #2392

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions