Decode Webpages using FireFox Plugin

Create a FireFox extension that allows post processing a webpage. Use a mapping file to decode hidden values.

Decode Webpages using FireFox Plugin
Photo by Xavier von Erlach / Unsplash

Context

We recently needed to showcase our Business Intelligence product on a customer dataset where we weren't allowed access to the keys. The problem is that our system ingested anonymized data, but for the results to be valuable the customer needs to have access to it original keys. We aren't building a new product here, we're just looking for a quick hack to run the session.

As a super simple example let's pretend that we have the following Grocery List Application:

We don't want the application server to know what we're shopping for so when we add items we add masked values. The masked values can be created however you want. Hashing is one choice, but for this I'm just listing them as SECRET_#. For these secret values I maintain a mapping object (or file)

var mapping = {
    'SECRET_1': 'Milk',
    'SECRET_2': 'Bread',
    'SECRET_3': 'Cheese'
};

After the server sends me the screen list and the browser renders the page I want to substitute the secret values for the original values.

Solution

I decided to use create a small FireFox extension to post-process values on a webpage. The page renders, I click a button, and the text is swapped out directly in my browser (client-side / locally).

First up, the manifest and the context menu code. These are copied almost verbatim from this Mozilla example.

Modify a web page - Mozilla | MDN
One of the most common use cases for an extension is to modify a web page. For example, an extension might want to change the style applied to a page, hide particular DOM nodes, or inject extra DOM nodes into the page.
{
    "manifest_version": 2,
    "name": "mapped-decoder",
    "version": "1.0",
    "permissions": [
        "activeTab",
        "contextMenus"
    ],
    "background": {
        "scripts": ["decoder-context-menu.js"]
    }
}
manifest.json
browser.contextMenus.create({
    id: "decode-page",
    title: "Run Decoder"
  });
  
browser.contextMenus.onClicked.addListener((info, tab) => {
    if (info.menuItemId === "decode-page") {
        browser.tabs.executeScript({
            file: "mapped-decoder.js"
          });
    }
});
decoder-context-menu.js

This script adds a context menu (right click) option called 'Run Decoder'. When the option is selected it will run the script 'mapped-decoder.js'.

Modifying the Page

I'm using TreeWalker and SHOW_TEXT node filter  to select all of the text shown on the page and allow me to do simple string replace.

var html = document.querySelector('html');
var walker = document.createTreeWalker(html, NodeFilter.SHOW_TEXT);
var node;

var mapping = {
    'SECRET_1': 'Milk',
    'SECRET_2': 'Bread',
    'SECRET_3': 'Cheese'
};

while (node = walker.nextNode()) {
    for (var key in mapping) {
        if (!mapping.hasOwnProperty(key)) {
            continue;
        }
        var encoded = key;
        var decoded = mapping[key];
        node.nodeValue = node.nodeValue.replace(encoded, decoded);
    }
}
mapped-decoder.js

In this example the mapping is a JS object, but for larger sets it might be better to load it in from a file.

If your encoded values have a common prefix or suffix  then you can shortcut the iteration over each node

while (node = walker.nextNode()) {
    if (node.nodeValue.includes('SECRET_') {
    	// apply mappings
    } else {
    	// skip this node
    }
}

Temporary Extension Install

I don't need a permanent extension installed or anything fancy, just something quick and minimal to do my task. In FireFox I can do a temporarily installation which only lasts until the browser is restarted. Instructions can be found here:

Temporary installation in Firefox
Get help creating & publishing Firefox extensions.

Result

To test it I've created this simple encoded shopping list

<head></head>
<body>
    <div>
        <div><strong>Grocery List</strong></div>
        <div>
            <li>SECRET_1</li>
            <li>SECRET_2</li>
            <li>SECRET_3</li>
        </div>
    </div>
</body>
example.html

This is the finished result