fixhunt

biome suspicious/noPrototypeBuiltins: fix changes program behavior

fix-changes-behaviorlabelled safeintended-change · lowrelated
Findings / 2026-10-17T21-09-20-346Z-6n46-biome-suspicious_noPrototypeBuiltins / r2-p005biome 2.5.1520 → 11 lines2026-10-17 21:09 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

The fix removes any opportunity for side effects in the retrieval of foo.hasOwnProperty—such as logging in a get trap or modifying state—because it no longer accesses the property on foo at all, where

Program

before
1let accessCount = 0;
2const handler = {
3 get(target, prop) {
4 if (prop === 'hasOwnProperty') {
5 console.log('Trap hit for hasOwnProperty, access #', accessCount);
6 }
7 return target[prop];
8 }
9};
10const obj = new Proxy({ foo: 1 }, handler);
11obj.hasOwnProperty('foo');
after
1let accessCount = 0;
2const handler = {
3 get(target, prop) {
4 if (prop === 'hasOwnProperty') {
5 console.log('Trap hit for hasOwnProperty, access #', accessCount);
6 }
7 return target[prop];
8 }
9};
10const obj = new Proxy({ foo: 1 }, handler);
11Object.hasOwn(obj, 'foo');

Output

stdout beforeexit 0
Trap hit for hasOwnProperty, access # 0
stdout afterexit 0
(no stdout)
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory intended-change · low

The rule explicitly rewrites direct calls like obj.hasOwnProperty('foo') to Object.hasOwn(obj, 'foo') to bypass any shadowed or custom hasOwnProperty on the object. The program demonstrates this by using a Proxy that traps access to hasOwnProperty; the fix intentionally avoids that trap, changing the observable behavior exactly as the documentation describes.

Explanation Nemotron 3 Super unverified

The original call `obj.hasOwnProperty('foo')` invokes the proxy’s `get` trap because property lookup on `obj` triggers it; when `prop` equals `'hasOwnProperty'`, the trap logs before returning the inherited method. The replacement `Object.hasOwn(obj, 'foo')` is a direct function call that does not access any property on `obj`, so the proxy’s `get` trap is never invoked and no logging occurs. Thus the rewrite changes observable behavior.

Reproduce

file
prog.js (the before program)
minimized program
r2-p005.js
evidence sheet
r2-p005.md
regression fixture
r2-p005.js the minimized program with a header stating the observed change, for the tool's test suite
node prog.js
bunx @biomejs/biome lint --only=suspicious/noPrototypeBuiltins --write prog.js
node prog.js

Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra

noPrototypeBuiltins fix · suspicious noPrototypeBuiltins proxy trap · Proxy object hasOwnProperty call · handler get method interception

  • related biomejs/biome/issues/10769 useLiteralKeys flags __proto__ in computed member expression, same rule family (noPrototypeBuiltins) but different construct (computed member vs direct property access)

Details

program
Property Access Trap Side Effect Lost
hazard
The fix removes any opportunity for side effects in the retrieval of foo.hasOwnProperty—such as logging in a get trap or modifying state—because it no longer accesses the property on foo at all, where
oracle
stdout before: Trap hit for hasOwnProperty, access # 0 stdout after:
run
2026-10-17T21-09-20-346Z-6n46-biome-suspicious_noPrototypeBuiltins
generated program (20 lines) and the fix's diff
1let accessCount = 0;
2const handler = {
3 get(target, prop) {
4 accessCount++;
5 if (prop === 'hasOwnProperty') {
6 console.log('Trap hit for hasOwnProperty, access #', accessCount);
7 return Object.prototype.hasOwnProperty;
8 }
9 return target[prop];
10 }
11};
12const obj = new Proxy({ foo: 1 }, handler);
13accessCount = 0;
14console.log('Before (original):');
15obj.hasOwnProperty('foo');
16console.log('Access count after original:', accessCount);
17accessCount = 0;
18console.log('\nAfter (fixed):');
19Object.prototype.hasOwnProperty.call(obj, 'foo');
20console.log('Access count after fixed:', accessCount);
--- orig.js	2026-10-17 17:09:47.033115103 -0400
+++ fixed.js	2026-10-17 17:09:47.306117955 -0400
@@ -12,9 +12,9 @@
 const obj = new Proxy({ foo: 1 }, handler);
 accessCount = 0;
 console.log('Before (original):');
-obj.hasOwnProperty('foo');
+Object.hasOwn(obj, 'foo');
 console.log('Access count after original:', accessCount);
 accessCount = 0;
 console.log('\nAfter (fixed):');
-Object.prototype.hasOwnProperty.call(obj, 'foo');
+Object.hasOwn(obj, 'foo');
 console.log('Access count after fixed:', accessCount);
rule documentation given to the models
Summary

- Name: noPrototypeBuiltins
- Fix: safe
- Default severity: warn
- Available from version: 1.1.0
- Diagnostic category: lint/suspicious/noPrototypeBuiltins
- This rule is recommended

Description

 Disallow direct use of `Object.prototype` builtins.

 ECMAScript 5.1 added `Object.create` which allows the creation of an object with a custom prototype.
 This pattern is often used for objects used as Maps. However, this pattern can lead to errors
 if something else relies on prototype properties/methods.
 Moreover, the methods could be shadowed, this can lead to random bugs and denial of service
 vulnerabilities. For example, calling `hasOwnProperty` directly on parsed JSON like `{"hasOwnProperty": 1}` could lead to vulnerabilities.
 To avoid subtle bugs like this, you should call these methods from `Object.prototype`.
 For example, `foo.isPrototypeOf(bar)` should be replaced with `Object.prototype.isPrototypeOf.call(foo, "bar")`
 As for the `hasOwn` method, `foo.hasOwn("bar")` should be replaced with `Object.hasOwn(foo, "bar")`.

Examples

Invalid

 ```js,expect_diagnostic
 var invalid = foo.hasOwnProperty("bar");
 ```

 ```js,expect_diagnostic
 var invalid = foo.isPrototypeOf(bar);
 ```

 ```js,expect_diagnostic
 var invalid = foo.propertyIsEnumerable("bar");
 ```

 ```js,expect_diagnostic
 Object.hasOwnProperty.call(foo, "bar");
 ```

Valid

 ```js
 var valid = Object.hasOwn(foo, "bar");
 var valid = Object.prototype.isPrototypeOf.call(foo, bar);
 var valid = {}.propertyIsEnumerable.call(foo, "bar");
 ```