artlung
artlung

Reputation: 34013

Detecting if code is being run as a Chrome Extension

I am working with some code that needs to be run as a page, and if it's being run as a Chrome Extension, I want to be able to do additional things. What I'm using is:

<script>
if (chrome && chrome.extension) {
    // extension stuff
}
</script>

This seems like a good capability detection. Using user agent string causes me trouble because it's the same no matter the context (web page vs. extension).

Question: are there other more reliable techniques for detecting if a piece of code is running inside a Chrome extension?

Update: I'm wondering if there's something I can put into my manifest.json file that I can then read back. Note, that the extension I'm working on is not intended as a persistent thing that runs all the time, it's a content application that runs in a single window or browser tab and has no need to interact with other windows or tabs or anything else.

Upvotes: 25

Views: 7332

Answers (8)

brunoais
brunoais

Reputation: 6836

Credit to Chad Scira for the original answer mine is based of.


I grabbed Chad's answer and I compacted it to ES2021 standards. Removed a lot of repeated content required for previous versions too.

const runningAt = (() => {
    let getBackgroundPage = chrome?.extension?.getBackgroundPage;
    if (getBackgroundPage){
        return getBackgroundPage() === window ? 'BACKGROUND' : 'POPUP';
    }
    return chrome?.runtime?.onMessage ? 'CONTENT' : 'WEB';
})();

The above should detect the 4 scenarios

  • javascript is run in a background page
  • javascript is run in a popup page / iframe
  • javascript is run in a context script
  • javascript is run in a directly on a website

Upvotes: 1

Chad
Chad

Reputation: 9859

I had the need for something similar.

But I didn't need to care about sites attempting to trick the code.

const SCRIPT_TYPE = (() => {
    if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() === window) {
        return 'BACKGROUND';
    } else if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() !== window) {
        return 'POPUP';
    } else if (!chrome || !chrome.runtime || !chrome.runtime.onMessage) {
        return 'WEB';
    } else {
        return 'CONTENT';
    }
})();

The above should detect the 4 scenarios

  • javascript is run in a background page
  • javascript is run in a popup page / iframe
  • javascript is run in a context script
  • javascript is run in a directly on a website

Upvotes: 8

redisko
redisko

Reputation: 649

To detect chrome apps and extensions use:

chrome.app.getDetails()

Upvotes: 0

Atif Hussain
Atif Hussain

Reputation: 898

Chrome does not provide any direct API to check the running status of the application that is it running in extension popup or in chrome page view. however, One indirect trick can work is we can match the extension body resolution that is equal or less than as specified in CSS (in this case, extension popup will be open) or greater than that (in this case, web page view will be open).

Upvotes: 1

ttsvetkov
ttsvetkov

Reputation: 51

I noticed that in Chrome, the chrome object, that is property of the global window object, could not be deleted. If it is user-defined property delete operation is successful. So you could test in this way:

var isRunningInExtension = (!(delete window.chrome) && chrome.extension) ? 
                           true : false;

UPDATE: Line above does not really guarantee that code is running in chrome extension. Everyone can create an object called 'chrome' with an 'extension' property and then freeze/seal that object - doing this will be enough to pass the check and have an incorrect result that your code is running inside a chrome extension.

The ensure you are running your code in an extension, you have to test the global chrome object before any javascript to be run - that way you will have guarantee that no fake chrome object is created before testing.

One possible solution is to use an iframe - in my example below i use iframe's sandbox property to instruct the iframe not to execute any scripts(even scripts included with script tag) - in that way i can ensure no script will be able to modify the global window.chrome object.

(function() {
  var isChromeExtension = (function () {
    var contentWindow,
        iframe = document.createElement("iframe"),
        isChromeExtension;
    // test for sandbox support. It is supported in most recent version of Chrome
    if ("sandbox" in iframe) {
      try {
        iframe.sandbox = "allow-same-origin";
        iframe.src=location.href;
        iframe.style="display: none";
        document.body.appendChild(iframe);
        contentWindow = iframe.contentWindow;
        isChromeExtension = !!(contentWindow.chrome && contentWindow.chrome.extension);
        document.body.removeChild(iframe);
      } catch(e) {}
    }
    return isChromeExtension;
  }());
}());

result could be:

  • true - if code is running inside an extension of Chrome
  • false - if code is not running inside an extension of Chrome
  • undefined - if browser does not support sandbox for iframes or some error happened during test

Upvotes: 1

Rob W
Rob W

Reputation: 348972

So many complicated answers here, while you can easily detect whether you're running in a Chrome extension by checking for existence and non-emptiness of chrome.runtime.id:

if (window.chrome && chrome.runtime && chrome.runtime.id) {
    // Code running in a Chrome extension (content script, background page, etc.)
}

Upvotes: 44

Debby Mendez
Debby Mendez

Reputation: 791

I know this is old, but just thought I'd offer an alternative. You can add another javascript file to the chrome extension, so it contains two .js files. manifest.json would have:

"js": ["additionalscript.js", "yourscript.js"]

The additionalscript.js could simply declare a variable var isextension = true. yourscript.js can check typeof isextension != 'undefined'.

But perhaps a more interesting example, it could declare

var adminpassword='letmein'

Now yourscript.js only has access to adminpassword when running in the extension.

(Of course you would not embed a password in a script file if the plugin were on a machine that could be compromised)

Upvotes: 1

davin
davin

Reputation: 45525

Pragmatically that's a good approach. Theoretically (not sure if this is relevant or not, e.g. may provide vulnerabilities) it can be spoofed very easily. I suppose it depends on your context how relevant that is.

Here's a slightly stronger idea:

if (chrome &&
    chrome.windows &&
    chrome.windows.get &&
    typeof chrome.windows.get === 'function' &&
    chrome.windows.get.toString() === 'function get() { [native code] }')

The idea is the same as yours, although it's slightly stronger, since AFAIK having an object be a function and having it's toString() value have that value is impossible since it's not valid syntax, so even trying to spoof that value wouldn't work unless you altered the native code (which requires a whole different level of hacker).

Don't offhand remember if checking things like this requires permissions or not, but the idea is clear I hope.

UPDATE

I just realised that the "native code" syntax idea can be fooled, by aliasing an existing function. E.g.

var FakeFn = Object.create;
FakeFn.toString(); // "function create() { [native code] }"

But that can be taken care of by careful selection of which function we use, since the name appears in the string. get is probably too common, but if we take an obscure function name (like captureVisibleTab of chrome.tabs.captureVisibleTab) that is implemented only in chrome extensions, it is still a very portable solution, because unlike the basic check where code can be fooled by other local user code, it is known in advance that the browsers don't implement any native functions with this name, so it's still safe in all browsers and with all user code.

UPDATE

As @Mathew pointed out, this idea is foolable (although seemingly only maliciously). I thought I could patch the problem by comparing to Function.prototype.toString but figured that even that can be fooled by aliasing the original toString method and creating a new one that for certain functions returns false strings and for others returns the original string.

In conclusion, my idea is slightly stronger than the original, in that it will rule out practically all chance of unintentional collision (slightly more than the OP's idea), but is certainly no defence against a malicious attack as I first thought it might be.

Upvotes: 5

Related Questions