2022 answer
Now that we have the Proxy API, there's no fail proof way to determine if a native function was overridden or not.
For example:
function isNativeFunction(f) {
return f.toString().includes("[native code]");
}
window.fetch = new Proxy(window.fetch, {
apply: function (target, thisArg, argumentsList) {
console.log("Fetch call intercepted:", ...argumentsList);
Reflect.apply(...arguments);
},
});
window.fetch.toString(); // → "function fetch() { [native code] }"
isNativeFunction(window.fetch); // → true
Following the specs, a proxied object should be indistinguishable from its target. Some runtimes (e.g., Node.js) offers some utilities to go against the spec and check if an object is proxied, but in the browser the only way to do so is by monkey patching the Proxy API itself before any proxy is applied.
So, back to the original question — I think the only available option nowadays is to hold a reference of the “clean” native function and, later on, compare your potentially monkey patched function with it:
<html>
<head>
<script>
// Store a reference of the original "clean" native function before any
// other script has a chance to modify it.
// In this case, we're just holding a reference of the original fetch API
// and hide it behind a closure. If you don't know in advance what API
// you'll want to check, you might need to store a reference to multiple
// `window` objects.
(function () {
const { fetch: originalFetch } = window;
window.__isFetchMonkeyPatched = function () {
return window.fetch !== originalFetch;
};
})();
// From now on, you can check if the fetch API has been monkey patched
// by invoking window.__isFetchMonkeyPatched().
//
// Example:
window.fetch = new Proxy(window.fetch, {
apply: function (target, thisArg, argumentsList) {
console.log("Fetch call intercepted:", ...argumentsList);
Reflect.apply(...arguments);
},
});
window.__isFetchMonkeyPatched(); // → true
</script>
</head>
</html>
By using a strict reference check, we avoid all toString()
loopholes. And it even works on proxies because they can’t trap equality comparisons.
The main drawback of this approach is that it can be impractical. It requires storing the original function reference before running any other code in your app (to ensure it’s still untouched), which sometimes you won’t be able to do (e.g., if you’re building a library).
For more info, I recently wrote an article going in-depth into the available approaches for determining if a native function was monkey patched. You can find it here.