CVE-2024-34714: Insufficient Information (NVD-CWE-noinfo)
value: The Hoppscotch Browser Extension is a browser extension for Hoppscotch, a community-driven end-to-end open-source API development ecosystem. Due to an oversight during a change made to the extension in the commit d4e8e4830326f46ba17acd1307977ecd32a85b58, a critical check for the origin list was missed and allowed for messages to be sent to the extension which the extension gladly processed and responded back with the results of, while this wasn't supposed to happen and be blocked by the origin not being present in the origin list.

This vulnerability exposes Hoppscotch Extension users to sites which call into Hoppscotch Extension APIs internally. This fundamentally allows any site running on the browser with the extension installed to bypass CORS restrictions if the user is running extensions with the given version. This security hole was patched in the commit 7e364b928ab722dc682d0fcad713a96cc38477d6 which was released along with the extension version `0.35`. As a workaround, Chrome users can use the Extensions Settings to disable the extension access to only the origins that you want. Firefox doesn't have an alternative to upgrading to a fixed version.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-05-14T16:17Z
Repository: https://github.com/hoppscotch/hoppscotch-extension
Language: JSON
Files changed: .parcelrc, manifest.chrome.json, manifest.common.json, manifest.firefox.json, mergeManifest.js, package.json, pnpm-lock.yaml, src/contentScript.ts, src/hookContent.js
Commit date: 2023-12-13 16:21:20 +0530
Commit message: chore: firefox manifest v3 support (#232)

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
Diff: diff --git a/.parcelrc b/.parcelrc
index d1e2701..6593efa 100644
--- a/.parcelrc
+++ b/.parcelrc
@@ -1,6 +1,11 @@
 {
   "extends": "@parcel/config-default",
   "transformers": {
-    "*.ttf": ["@parcel/transformer-raw"]
-  }
-}
\ No newline at end of file
+    "*.ttf": [
+      "@parcel/transformer-raw"
+    ]
+  },
+  "reporters": [
+    "./mergeManifest.js"
+  ]
+}
diff --git a/manifest.chrome.json b/manifest.chrome.json
new file mode 100644
index 0000000..f587326
--- /dev/null
+++ b/manifest.chrome.json
@@ -0,0 +1,5 @@
+{
+  "background": {
+    "service_worker": "index.js"
+  }
+}
diff --git a/manifest.json b/manifest.common.json
similarity index 67%
rename from manifest.json
rename to manifest.common.json
index 8905b45..6e5c5ae 100644
--- a/manifest.json
+++ b/manifest.common.json
@@ -1,34 +1,21 @@
 {
   "manifest_version": 3,
   "name": "Hoppscotch Browser Extension",
-  "version": "0.26",
+  "version": "0.27",
   "description": "Provides more capabilities for Hoppscotch",
   "icons": {
     "16": "icons/icon-16x16.png",
     "48": "icons/icon-48x48.png",
     "128": "icons/icon-128x128.png"
   },
-  "background": {
-    "service_worker": "index.js"
-  },
   "action": {
     "default_title": "Hoppscotch Extension",
     "default_popup": "popup.html"
   },
   "permissions": ["storage", "tabs", "cookies", "scripting"],
   "host_permissions": ["<all_urls>"],
-  "applications": {
-    "gecko": {
-      "id": "postwoman-firefox@postwoman.io"
-    }
-  },
-  "browser_specific_settings": {
-    "gecko": {
-      "id": "postwoman-firefox@postwoman.io"
-    }
-  },
   "web_accessible_resources": [
     {
       "resources": ["*.js.map"],
       "matches": ["<all_urls>"]
     }
diff --git a/manifest.firefox.json b/manifest.firefox.json
new file mode 100644
index 0000000..9bdc5ad
--- /dev/null
+++ b/manifest.firefox.json
@@ -0,0 +1,10 @@
+{
+  "background": {
+    "scripts": ["index.js"]
+  },
+  "browser_specific_settings": {
+    "gecko": {
+      "id": "postwoman-firefox@postwoman.io"
+    }
+  }
+}
diff --git a/mergeManifest.js b/mergeManifest.js
new file mode 100644
index 0000000..a7c61dc
--- /dev/null
+++ b/mergeManifest.js
@@ -0,0 +1,50 @@
+const { Reporter } = require("@parcel/plugin")
+const fs = require("fs").promises
+
+module.exports = new Reporter({
+  async report({ event }) {
+    const target = process.env.HOPP_EXTENSION_TARGET
+
+    if (!target) {
+      return
+    }
+
+    if (event.type == "buildSuccess") {
+      const manifestCommon = (
+        await fs.readFile("./manifest.common.json")
+      ).toString()
+
+      let targetManifestFilePath
+
+      if (target == "CHROME") {
+        targetManifestFilePath = "./manifest.chrome.json"
+      } else if (target == "FIREFOX") {
+        targetManifestFilePath = "./manifest.firefox.json"
+      } else {
+        return
+      }
+
+      const targetSpecificManifest = (
+        await fs.readFile(targetManifestFilePath)
+      ).toString()
+
+      const manifestFinal = JSON.stringify(
+        {
+          ...JSON.parse(manifestCommon),
+          ...JSON.parse(targetSpecificManifest),
+        },
+        null,
+        2
+      )
+
+      // make sure the ./dist folder exists
+      await fs.mkdir("./dist").catch(() => {})
+
+      await fs.writeFile("./dist/manifest.json", manifestFinal, {
+        flag: "w",
+      })
+
+      process.stdout.write("💚 Manifest File Written Successfully")
+    }
+  },
+})
diff --git a/package.json b/package.json
index cb67db8..5660b82 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,20 @@
 {
   "name": "hoppscotch-extension",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "description": "Provides more features to the Hoppscotch webapp (https://hoppscotch.io/)",
   "scripts": {
-    "clean": "rimraf dist",
-    "build": "pnpm exec parcel build src/* --dist-dir dist/ && pnpm exec copyfiles manifest.json dist && pnpm exec copyfiles icons/* dist"
+    "clean": "rimraf dist .parcel-cache",
+    "build:chrome": "HOPP_EXTENSION_TARGET=CHROME parcel build src/* --dist-dir dist/ && copyfiles icons/* dist",
+    "build:firefox": "HOPP_EXTENSION_TARGET=FIREFOX parcel build src/* --dist-dir dist/ && copyfiles icons/* dist"
   },
   "author": "Andrew Bastin",
   "license": "MIT",
   "devDependencies": {
     "@parcel/config-default": "^2.10.0",
     "@parcel/core": "^2.10.0",
+    "@parcel/plugin": "^2.10.0",
     "@parcel/transformer-raw": "^2.10.0",
     "@types/chrome": "^0.0.246",
     "@types/node": "^17.0.23",
     "buffer": "^6.0.3",
     "copyfiles": "^2.4.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a768cea..9e370e1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,10 +17,13 @@ devDependencies:
     specifier: ^2.10.0
     version: 2.10.0(@parcel/core@2.10.0)(typescript@5.2.2)
   '@parcel/core':
     specifier: ^2.10.0
     version: 2.10.0
+  '@parcel/plugin':
+    specifier: ^2.10.0
+    version: 2.10.0(@parcel/core@2.10.0)
   '@parcel/transformer-raw':
     specifier: ^2.10.0
     version: 2.10.0(@parcel/core@2.10.0)
   '@types/chrome':
     specifier: ^0.0.246
diff --git a/src/contentScript.ts b/src/contentScript.ts
index d32268f..f8eb25a 100644
--- a/src/contentScript.ts
+++ b/src/contentScript.ts
@@ -1,5 +1,18 @@
+const fs = require("fs")
+
+const hookContent = fs.readFileSync(__dirname + "/hookContent.js", {
+  encoding: "utf-8",
+})
+
+const hookContentInvalidOrigin = fs.readFileSync(
+  __dirname + "/hookContentInvalidOrigin.js",
+  {
+    encoding: "utf-8",
+  }
+)
+
 export type HOOK_MESSAGE = {
   type: "execute_hook"
   origin_type: "VALID_ORIGIN" | "UNKNOWN_ORIGIN"
 }
 
@@ -25,16 +38,27 @@ chrome.storage.onChanged.addListener((changes, _areaName) => {
 async function injectHoppExtensionHook() {
   let originList = await getOriginList()
 
   let url = new URL(window.location.href)
 
-  chrome.runtime.sendMessage(<HOOK_MESSAGE>{
-    type: "execute_hook",
-    origin_type: originList.includes(url.origin)
-      ? "VALID_ORIGIN"
-      : "UNKNOWN_ORIGIN",
-  })
+  const originType = originList.includes(url.origin)
+    ? "VALID_ORIGIN"
+    : "UNKNOWN_ORIGIN"
+
+  if (process.env.HOPP_EXTENSION_TARGET === "FIREFOX") {
+    const script = document.createElement("script")
+    script.textContent = originList.includes(url.origin)
+      ? hookContent
+      : hookContentInvalidOrigin
+    document.documentElement.appendChild(script)
+    script.parentNode.removeChild(script)
+  } else {
+    chrome.runtime.sendMessage(<HOOK_MESSAGE>{
+      type: "execute_hook",
+      origin_type: originType,
+    })
+  }
 }
 
 window.addEventListener("message", (ev) => {
   if (ev.source !== window || !ev.data) {
     return
@@ -72,11 +96,11 @@ window.addEventListener("message", (ev) => {
       messageType: "cancel-req",
     })
   }
 })
 
-const VERSION = { major: 0, minor: 26 }
+const VERSION = { major: 0, minor: 27 }
 
 injectHoppExtensionHook()
 
 chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
   if (msg.action === "__POSTWOMAN_EXTENSION_PING__") {
diff --git a/src/hookContent.js b/src/hookContent.js
index ae4569e..52af2bd 100644
--- a/src/hookContent.js
+++ b/src/hookContent.js
@@ -34,11 +34,11 @@
       status: "available",
     })
   }
 
   window.__POSTWOMAN_EXTENSION_HOOK__ = {
-    getVersion: () => ({ major: 0, minor: 26 }),
+    getVersion: () => ({ major: 0, minor: 27 }),
 
     decodeB64ToArrayBuffer: (input, ab) => {
       const keyStr =
         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
 

Vulnerable code:     "*.ttf": ["@parcel/transformer-raw"]
  }
}
  "version": "0.26",
  "background": {
    "service_worker": "index.js"
  },
  "applications": {
    "gecko": {
      "id": "postwoman-firefox@postwoman.io"
    }
  },
  "browser_specific_settings": {
    "gecko": {
      "id": "postwoman-firefox@postwoman.io"
    }
  },
  "version": "0.26.0",
    "clean": "rimraf dist",
    "build": "pnpm exec parcel build src/* --dist-dir dist/ && pnpm exec copyfiles manifest.json dist && pnpm exec copyfiles icons/* dist"
  chrome.runtime.sendMessage(<HOOK_MESSAGE>{
    type: "execute_hook",
    origin_type: originList.includes(url.origin)
      ? "VALID_ORIGIN"
      : "UNKNOWN_ORIGIN",
  })
const VERSION = { major: 0, minor: 26 }
    getVersion: () => ({ major: 0, minor: 26 }),
Fixed code:     "*.ttf": [
      "@parcel/transformer-raw"
    ]
  },
  "reporters": [
    "./mergeManifest.js"
  ]
}
{
  "background": {
    "service_worker": "index.js"
  }
}
  "version": "0.27",
{
  "background": {
    "scripts": ["index.js"]
  },
  "browser_specific_settings": {
    "gecko": {
      "id": "postwoman-firefox@postwoman.io"
    }
  }
}
const { Reporter } = require("@parcel/plugin")
const fs = require("fs").promises

module.exports = new Reporter({
  async report({ event }) {
    const target = process.env.HOPP_EXTENSION_TARGET

    if (!target) {
      return
    }

    if (event.type == "buildSuccess") {
      const manifestCommon = (
        await fs.readFile("./manifest.common.json")
      ).toString()

      let targetManifestFilePath

      if (target == "CHROME") {
        targetManifestFilePath = "./manifest.chrome.json"
      } else if (target == "FIREFOX") {
        targetManifestFilePath = "./manifest.firefox.json"
      } else {
        return
      }

      const targetSpecificManifest = (
        await fs.readFile(targetManifestFilePath)
      ).toString()

      const manifestFinal = JSON.stringify(
        {
          ...JSON.parse(manifestCommon),
          ...JSON.parse(targetSpecificManifest),
        },
        null,
        2
      )

      // make sure the ./dist folder exists
      await fs.mkdir("./dist").catch(() => {})

      await fs.writeFile("./dist/manifest.json", manifestFinal, {
        flag: "w",
      })

      process.stdout.write("💚 Manifest File Written Successfully")
    }
  },
})
  "version": "0.27.0",
    "clean": "rimraf dist .parcel-cache",
    "build:chrome": "HOPP_EXTENSION_TARGET=CHROME parcel build src/* --dist-dir dist/ && copyfiles icons/* dist",
    "build:firefox": "HOPP_EXTENSION_TARGET=FIREFOX parcel build src/* --dist-dir dist/ && copyfiles icons/* dist"
    "@parcel/plugin": "^2.10.0",
  '@parcel/plugin':
    specifier: ^2.10.0
    version: 2.10.0(@parcel/core@2.10.0)
const fs = require("fs")

const hookContent = fs.readFileSync(__dirname + "/hookContent.js", {
  encoding: "utf-8",
})

const hookContentInvalidOrigin = fs.readFileSync(
  __dirname + "/hookContentInvalidOrigin.js",
  {
    encoding: "utf-8",
  }
)

  const originType = originList.includes(url.origin)
    ? "VALID_ORIGIN"
    : "UNKNOWN_ORIGIN"

  if (process.env.HOPP_EXTENSION_TARGET === "FIREFOX") {
    const script = document.createElement("script")
    script.textContent = originList.includes(url.origin)
      ? hookContent
      : hookContentInvalidOrigin
    document.documentElement.appendChild(script)
    script.parentNode.removeChild(script)
  } else {
    chrome.runtime.sendMessage(<HOOK_MESSAGE>{
      type: "execute_hook",
      origin_type: originType,
    })
  }
const VERSION = { major: 0, minor: 27 }
    getVersion: () => ({ major: 0, minor: 27 }),