daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

overview.md (49022B)


      1 ---
      2 title: "Browser Extension Pentesting Methodology"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/browser-extension-pentesting-methodology/README.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/browser-extension-pentesting-methodology/README.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: true
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Browser Extension Pentesting Methodology
     14 
     15 ## Basic Information
     16 
     17 Browser extensions are written in JavaScript and loaded by the browser in the background. It has its [DOM](https://www.w3schools.com/js/js_htmldom.asp) but can interact with other sites' DOMs. This means that it may compromise other sites' confidentiality, integrity, and availability (CIA).<sup>[[1]](#references)</sup>
     18 
     19 ## Main Components
     20 
     21 An extension architecture is easiest to understand as three components.<sup>[[12]](#references)</sup>
     22 
     23 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2816%29%20%281%29%20%281%29.png" alt=""><figcaption><p><a href="http://webblaze.cs.berkeley.edu/papers/Extensions.pdf">http://webblaze.cs.berkeley.edu/papers/Extensions.pdf</a></p></figcaption></figure>
     24 
     25 ### **Content Scripts**
     26 
     27 Each content script has direct access to the DOM of a **single web page** and is thereby exposed to **potentially malicious input**. However, the content script contains no permissions other than the ability to send messages to the extension core.
     28 
     29 ### **Extension Core**
     30 
     31 The extension core contains most of the extension privileges/access, but the extension core can only interact with web content via [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and content scripts. Also, the extension core does not have direct access to the host machine.
     32 
     33 ### **Native Binary**
     34 
     35 The extension allows a native binary that can **access the host machine with the user’s full privileges.** The native binary interacts with the extension core through the standard Netscape Plugin Application Programming Interface ([NPAPI](https://en.wikipedia.org/wiki/NPAPI)) used by Flash and other browser plug-ins.
     36 
     37 ### Boundaries
     38 
     39 > [!CAUTION]
     40 > To obtain the user's full privileges, an attacker must convince the extension to pass malicious input from the content script to the extension's core and from the extension's core to the native binary.
     41 
     42 Each component of the extension is separated from each other by **strong protective boundaries**. Each component runs in a **separate operating system process**. Content scripts and extension cores run in **sandbox processes** unavailable to most operating system services.
     43 
     44 Moreover, content scripts separate from their associated web pages by **running in a separate JavaScript heap**. The content script and web page have **access to the same underlying DOM**, but the two **never exchange JavaScript pointers**, preventing the leaking of JavaScript functionality.
     45 
     46 ## **`manifest.json`**
     47 
     48 A Chrome extension is just a ZIP folder with a [.crx file extension](https://www.lifewire.com/crx-file-2620391). The extension's core is the **`manifest.json`** file at the root of the folder, which specifies layout, permissions, and other configuration options.<sup>[[2]](#references)</sup>
     49 
     50 Example:
     51 
     52 ```json
     53 {
     54   "manifest_version": 2,
     55   "name": "My extension",
     56   "version": "1.0",
     57   "permissions": ["storage"],
     58   "content_scripts": [
     59     {
     60       "js": ["script.js"],
     61       "matches": ["https://example.com/*", "https://www.example.com/*"],
     62       "exclude_matches": ["*://*/*business*"]
     63     }
     64   ],
     65   "background": {
     66     "scripts": ["background.js"]
     67   },
     68   "options_ui": {
     69     "page": "options.html"
     70   }
     71 }
     72 ```
     73 
     74 ### `content_scripts`
     75 
     76 Content scripts are **loaded** whenever the user **navigates to a matching page**, in our case any page matching the **`https://example.com/*`** expression and not matching the **`*://*/*/business*`** regex. They execute **like the page’s own scripts** and have arbitrary access to the page’s [Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model).
     77 
     78 ```json
     79 "content_scripts": [
     80     {
     81       "js": [
     82         "script.js"
     83       ],
     84       "matches": [
     85         "https://example.com/*",
     86         "https://www.example.com/*"
     87       ],
     88       "exclude_matches": ["*://*/*business*"],
     89     }
     90   ],
     91 ```
     92 
     93 Additional URL filters can be expressed with **`include_globs`** and **`exclude_globs`**.<sup>[[5]](#references)</sup>
     94 
     95 This is an example content script which will add an explain button to the page when [the storage API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage) to retrieve the `message` value from extension’s storage.
     96 
     97 ```javascript
     98 chrome.storage.local.get("message", (result) => {
     99   let div = document.createElement("div")
    100   div.innerHTML = result.message + " <button>Explain</button>"
    101   div.querySelector("button").addEventListener("click", () => {
    102     chrome.runtime.sendMessage("explain")
    103   })
    104   document.body.appendChild(div)
    105 })
    106 ```
    107 
    108 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2823%29.png" alt=""><figcaption></figcaption></figure>
    109 
    110 A message is sent to the extension pages by the content script when this button is clicked, through the utilization of the [**runtime.sendMessage() API**](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage). This is due to the content script's limitation in direct access to APIs, with `storage` being among the few exceptions. For functionalities beyond these exceptions, messages are sent to extension pages which content scripts can communicate with.
    111 
    112 > [!WARNING]
    113 > Depending on the browser, the capabilities of the content script may vary slightly. For Chromium-based browsers, the capabilities list is available in the [Chrome Developers documentation](https://developer.chrome.com/docs/extensions/mv3/content_scripts/#capabilities), and for Firefox, the [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#webextension_apis) serves as the primary source.<sup>[[5]](#references)</sup>\
    114 > It is also noteworthy that content scripts have the ability to communicate with background scripts, enabling them to perform actions and relay responses back.
    115 
    116 For viewing and debugging content scripts in Chrome, the Chrome developer tools menu can be accessed from Options > More tools > Developer tools OR by pressing Ctrl + Shift + I.
    117 
    118 Upon the developer tools being displayed, the **Source tab** is to be clicked, followed by the **Content Scripts** tab. This allows for the observation of running content scripts from various extensions and the setting of breakpoints to track the execution flow.
    119 
    120 ### Injected content scripts
    121 
    122 > [!TIP]
    123 > **Content scripts are not mandatory**: extensions can also inject scripts dynamically with `chrome.scripting` (Manifest V3) or the legacy `tabs.executeScript` API. This provides more granular control over when injection occurs.<sup>[[5]](#references)</sup>
    124 
    125 For the programmatic injection of a content script, the extension is required to have [host permissions](https://developer.chrome.com/docs/extensions/reference/permissions) for the page into which the scripts are to be injected. These permissions may be secured either by **requesting them** within the manifest of the extension or on a temporary basis through [**activeTab**](https://developer.chrome.com/docs/extensions/reference/manifest/activeTab).
    126 
    127 #### Example activeTab-based extension
    128 
    129 ```json
    130 {
    131   "name": "My extension",
    132   ...
    133   "permissions": [
    134     "activeTab",
    135     "scripting"
    136   ],
    137   "background": {
    138     "service_worker": "background.js"
    139   },
    140   "action": {
    141     "default_title": "Action Button"
    142   }
    143 }
    144 ```
    145 
    146 - **Inject a JS file on click:**
    147 
    148 ```javascript
    149 // content-script.js
    150 document.body.style.backgroundColor = "orange"
    151 
    152 //service-worker.js - Inject the JS file
    153 chrome.action.onClicked.addListener((tab) => {
    154   chrome.scripting.executeScript({
    155     target: { tabId: tab.id },
    156     files: ["content-script.js"],
    157   })
    158 })
    159 ```
    160 
    161 - **Inject a function** on click:
    162 
    163 ```javascript
    164 //service-worker.js - Inject a function
    165 function injectedFunction() {
    166   document.body.style.backgroundColor = "orange"
    167 }
    168 
    169 chrome.action.onClicked.addListener((tab) => {
    170   chrome.scripting.executeScript({
    171     target: { tabId: tab.id },
    172     func: injectedFunction,
    173   })
    174 })
    175 ```
    176 
    177 #### Example with scripting permissions
    178 
    179 ```javascript
    180 // service-workser.js
    181 chrome.scripting.registerContentScripts([
    182   {
    183     id: "test",
    184     matches: ["https://*.example.com/*"],
    185     excludeMatches: ["*://*/*business*"],
    186     js: ["contentScript.js"],
    187   },
    188 ])
    189 
    190 // Another example
    191 chrome.tabs.executeScript(tabId, { file: "content_script.js" })
    192 ```
    193 
    194 ### Content Scripts `run_at`
    195 
    196 The `run_at` field controls **when JavaScript files are injected into the web page**. The preferred and default value is `"document_idle"`.
    197 
    198 The possible values are:
    199 
    200 - **`document_idle`**: Whenever possible
    201 - **`document_start`**: After any files from `css`, but before any other DOM is constructed or any other script is run.
    202 - **`document_end`**: Immediately after the DOM is complete, but before subresources like images and frames have loaded.
    203 
    204 #### Via `manifest.json`
    205 
    206 ```json
    207 {
    208   "name": "My extension",
    209   ...
    210   "content_scripts": [
    211     {
    212       "matches": ["https://*.example.com/*"],
    213       "run_at": "document_idle",
    214       "js": ["contentScript.js"]
    215     }
    216   ],
    217   ...
    218 }
    219 
    220 ```
    221 
    222 Via **`service-worker.js`**
    223 
    224 ```javascript
    225 chrome.scripting.registerContentScripts([
    226   {
    227     id: "test",
    228     matches: ["https://*.example.com/*"],
    229     runAt: "document_idle",
    230     js: ["contentScript.js"],
    231   },
    232 ])
    233 ```
    234 
    235 ### `background`
    236 
    237 Messages sent by content scripts are received by the **background page**, which serves a central role in coordinating the extension's components. Notably, the background page persists across the extension's lifetime, operating discreetly without direct user interaction. It possesses its own Document Object Model (DOM), enabling complex interactions and state management.<sup>[[7]](#references)</sup>
    238 
    239 **Key Points**:
    240 
    241 - **Background Page Role:** Acts as the nerve center for the extension, ensuring communication and coordination among various parts of the extension.
    242 - **Persistence:** It's an ever-present entity, invisible to the user but integral to the extension's functionality.
    243 - **Automatic Generation:** If not explicitly defined, the browser will automatically create a background page. This auto-generated page will include all the background scripts specified in the extension's manifest, ensuring the seamless operation of the extension's background tasks.
    244 
    245 > [!TIP]
    246 > The convenience provided by the browser in automatically generating a background page (when not explicitly declared) ensures that all necessary background scripts are integrated and operational, streamlining the extension's setup process.
    247 
    248 Example background script:
    249 
    250 ```javascript
    251 chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    252   if (request == "explain") {
    253     chrome.tabs.create({ url: "https://example.net/explanation" })
    254   }
    255 })
    256 ```
    257 
    258 It uses [runtime.onMessage API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage) to listen to messages. When an `"explain"` message is received, it uses [tabs API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs) to open a page in a new tab.
    259 
    260 To debug the background script you could go to the **extension details and inspect the service worker,** this will open the developer tools with the background script:
    261 
    262 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/browser-extension-pentesting-methodology/broken-reference" alt=""><figcaption></figcaption></figure>
    263 
    264 ### Options pages and other
    265 
    266 Browser extensions can contain various kinds of pages:
    267 
    268 - **Action pages** are displayed in a **drop-down when the extension ico**n is clicked.
    269 - Pages that the extension will **load in a new tab**.
    270 - **Option Pages**: This page displays on top of the extension when clicked. In the previous manifest In my case I was able to access this page in `chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca` or clicking:
    271 
    272 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2824%29.png" alt="" width="375"><figcaption></figcaption></figure>
    273 
    274 Note that these pages aren't persistent like background pages as they load dynamically content on necessity. Despite this, they share certain capabilities with the background page:
    275 
    276 - **Communication with Content Scripts:** Similar to the background page, these pages can receive messages from content scripts, facilitating interaction within the extension.
    277 - **Access to Extension-Specific APIs:** These pages enjoy comprehensive access to extension-specific APIs, subject to the permissions defined for the extension.
    278 
    279 ### `permissions` & `host_permissions`
    280 
    281 **`permissions`** and **`host_permissions`** are entries from the `manifest.json` that will indicate **which permissions** the browser extensions has (storage, location...) and in **which web pages**.<sup>[[3]](#references)</sup>
    282 
    283 As browser extensions can be so **privileged**, a malicious one or one being compromised could allow the attacker **different means to steal sensitive information and spy on the user**.
    284 
    285 Check how these settings work and how they could get abused in:
    286 
    287 
    288 [Browext Permissions And Host Permissions](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-permissions-and-host-permissions)
    289 
    290 ### `content_security_policy`
    291 
    292 A **content security policy** can be declared also inside the `manifest.json`. If there is one defined, it could be **vulnerable**.
    293 
    294 The default setting for browser extension pages is rather restrictive:
    295 
    296 ```bash
    297 script-src 'self'; object-src 'self';
    298 ```
    299 
    300 For more info about CSP and potential bypasses check:
    301 
    302 
    303 [Content Security Policy Csp Bypass](/hacktricks/pentesting-web/content-security-policy-csp-bypass/overview)
    304 
    305 ### `web_accessible_resources`
    306 
    307 in order for a webpage to access a page of a Browser Extension, a `.html` page for example, this page needs to be mentioned in the **`web_accessible_resources`** field of the `manifest.json`.<sup>[[4]](#references)</sup>\
    308 For example:
    309 
    310 ```javascript
    311 {
    312  ...
    313  "web_accessible_resources": [
    314    {
    315      "resources": [ "images/*.png" ],
    316      "matches": [ "https://example.com/*" ]
    317    },
    318    {
    319      "resources": [ "fonts/*.woff" ],
    320      "matches": [ "https://example.com/*" ]
    321    }
    322  ],
    323  ...
    324 }
    325 ```
    326 
    327 These pages are accesible in URL like:
    328 
    329 ```text
    330 chrome-extension://<extension-id>/message.html
    331 ```
    332 
    333 In public extensions the **extension-id is accesible**:
    334 
    335 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281194%29.png" alt="" width="375"><figcaption></figcaption></figure>
    336 
    337 Although, if the `manifest.json` parameter **`use_dynamic_url`** is used, this **id can be dynamic**.
    338 
    339 > [!TIP]
    340 > Note that even if a page is mentioned here, it might be **protected against ClickJacking** thanks to the **Content Security Policy**. So you also need to check it (frame-ancestors section) before confirming a ClickJacking attack is possible.
    341 
    342 Being allowed to access these pages make these pages **potentially vulnerable ClickJacking**:
    343 
    344 
    345 [Browext Clickjacking](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-clickjacking)
    346 
    347 > [!TIP]
    348 > Allowing these pages to be loaded only by the extension and not by random URLs could prevent ClickJacking attacks.
    349 
    350 > [!CAUTION]
    351 > Note that the pages from **`web_accessible_resources`** and other pages of the extension are also capable of **contacting background scripts**. So if one of these pages is vulnerable to **XSS** it could open a bigger vulnerability.
    352 >
    353 > Moreover, note that you can only open pages indicated in **`web_accessible_resources`** inside iframes, but from a new tab it's possible to access any page in the extension knowing the extension ID. Therefore, if an XSS is found abusing same parameters, it could be abused even if the page isn't configured in **`web_accessible_resources`**.
    354 
    355 ### `externally_connectable`
    356 
    357 A per the [**docs**](https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable)<sup>[[6]](#references)</sup>, The `"externally_connectable"` manifest property declares **which extensions and web pages can connect** to your extension via [runtime.connect](https://developer.chrome.com/docs/extensions/reference/runtime#method-connect) and [runtime.sendMessage](https://developer.chrome.com/docs/extensions/reference/runtime#method-sendMessage).
    358 
    359 - If the **`externally_connectable`** key is **not** declared in your extension's manifest or it's declared as **`"ids": ["*"]`**, **all extensions can connect, but no web pages can connect**.
    360 - If **specific IDs are specified**, like in `"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]`, **only those applications** can connect.
    361 - If **matches** are specified, those web apps will be able to connect:
    362 
    363 ```json
    364 "matches": [
    365       "https://*.google.com/*",
    366       "*://*.chromium.org/*",
    367 ```
    368 
    369 - If it's specified as empty: **`"externally_connectable": {}`**, no app or web will be able to connect.
    370 
    371 The **less extensions and URLs** indicated here, the **smaller the attack surface** will be.
    372 
    373 > [!CAUTION]
    374 > If a web page **vulnerable to XSS or takeover** is indicated in **`externally_connectable`**, an attacker will be able to **send messages directly to the background script**, completely bypassing the Content Script and its CSP.
    375 >
    376 > Therefore, this is a **very powerful bypass**.
    377 >
    378 > Moreover, if the client installs a rogue extension, it could inject **XSS data into an allowed web page** or abuse **`webRequest`** or **`declarativeNetRequest`** to alter a target page's request for a **JavaScript file**, even when it cannot communicate with the vulnerable extension directly. The target page's CSP may constrain this chain.<sup>[[14]](#references)</sup>
    379 
    380 #### Wildcard-trusted web origins to privileged action injection
    381 
    382 If an extension exposes a **high-privilege message handler** to the web via `externally_connectable`, avoid trusting a broad pattern such as `https://*.example.com/*`. A single **XSS**, **subdomain takeover**, or **vendor widget compromise** on any matching subdomain becomes equivalent to owning the extension's web-facing API.<sup>[[11]](#references)</sup>
    383 
    384 Typical exploitation path:
    385 
    386 1. Find the extension ID and enumerate `externally_connectable.matches`.
    387 2. Identify a message type that triggers a **privileged action** (`open tab`, `read page`, `submit prompt`, `fetch with extension privileges`, `native messaging`, and so on).
    388 3. Get JavaScript execution in **any** trusted origin.
    389 4. Send the request directly to the extension:
    390 
    391 ```javascript
    392 chrome.runtime.sendMessage("<extension-id>", {
    393   type: "privileged_action",
    394   payload: { attacker: "controlled" },
    395 })
    396 ```
    397 
    398 This is especially dangerous in **agentic extensions** or assistants because the "message" might be a full **instruction prompt** that is executed with the extension's host permissions.
    399 
    400 > [!CAUTION]
    401 > Treat **vendor code hosted on a first-party trusted subdomain** as part of the trust boundary. If `captcha.example.com`, `cdn.example.com`, or a support widget origin is allowed by `externally_connectable`, an XSS in that third-party component can pivot into the extension exactly as if the bug existed on the main application.
    402 
    403 #### Auditing `chrome.runtime.sendMessage` / `onMessageExternal` trust
    404 
    405 When reviewing a browser extension that accepts messages from web pages:
    406 
    407 - Search for `chrome.runtime.onMessageExternal.addListener`, `chrome.runtime.onConnectExternal.addListener`, and sensitive handlers reachable from those listeners.
    408 - Verify the extension performs **exact origin equality** checks on the sender instead of suffix, regex, or wildcard matching.
    409 - Verify the handler authorizes **message type + origin** together. A safe origin for telemetry or onboarding is not automatically safe for privileged actions.
    410 - Confirm the extension does not trust a subdomain just because it shares the parent eTLD+1.
    411 - Check whether any allowed origin hosts **third-party JavaScript**, **user-generated content**, or **legacy static assets**.
    412 
    413 Bad patterns:
    414 
    415 ```javascript
    416 if (sender.origin.endsWith(".example.com")) { /* trust */ }
    417 if (/^https:\/\/.*\.example\.com$/.test(sender.origin)) { /* trust */ }
    418 ```
    419 
    420 Prefer strict matching to the minimal set of origins:
    421 
    422 ```javascript
    423 if (sender.origin !== "https://app.example.com") return
    424 ```
    425 
    426 #### Rollback hunting on trusted static assets
    427 
    428 If the trusted origin serves **versioned static assets** or embedded widgets from predictable paths, try walking older versions looking for still-reachable vulnerable builds. This is useful when the current version is patched but the extension still trusts the origin hosting archived assets.
    429 
    430 Common patterns:
    431 
    432 - `/assets/widget/1.26.0/index.html`
    433 - `/static/app-2024.12.1/`
    434 - `/cdn/component/v1234/`
    435 
    436 Practical checks:
    437 
    438 - Start from a version observed in network traffic or page source.
    439 - Decrement semantic versions / build numbers and request older paths.
    440 - Look for `200`, `301`, or cache hits on old builds.
    441 - Review older JS bundles for DOM XSS, unsafe message handlers, or gadget endpoints that can re-establish JavaScript execution on the trusted origin.
    442 
    443 ## Communication summary
    444 
    445 ### Extension <--> WebApp
    446 
    447 To communicate between the content script and the web page post messages are usually used. Therefore, in the web application you will usually find calls to the function **`window.postMessage`** and in the content script listeners like **`window.addEventListener`**. Note however, that the extension could also **communicate with the web application sending a Post Message** (and therefore the web should expect it) or just make the web load a new script.
    448 
    449 ### Inside the extension
    450 
    451 Usually the function **`chrome.runtime.sendMessage`** is used to send a message inside the extension (usually handled by the `background` script) and in order to receive and handle it a listener is declared calling **`chrome.runtime.onMessage.addListener`**.
    452 
    453 It's also possible to use **`chrome.runtime.connect()`** to have a persistent connection instead of sending single messages, it's possible to use it to **send** and **receive** **messages** like in the following example:
    454 
    455 <details>
    456 
    457 <summary><code>chrome.runtime.connect()</code> example</summary>
    458 
    459 ```javascript
    460 var port = chrome.runtime.connect()
    461 
    462 // Listen for messages from the web page
    463 window.addEventListener(
    464   "message",
    465   (event) => {
    466     // Only accept messages from the same window
    467     if (event.source !== window) {
    468       return
    469     }
    470 
    471     // Check if the message type is "FROM_PAGE"
    472     if (event.data.type && event.data.type === "FROM_PAGE") {
    473       console.log("Content script received: " + event.data.text)
    474       // Forward the message to the background script
    475       port.postMessage({ type: "FROM_PAGE", text: event.data.text })
    476     }
    477   },
    478   false
    479 )
    480 
    481 // Listen for messages from the background script
    482 port.onMessage.addListener(function (msg) {
    483   console.log("Content script received message from background script:", msg)
    484   // Handle the response message from the background script
    485 })
    486 ```
    487 
    488 </details>
    489 
    490 It's also possible to send messages from a background script to a content script located in a specific tab calling **`chrome.tabs.sendMessage`** where you will need to indicated the **ID of the tab** to send the message to.
    491 
    492 ### From allowed `externally_connectable` to the extension
    493 
    494 **Web apps and external browser extensions allowed** in the `externally_connectable` configuration can send requests using :
    495 
    496 ```javascript
    497 chrome.runtime.sendMessage(extensionId, ...
    498 ```
    499 
    500 Where it's needed to mention the **extension ID**.
    501 
    502 ### Native Messaging
    503 
    504 It's possible for the background scripts to communicate with binaries inside the system, which might be **prone to critical vulnerabilities such as RCEs** if this communication is not properly secured. [More on this later](#native-messaging).
    505 
    506 ```javascript
    507 chrome.runtime.sendNativeMessage(
    508   "com.my_company.my_application",
    509   { text: "Hello" },
    510   function (response) {
    511     console.log("Received " + response)
    512   }
    513 )
    514 ```
    515 
    516 ## Web **↔︎** Content Script Communication
    517 
    518 The environments where **content scripts** operate and where the host pages exist are **separated** from one another, ensuring **isolation**. Despite this isolation, both have the ability to interact with the page's **Document Object Model (DOM)**, a shared resource. For the host page to engage in communication with the **content script**, or indirectly with the extension through the content script, it is required to utilize the **DOM** that is accessible by both parties as the communication channel.
    519 
    520 ### Post Messages
    521 
    522 ```javascript
    523 // This is like "chrome.runtime.sendMessage" but to maintain the connection
    524 var port = chrome.runtime.connect()
    525 
    526 window.addEventListener(
    527   "message",
    528   (event) => {
    529     // We only accept messages from ourselves
    530     if (event.source !== window) {
    531       return
    532     }
    533 
    534     if (event.data.type && event.data.type === "FROM_PAGE") {
    535       console.log("Content script received: " + event.data.text)
    536       // Forward the message to the background script
    537       port.postMessage(event.data.text)
    538     }
    539   },
    540   false
    541 )
    542 ```
    543 
    544 ```javascript
    545 document.getElementById("theButton").addEventListener(
    546   "click",
    547   () => {
    548     window.postMessage(
    549       { type: "FROM_PAGE", text: "Hello from the webpage!" },
    550       "*"
    551     )
    552   },
    553   false
    554 )
    555 ```
    556 
    557 A secure Post Message communication should check the authenticity of the received message, this can be done checking:<sup>[[1]](#references)</sup>
    558 
    559 - **`event.isTrusted`**: This is True only if the event was triggered by a users action
    560   - The content script might expecting a message only if the user performs some action
    561 - **origin domain**: might expecting a message only allowlist of domains.
    562   - If a regex is used, be very careful
    563 - **Source**: `received_message.source !== window` can be used to check if the message was **from the same window** where the Content Script is listening.
    564 
    565 The previous checks, even if performed, could be vulnerable, so check in the following page **potential Post Message bypasses**:
    566 
    567 
    568 [Postmessage Vulnerabilities](/hacktricks/pentesting-web/postmessage-vulnerabilities/overview)
    569 
    570 ### Iframe
    571 
    572 Another possible way of communication might be through **Iframe URLs**, you can find an example in:
    573 
    574 
    575 [Browext Xss Example](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-xss-example)
    576 
    577 ### DOM
    578 
    579 This isn't "exactly" a communication way, but the **web and the content script will have access to the web DOM**. So, if the **content script** is reading some information from it, **trusting the web DOM**, the web could **modify this dat**a (because the web shouldn't be trusted, or because the web is vulnerable to XSS) and **compromise the Content Script**.
    580 
    581 You can also find an example of a **DOM based XSS to compromise a browser extension** in:
    582 
    583 
    584 [Browext Xss Example](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-xss-example)
    585 
    586 ## Content Script **↔︎** Background Script Communication
    587 
    588 A Content Script can use the functions [**runtime.sendMessage()**](https://developer.chrome.com/docs/extensions/reference/runtime#method-sendMessage) **or** [**tabs.sendMessage()**](https://developer.chrome.com/docs/extensions/reference/tabs#method-sendMessage) to send a **one-time JSON-serializable** message.
    589 
    590 To handle the **response**, use the returned **Promise**. Although, for backward compatibility, you can still pass a **callback** as the last argument.
    591 
    592 Sending a request from a **content script** looks like this:
    593 
    594 ```javascript
    595 ;(async () => {
    596   const response = await chrome.runtime.sendMessage({ greeting: "hello" })
    597   // do something with response here, not outside the function
    598   console.log(response)
    599 })()
    600 ```
    601 
    602 Sending a request from the **extension** (usually a **background script**). Example of how to send message to the content script in the selected tab:
    603 
    604 ```javascript
    605 // From https://stackoverflow.com/questions/36153999/how-to-send-a-message-between-chrome-extension-popup-and-content-script
    606 ;(async () => {
    607   const [tab] = await chrome.tabs.query({
    608     active: true,
    609     lastFocusedWindow: true,
    610   })
    611   const response = await chrome.tabs.sendMessage(tab.id, { greeting: "hello" })
    612   // do something with response here, not outside the function
    613   console.log(response)
    614 })()
    615 ```
    616 
    617 On the **receiving end**, you need to set up an [**runtime.onMessage**](https://developer.chrome.com/docs/extensions/reference/runtime#event-onMessage) **event listener** to handle the message. This looks the same from a content script or extension page.
    618 
    619 ```javascript
    620 // From https://stackoverflow.com/questions/70406787/javascript-send-message-from-content-js-to-background-js
    621 chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    622   console.log(
    623     sender.tab
    624       ? "from a content script:" + sender.tab.url
    625       : "from the extension"
    626   )
    627   if (request.greeting === "hello") sendResponse({ farewell: "goodbye" })
    628 })
    629 ```
    630 
    631 In the example highlighted, **`sendResponse()`** was executed in a synchronous fashion. To modify the `onMessage` event handler for asynchronous execution of `sendResponse()`, it's imperative to incorporate `return true;`.
    632 
    633 An important consideration is that in scenarios where multiple pages are set to receive `onMessage` events, **the first page to execute `sendResponse()`** for a specific event will be the only one able to deliver the response effectively. Any subsequent responses to the same event will not be taken into account.
    634 
    635 When crafting new extensions, the preference should be towards promises as opposed to callbacks. Concerning the use of callbacks, the `sendResponse()` function is considered valid only if it's executed directly within the synchronous context, or if the event handler indicates an asynchronous operation by returning `true`. Should none of the handlers return `true` or if the `sendResponse()` function is removed from memory (garbage-collected), the callback associated with the `sendMessage()` function will be triggered by default.
    636 
    637 ## Native Messaging
    638 
    639 Browser extensions also allow to communicate with **binaries in the system via stdin**. The application must install a json indicating so in a json like:
    640 
    641 ```json
    642 {
    643   "name": "com.my_company.my_application",
    644   "description": "My Application",
    645   "path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe",
    646   "type": "stdio",
    647   "allowed_origins": ["chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"]
    648 }
    649 ```
    650 
    651 Where the `name` is the string passed to [`runtime.connectNative()`](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-connectNative) or [`runtime.sendNativeMessage()`](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-sendNativeMessage) to communicate with the application from the background scripts of the browser extension. The `path` is the path to the binary, there is only 1 valid `type` which is stdio (use stdin and stdout) and the `allowed_origins` indicate the extensions that can access it (and can't have wildcard).
    652 
    653 Chrome/Chromium will search for this json in some windows registry and some paths in macOS and Linux (more info in the [**docs**](https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging)).<sup>[[17]](#references)</sup>
    654 
    655 > [!TIP]
    656 > The browser extension also needs the `nativeMessaing` permission declared in order to be able to use this communication.
    657 
    658 This is how it looks like some background script code sending messages to a native application:
    659 
    660 ```javascript
    661 chrome.runtime.sendNativeMessage(
    662   "com.my_company.my_application",
    663   { text: "Hello" },
    664   function (response) {
    665     console.log("Received " + response)
    666   }
    667 )
    668 ```
    669 
    670 In [**this blog post**](https://spaceraccoon.dev/universal-code-execution-browser-extensions/)<sup>[[13]](#references)</sup>, a vulnerable pattern abusing native messages is proposed:
    671 
    672 1. Browser extension has a wildcard pattern for content script.
    673 2. Content script passes `postMessage` messages to the background script using `sendMessage`.
    674 3. Background script passes the message to native application using `sendNativeMessage`.
    675 4. Native application handles the message dangerously, leading to code execution.
    676 
    677 And inside of it an example of **going from any page to RCE abusing a browser extension is explained**.
    678 
    679 ## Sensitive Information in Memory/Code/Clipboard
    680 
    681 If a browser extension stores **sensitive information in its memory**, a local process dump—especially on Windows—may expose it.<sup>[[1]](#references)</sup>
    682 
    683 Therefore, the memory of the Browser Extension **shouldn't be considered secure** and **sensitive information** such as credentials or mnemonic phrases **shouldn't be stored**.
    684 
    685 Of course, do **not put sensitive information in the code**, as it will be **public**.
    686 
    687 To dump memory from the browser you could **dump the process memory** or to go to the **settings** of the browser extension click on **`Inspect pop-up`** -> In the **`Memory`** section -> **`Take a snaphost`** and **`CTRL+F`** to search inside the snapshot for sensitive info.
    688 
    689 Moreover, highly sensitive information like mnemonic keys or passwords **shouldn't be allowed to be copied in the clipboard** (or at least remove it from the clipboard in a few seconds) because then processes monitoring the clipboard will be able to get them.<sup>[[16]](#references)</sup>
    690 
    691 ## Loading an Extension in the Browser
    692 
    693 1. **Download** the Browser Extension & unzipped
    694 2. Go to **`chrome://extensions/`** and **enable** the `Developer Mode`
    695 3. Click the **`Load unpacked`** button
    696 
    697 In **Firefox** you go to **`about:debugging#/runtime/this-firefox`** and click **`Load Temporary Add-on`** button.
    698 
    699 ## Getting the source code from the store
    700 
    701 The source code of a Chrome extension can be obtained through various methods. Below are detailed explanations and instructions for each option.<sup>[[9]](#references)</sup>
    702 
    703 ### Download Extension as ZIP via Command Line
    704 
    705 The source code of a Chrome extension can be downloaded as a ZIP file using the command line. This involves using `curl` to fetch the ZIP file from a specific URL and then extracting the contents of the ZIP file to a directory. Here are the steps:
    706 
    707 1. Replace `"extension_id"` with the actual ID of the extension.
    708 2. Execute the following commands:
    709 
    710 ```bash
    711 extension_id=your_extension_id   # Replace with the actual extension ID
    712 curl -L -o "$extension_id.zip" "https://clients2.google.com/service/update2/crx?response=redirect&os=mac&arch=x86-64&nacl_arch=x86-64&prod=chromecrx&prodchannel=stable&prodversion=44.0.2403.130&x=id%3D$extension_id%26uc"
    713 unzip -d "$extension_id-source" "$extension_id.zip"
    714 ```
    715 
    716 ### Use the CRX Viewer website
    717 
    718 [https://robwu.nl/crxviewer/](https://robwu.nl/crxviewer/)
    719 
    720 ### Use the CRX Viewer extension
    721 
    722 Another convenient method is using the Chrome Extension Source Viewer, which is an open-source project. It can be installed from the [Chrome Web Store](https://chrome.google.com/webstore/detail/chrome-extension-source-v/jifpbeccnghkjeaalbbjmodiffmgedin?hl=en). The source code of the viewer is available in its [GitHub repository](https://github.com/Rob--W/crxviewer).
    723 
    724 ### View source of locally installed extension
    725 
    726 Chrome extensions installed locally can also be inspected. Here's how:
    727 
    728 1. Access your Chrome local profile directory by visiting `chrome://version/` and locating the "Profile Path" field.
    729 2. Navigate to the `Extensions/` subfolder within the profile directory.
    730 3. This folder contains all installed extensions, typically with their source code in a readable format.
    731 
    732 To identify extensions, you can map their IDs to names:
    733 
    734 - Enable Developer Mode on the `about:extensions` page to see the IDs of each extension.
    735 - Within each extension's folder, the `manifest.json` file contains a readable `name` field, helping you to identify the extension.
    736 
    737 ### Use a File Archiver or Unpacker
    738 
    739 Go to the Chrome Web Store and download the extension. The file will have a `.crx` extension. Change the file extension from `.crx` to `.zip`. Use any file archiver (like WinRAR, 7-Zip, etc.) to extract the contents of the ZIP file.
    740 
    741 ### Use Developer Mode in Chrome
    742 
    743 Open Chrome and go to `chrome://extensions/`. Enable "Developer mode" at the top right. Click on "Load unpacked extension...". Navigate to the directory of your extension. This doesn't download the source code, but it's useful for viewing and modifying the code of an already downloaded or developed extension.
    744 
    745 ## Chrome extension manifest dataset
    746 
    747 In order to try to spot vulnerable browser extensions you could use the[https://github.com/palant/chrome-extension-manifests-dataset](https://github.com/palant/chrome-extension-manifests-dataset) and check their manifest files for potentially vulnerable signs. For example to check for extensions with more than 25000 users, `content_scripts` and the permission `nativeMessaing`:<sup>[[13]](#references)</sup>
    748 
    749 ```bash
    750 # Query example from https://spaceraccoon.dev/universal-code-execution-browser-extensions/
    751 node query.js -f "metadata.user_count > 250000" "manifest.content_scripts?.length > 0 && manifest.permissions?.includes('nativeMessaging')"
    752 ```
    753 
    754 ## Post-exploitation: Forced extension load & persistence (Windows)
    755 
    756 Stealthy technique to backdoor Chromium by directly editing per-user Preferences and forging valid HMACs, causing the browser to accept and activate an arbitrary unpacked extension without prompts or flags.
    757 
    758 [Forced Extension Load Preferences Mac Forgery Windows](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/forced-extension-load-preferences-mac-forgery-windows)
    759 
    760 ## Detecting Malicious Extension Updates (Static Version Diffing)
    761 
    762 Supply-chain compromises often arrive as **malicious updates** to previously benign extensions. A practical, low-noise approach is to **compare a new extension package against the last known-good version** using static analysis (for example, [Assemblyline](https://github.com/CybercentreCanada/assemblyline)). The goal is to alert on **high-signal deltas** rather than on any change.<sup>[[10]](#references)</sup>
    763 
    764 ### Workflow
    765 
    766 - **Submit both versions** (old + new) to the same static-analysis profile.
    767 - **Flag new or updated background/service worker scripts** (persistence + privileged logic).
    768 - **Flag new or updated content scripts** (DOM access and data collection).
    769 - **Flag new permissions/host_permissions** added in `manifest.json`.
    770 - **Flag new domains** extracted from code (potential C2/exfil endpoints).
    771 - **Flag new static-analysis detections** (e.g., base64 decode, cookie harvesting, network-request builders, obfuscation patterns).
    772 - **Flag statistical anomalies** such as sharp entropy jumps or outlier z-scores in changed scripts.
    773 
    774 ### Detecting script changes accurately
    775 
    776 - **New script added** → detect via `manifest.json` diff.
    777 - **Existing script modified** (manifest unchanged) → compare **per-file hashes** from the extracted file tree (e.g., Assemblyline `Extract` output). This catches stealthy updates to existing workers or content scripts.
    778 
    779 ### Pre-disclosure detections
    780 
    781 To avoid “easy mode” detections based on already-known IOCs, **disable threat-intel-fed services** and rely on intrinsic signals (domains, heuristic signatures, script deltas, entropy anomalies). This increases chances of catching malicious updates **before public reporting**.
    782 
    783 ### Example high-confidence alert logic
    784 
    785 - **Low-noise combo:** new domains + new static-analysis detections + updated background/service worker + updated or added content scripts.
    786 - **Broader catch:** new domain + new or updated background/service worker (higher recall, higher noise).
    787 
    788 Key Assemblyline services for this workflow:
    789 
    790 - **Extract**: unpacks the extension and yields per-file hashes.
    791 - **Characterize**: computes file characteristics (e.g., entropy).
    792 - **JsJAWS / FrankenStrings / URLCreator**: surface JS heuristics, strings, and domains to diff between versions.
    793 
    794 ## Security Audit Checklist
    795 
    796 Even though Browser Extensions have a **limited attack surface**, some of them might contain **vulnerabilities** or **potential hardening improvements**. The following ones are the most common ones:<sup>[[8]](#references)</sup>
    797 
    798 - [ ] **Limit** as much as possible requested **`permissions`**
    799 - [ ] **Limit** as much as possible **`host_permissions`**
    800 - [ ] Use a **strong** **`content_security_policy`**
    801 - [ ] **Limit** as much as possible the **`externally_connectable`**, if none is needed and possible, do not leave it by default, specify **`{}`**
    802   - [ ] If **URL vulnerable to XSS or to takeover** is mentioned here, an attacker will be able to **send messages to the background scripts directly**. Very powerful bypass.
    803   - [ ] Reject wildcard or suffix trust such as `*.example.com` for **privileged** external message handlers.
    804   - [ ] Review whether any allowed origin serves **vendor-hosted widgets**, **user content**, or **versioned legacy assets** that could restore code execution on a trusted subdomain.
    805 - [ ] **Limit** as much as possible the **`web_accessible_resources`**, even empty if possible.
    806 - [ ] If **`web_accessible_resources`** is not none, check for [**ClickJacking**](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-clickjacking)
    807 - [ ] If any **communication** occurs from the **extension** to the **web page**, [**check for XSS**](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-xss-example) **vulnerabilities** caused in the communication.
    808   - [ ] If Post Messages are used, check for [**Post Message vulnerabilities**](../postmessage-vulnerabilities/index.html)**.**
    809   - [ ] If the **Content Script access DOM details**, check that they **aren't introducing a XSS** if they get **modified** by the web
    810   - [ ] Make a special emphasis if this communication is also involved in the **Content Script -> Background script communication**
    811   - [ ] If the background script is communicating via **native messaging** check the communication is secure and sanitized
    812 - [ ] **Sensitive information shouldn't be stored** inside the Browser Extension **code**
    813 - [ ] **Sensitive information shouldn't be stored** inside the Browser Extension **memory**
    814 - [ ] **Sensitive information shouldn't be stored** inside the **file system unprotected**
    815 
    816 ## Browser Extension Risks
    817 
    818 - The app [https://crxaminer.tech/](https://crxaminer.tech/) analyzes some data like the permissions browser extension requests to give a risk level of using the browser extension.
    819 
    820 ## Tools
    821 
    822 ### [**Tarnish**](https://thehackerblog.com/tarnish/)
    823 
    824 - Pulls any Chrome extension from a provided Chrome webstore link.<sup>[[1]](#references)</sup>
    825 - [**manifest.json**](https://developer.chrome.com/extensions/manifest) **viewer**: simply displays a JSON-prettified version of the extension’s manifest.
    826 - **Fingerprint Analysis**: Detection of [web_accessible_resources](https://developer.chrome.com/extensions/manifest/web_accessible_resources) and automatic generation of Chrome extension fingerprinting JavaScript.
    827 - **Potential Clickjacking Analysis**: Detection of extension HTML pages with the [web_accessible_resources](https://developer.chrome.com/extensions/manifest/web_accessible_resources) directive set. These are potentially vulnerable to clickjacking depending on the purpose of the pages.
    828 - **Permission Warning(s) viewer**: which shows a list of all the Chrome permission prompt warnings which will be displayed upon a user attempting to install the extension.
    829 - **Dangerous Function(s)**: shows the location of dangerous functions which could potentially be exploited by an attacker (e.g. functions such as innerHTML, chrome.tabs.executeScript).
    830 - **Entry Point(s)**: shows where the extension takes in user/external input. This is useful for understanding an extension’s surface area and looking for potential points to send maliciously-crafted data to the extension.
    831 - Both the Dangerous Function(s) and Entry Point(s) scanners have the following for their generated alerts:
    832   - Relevant code snippet and line that caused the alert.
    833   - Description of the issue.
    834   - A “View File” button to view the full source file containing the code.
    835   - The path of the alerted file.
    836   - The full Chrome extension URI of the alerted file.
    837   - The type of file it is, such as a Background Page script, Content Script, Browser Action, etc.
    838   - If the vulnerable line is in a JavaScript file, the paths of all of the pages where it is included as well as these page’s type, and [web_accessible_resource](https://developer.chrome.com/extensions/manifest/web_accessible_resources) status.
    839 - **Content Security Policy (CSP) analyzer and bypass checker**: This will point out weaknesses in your extension’s CSP and will also illuminate any potential ways to bypass your CSP due to whitelisted CDNs, etc.
    840 - **Known Vulnerable Libraries**: This uses [Retire.js](https://retirejs.github.io/retire.js/) to check for any usage of known-vulnerable JavaScript libraries.
    841 - Download extension and formatted versions.
    842   - Download the original extension.
    843   - Download a beautified version of the extension (auto prettified HTML and JavaScript).
    844 - Automatic caching of scan results, running an extension scan will take a good amount of time the first time you run it. However the second time, assuming the extension hasn’t been updated, will be almost instant due to the results being cached.
    845 - Linkable Report URLs, easily link someone else to an extension report generated by tarnish.
    846 
    847 ### [Neto](https://github.com/elevenpaths/neto)
    848 
    849 Project Neto is a Python 3 package for analyzing browser plugins and extensions for browsers such as Firefox and Chrome. It unpacks extension packages and extracts relevant features from resources such as `manifest.json`, localization folders, JavaScript, and HTML files.
    850 
    851 **Thanks to** [**@naivenom**](https://twitter.com/naivenom) **for the help with this methodology.**<sup>[[15]](#references)</sup>
    852 
    853 ## References
    854 
    855 - [1] [Introduction to Chrome Browser Extension Security Testing](https://www.cobalt.io/blog/introduction-to-chrome-browser-extension-security-testing)
    856 - [2] [Anatomy of a Basic Extension](https://palant.info/2022/08/10/anatomy-of-a-basic-extension/)
    857 - [3] [Attack Surface of Extension Pages](https://palant.info/2022/08/24/attack-surface-of-extension-pages/)
    858 - [4] [When Extension Pages Are Web-Accessible](https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/)
    859 - [5] [Content scripts - Chrome for Developers](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts)
    860 - [6] [externally_connectable - Chrome for Developers](https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable)
    861 - [7] [Background Pages (Manifest V2) - Chrome for Developers](https://developer.chrome.com/docs/extensions/mv2/background-pages)
    862 - [8] [Kicking the Rims: A Guide for Securely Writing and Auditing Chrome Extensions](https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/)
    863 - [9] [How to View Source of a Chrome Extension (gist)](https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0)
    864 - [10] [Moving up the Assemblyline: Exposing Malicious Code in Browser Extensions](https://redcanary.com/blog/threat-detection/assemblyline-browser-extensions/)
    865 - [11] [ShadowPrompt: How Any Website Could Have Hijacked Anthropic's Claude Chrome Extension](https://www.koi.ai/blog/shadowprompt-how-any-website-could-have-hijacked-anthropic-claude-chrome-extension)
    866 - [12] [An Evaluation of the Google Chrome Extension Security Architecture](http://webblaze.cs.berkeley.edu/papers/Extensions.pdf)
    867 - [13] [Universal Code Execution in Browser Extensions](https://spaceraccoon.dev/universal-code-execution-browser-extensions/)
    868 - [14] [Opera Browser Zero-Day RCE Vulnerability on Cross-Platforms](https://www.darkrelay.com/post/opera-zero-day-rce-vulnerability)
    869 - [15] Thanks to [@naivenom](https://twitter.com/naivenom) for the help with this methodology
    870 - [16] [Passbolt PBL-02 security report](https://help.passbolt.com/assets/files/PBL-02-report.pdf)
    871 - [17] [developer.chrome.com - Concepts - Native Messaging](https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging)