tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_webextension_inspected_window_access.js (10215B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 async function run_inspectedWindow_eval({ tab, codeToEval, extension }) {
      7  const fakeExtCallerInfo = {
      8    url: `moz-extension://${extension.uuid}/another/fake-caller-script.js`,
      9    lineNumber: 1,
     10    addonId: extension.id,
     11  };
     12  const commands = await CommandsFactory.forTab(tab, { isWebExtension: true });
     13  await commands.targetCommand.startListening();
     14  const result = await commands.inspectedWindowCommand.eval(
     15    fakeExtCallerInfo,
     16    codeToEval,
     17    {}
     18  );
     19  await commands.destroy();
     20  return result;
     21 }
     22 
     23 async function openAboutBlankTabWithExtensionOrigin(extension) {
     24  const tab = await BrowserTestUtils.openNewForegroundTab(
     25    gBrowser,
     26    `moz-extension://${extension.uuid}/manifest.json`
     27  );
     28  const loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, {
     29    wantLoad: "about:blank",
     30  });
     31  await ContentTask.spawn(tab.linkedBrowser, null, () => {
     32    // about:blank inherits the principal when opened from content.
     33    content.wrappedJSObject.location.assign("about:blank");
     34  });
     35  await loaded;
     36  // Sanity checks:
     37  is(tab.linkedBrowser.currentURI.spec, "about:blank", "expected tab");
     38  is(
     39    tab.linkedBrowser.contentPrincipal.originNoSuffix,
     40    `moz-extension://${extension.uuid}`,
     41    "about:blank should be at the extension origin"
     42  );
     43  return tab;
     44 }
     45 
     46 async function checkEvalResult({
     47  extension,
     48  description,
     49  url,
     50  createTab = () => BrowserTestUtils.openNewForegroundTab(gBrowser, url),
     51  expectedResult,
     52 }) {
     53  const tab = await createTab();
     54  is(tab.linkedBrowser.currentURI.spec, url, "Sanity check: tab URL");
     55  const result = await run_inspectedWindow_eval({
     56    tab,
     57    codeToEval: "'code executed at ' + location.href",
     58    extension,
     59  });
     60  BrowserTestUtils.removeTab(tab);
     61  SimpleTest.isDeeply(
     62    result,
     63    expectedResult,
     64    `eval result for devtools.inspectedWindow.eval at ${url} (${description})`
     65  );
     66 }
     67 
     68 async function checkEvalAllowed({ extension, description, url, createTab }) {
     69  info(`checkEvalAllowed: ${description} (at URL: ${url})`);
     70  await checkEvalResult({
     71    extension,
     72    description,
     73    url,
     74    createTab,
     75    expectedResult: { value: `code executed at ${url}` },
     76  });
     77 }
     78 async function checkEvalDenied({ extension, description, url, createTab }) {
     79  info(`checkEvalDenied: ${description} (at URL: ${url})`);
     80  await checkEvalResult({
     81    extension,
     82    description,
     83    url,
     84    createTab,
     85    expectedResult: {
     86      exceptionInfo: {
     87        isError: true,
     88        code: "E_PROTOCOLERROR",
     89        details: [
     90          "This extension is not allowed on the current inspected window origin",
     91        ],
     92        description: "Inspector protocol error: %s",
     93      },
     94    },
     95  });
     96 }
     97 
     98 add_task(async function test_eval_at_http() {
     99  await SpecialPowers.pushPrefEnv({
    100    set: [["dom.security.https_first", false]],
    101  });
    102 
    103  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
    104  const httpUrl = "http://example.com/";
    105 
    106  // When running with --use-http3-server, http:-URLs cannot be loaded.
    107  try {
    108    await fetch(httpUrl);
    109  } catch {
    110    info("Skipping test_eval_at_http because http:-URL cannot be loaded");
    111    return;
    112  }
    113 
    114  const extension = ExtensionTestUtils.loadExtension({});
    115  await extension.startup();
    116 
    117  await checkEvalAllowed({
    118    extension,
    119    description: "http:-URL",
    120    url: httpUrl,
    121  });
    122  await extension.unload();
    123 
    124  await SpecialPowers.popPrefEnv();
    125 });
    126 
    127 add_task(async function test_eval_at_https() {
    128  const extension = ExtensionTestUtils.loadExtension({});
    129  await extension.startup();
    130 
    131  const privilegedExtension = ExtensionTestUtils.loadExtension({
    132    isPrivileged: true,
    133  });
    134  await privilegedExtension.startup();
    135 
    136  await checkEvalAllowed({
    137    extension,
    138    description: "https:-URL",
    139    url: "https://example.com/",
    140  });
    141 
    142  await checkEvalAllowed({
    143    extension,
    144    description: "https:-URL with opaque origin (CSP sandbox)",
    145    url: URL_ROOT_SSL + "csp_sandbox.sjs",
    146  });
    147 
    148  await checkEvalDenied({
    149    extension,
    150    description: "a restricted domain",
    151    // Domain in extensions.webextensions.restrictedDomains by browser.toml.
    152    url: "https://test2.example.com/",
    153  });
    154 
    155  await SpecialPowers.pushPrefEnv({
    156    set: [["extensions.quarantinedDomains.list", "example.com"]],
    157  });
    158 
    159  await checkEvalDenied({
    160    extension,
    161    description: "a quarantined domain",
    162    url: "https://example.com/",
    163  });
    164 
    165  await checkEvalAllowed({
    166    extension: privilegedExtension,
    167    description: "a quarantined domain",
    168    url: "https://example.com/",
    169  });
    170 
    171  await SpecialPowers.popPrefEnv();
    172 
    173  await extension.unload();
    174  await privilegedExtension.unload();
    175 });
    176 
    177 add_task(async function test_eval_at_sandboxed_page() {
    178  const extension = ExtensionTestUtils.loadExtension({});
    179  await extension.startup();
    180 
    181  await checkEvalAllowed({
    182    extension,
    183    description: "page with CSP sandbox",
    184    url: "https://example.com/document-builder.sjs?headers=Content-Security-Policy:sandbox&html=x",
    185  });
    186  await checkEvalDenied({
    187    extension,
    188    description: "restricted domain with CSP sandbox",
    189    url: "https://test2.example.com/document-builder.sjs?headers=Content-Security-Policy:sandbox&html=x",
    190  });
    191 
    192  await extension.unload();
    193 });
    194 
    195 add_task(async function test_eval_at_own_extension_origin_allowed() {
    196  const extension = ExtensionTestUtils.loadExtension({
    197    background() {
    198      // eslint-disable-next-line no-undef
    199      browser.test.sendMessage(
    200        "blob_url",
    201        URL.createObjectURL(new Blob(["blob: here", { type: "text/html" }]))
    202      );
    203    },
    204    files: {
    205      "mozext.html": `<!DOCTYPE html>moz-extension: here`,
    206    },
    207  });
    208  await extension.startup();
    209  const blobUrl = await extension.awaitMessage("blob_url");
    210 
    211  await checkEvalAllowed({
    212    extension,
    213    description: "moz-extension:-URL from own extension",
    214    url: `moz-extension://${extension.uuid}/mozext.html`,
    215  });
    216  await checkEvalAllowed({
    217    extension,
    218    description: "blob:-URL from own extension",
    219    url: blobUrl,
    220  });
    221  await checkEvalAllowed({
    222    extension,
    223    description: "about:blank with origin from own extension",
    224    url: "about:blank",
    225    createTab: () => openAboutBlankTabWithExtensionOrigin(extension),
    226  });
    227 
    228  await extension.unload();
    229 });
    230 
    231 add_task(async function test_eval_at_other_extension_denied() {
    232  // The extension for which we simulate devtools_page, chosen as caller of
    233  // devtools.inspectedWindow.eval API calls.
    234  const extension = ExtensionTestUtils.loadExtension({});
    235  await extension.startup();
    236 
    237  // The other extension, that |extension| should not be able to access:
    238  const otherExt = ExtensionTestUtils.loadExtension({
    239    background() {
    240      // eslint-disable-next-line no-undef
    241      browser.test.sendMessage(
    242        "blob_url",
    243        URL.createObjectURL(new Blob(["blob: here", { type: "text/html" }]))
    244      );
    245    },
    246    files: {
    247      "mozext.html": `<!DOCTYPE html>moz-extension: here`,
    248    },
    249  });
    250  await otherExt.startup();
    251  const otherExtBlobUrl = await otherExt.awaitMessage("blob_url");
    252 
    253  await checkEvalDenied({
    254    extension,
    255    description: "moz-extension:-URL from another extension",
    256    url: `moz-extension://${otherExt.uuid}/mozext.html`,
    257  });
    258  await checkEvalDenied({
    259    extension,
    260    description: "blob:-URL from another extension",
    261    url: otherExtBlobUrl,
    262  });
    263  await checkEvalDenied({
    264    extension,
    265    description: "about:blank with origin from another extension",
    266    url: "about:blank",
    267    createTab: () => openAboutBlankTabWithExtensionOrigin(otherExt),
    268  });
    269 
    270  await otherExt.unload();
    271  await extension.unload();
    272 });
    273 
    274 add_task(async function test_eval_at_about() {
    275  const extension = ExtensionTestUtils.loadExtension({});
    276  await extension.startup();
    277  await checkEvalAllowed({
    278    extension,
    279    description: "about:blank (null principal)",
    280    url: "about:blank",
    281  });
    282  await checkEvalDenied({
    283    extension,
    284    description: "about:addons (system principal)",
    285    url: "about:addons",
    286  });
    287  await checkEvalDenied({
    288    extension,
    289    description: "about:robots (about page)",
    290    url: "about:robots",
    291  });
    292  await extension.unload();
    293 });
    294 
    295 add_task(async function test_eval_at_file() {
    296  // FYI: There is also an equivalent test case with a full end-to-end test at:
    297  // browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_file.js
    298 
    299  const extension = ExtensionTestUtils.loadExtension({});
    300  await extension.startup();
    301 
    302  // A dummy file URL that can be loaded in a tab.
    303  const fileUrl =
    304    "file://" +
    305    getTestFilePath("browser_webextension_inspected_window_access.js");
    306 
    307  // checkEvalAllowed test helper cannot be used, because the file:-URL may
    308  // redirect elsewhere, so the comparison with the full URL fails.
    309  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, fileUrl);
    310  const result = await run_inspectedWindow_eval({
    311    tab,
    312    codeToEval: "'code executed at ' + location.protocol",
    313    extension,
    314  });
    315  BrowserTestUtils.removeTab(tab);
    316  SimpleTest.isDeeply(
    317    result,
    318    { value: "code executed at file:" },
    319    `eval result for devtools.inspectedWindow.eval at ${fileUrl}`
    320  );
    321 
    322  await extension.unload();
    323 });
    324 
    325 add_task(async function test_eval_at_view_source() {
    326  const extension = ExtensionTestUtils.loadExtension({});
    327  await extension.startup();
    328  const otherExt = ExtensionTestUtils.loadExtension({});
    329  await otherExt.startup();
    330 
    331  await checkEvalDenied({
    332    extension,
    333    description: "view-source at https:-URL",
    334    url: "view-source:https://example.com/?somepage",
    335  });
    336  await checkEvalDenied({
    337    extension,
    338    description: "view-source at https:-URL with opaque origin (CSP sandbox)",
    339    url: `view-source:${URL_ROOT_SSL}csp_sandbox.sjs`,
    340  });
    341  await checkEvalDenied({
    342    extension,
    343    description: "view-source from own extension",
    344    url: `view-source:moz-extension://${extension.uuid}/manifest.json?`,
    345  });
    346  await checkEvalDenied({
    347    extension,
    348    description: "view-source from another extension",
    349    url: `view-source:moz-extension://${otherExt.uuid}/manifest.json?`,
    350  });
    351 
    352  await otherExt.unload();
    353  await extension.unload();
    354 });