How to write a new wrapper

The primary focus of jShelter is to provide security and privacy oriented wrappers of JavaScript APIs. As some of the code is very similar for each wrapper, we use a unified approach to describe wrappers. The approach is described below. This approach also helps to automatically modify toString conversions of the wrapped APIs, i.e. a correctly written wrapper creates a modified function but wrapper.toString() returns the original string. Finally, this approach also helps in generating code dealing with the Firefox bug described in #25.

File structure

  • wrapping.js provides main facilities for interacting with specific wrappers:
  • add_wrappers() has to be provided with all the wrappers. Hence, a typical module with wrappers of a web standard APIS. ends with a call of add_wrappers(list_of_all_wrappers_defined_by_the_module).
  • build_wrapping_code contains all the registered wrappers. The keys are referenced by the levels wrapper list. Do not modify the build_wrapping_code variable directly, use add_wrappers instead.
  • The module also provides common functions used by the wrappers, e.g. rounding_function and noise_function.
  • wrappingS-XYZ are files dealing with APIs introduced by specific web or ECMA standard. See the naming conventions for details on the XYZ part of the name. See the wrapper specification section for the properties of the wrapper objects.

Naming conventions

jShelter adopted the naming conventions of the Web API Manager. See the paper:

Peter Snyder, Cynthia Taylor, and Chris Kanich, “Most Websites Don’t Need to Vibrate: A Cost–Benefit Approach to Improving Browser Security,” in Proceedings of the 2017 ACM Conference on Computer and Communications Security, 2017.

Wrapper specification

Once you are set with the file name for your wrappers, you can start coding. The basic structure of the file is:

(function() {
    // definition of common functions used by the wrappers bellow
    var wrappers = [
        {
            // wrapping object 1
        },
        {
            // wrapping object 2
        },
...
        {
            // wrapping object n
        },
    ]
    add_wrappers(wrappers);
})();

The content of the module should be in an IIFE so that it does not polute the global namespace. The property values are strings that are generally interpreted either as identifiers or JS code.

Each wrapping object must have the following mandatory properties:

  • parent_object and parent_object_property are used to define the name of the wrapping (parent_object.parent_object_property) that is referenced by level wrappers. Additionally, it is used if wrapper_prototype is defined to provide the object name to have the prototype changed. Finally, Object.freeze is called on parent_object.parent_object_property.
  • wrapped_objects is a list of objects, each having two properties:
  • original_name - the original name of the object to be wrapped. Do not mention window here!
    • wrapped_name - the vatiable name that can be used by wrapping_function_body, helping_code and other code fragments to reference the original object. Note that this name is not available outside the code generated by this wrappper.

Each wrapping object can have the following optional properties:

  • wrapping_function_args is a string that is used as an argument list for the wrapping function. Typically this string reflects the parameters of the original method, it can be an empty string, a list of parameters, such as "source, target, color" or "...args".
  • wrapping_function_body specified the behaviour of the wrapped function. You can provide a completely different implementation but often, you want to refer to the original implementation (available in the variable wrapped_name) and modify the original implementation.
  • wrapping_code_function_name can be used to call the wrapping function from the other code inside the wrapper. For example to create another wrapper similar to the original wrapper.
  • wrapper_prototype - if defined, parent_object.parent_object_property prototype is set to the prototype identifier provided by wrapper_prototype.
  • original_function if not provided, it defaults to parent_object.parent_object_property. This name is used for overloading the toString function. Instead of the wrapping code, toString returns the content of the original function.
  • helping_code provides you an option to define code available for both replacement function and post_wrapping_code
  • replace_original_function is used to control which function should be replaced
  • post_replacement_code Allows to provide additional code with the access to the original function and to the wrapped function
  • nofreeze if set to true causes the API not to be freeed. Use this option with caution and make sure that the wrapping is not deletable.
  • post_wrapping_code is a list of additional wrapping objects with a similar structure to the wrappers, see the section bellow.

Post wrapping code

Complex wrappers need to provide additional wrapping code.

You can make the generation of the post wrapping code generation conditional by using apply_if, e.g.:

                {
                    ...
                    apply_if: "!enableGeolocation",
                }

(enableGeolocation is a Booloean variable)

Currently jShelter supports additional wrapping of:

  • Definition of a function (used, for example, to reintroduce Date.now() function to the wrapped Date object)
[
    {
        code_type: "function_define",
        original_function: "originalDateConstructor.now",
        parent_object: "window.Date",
        parent_object_property: "now",
        wrapping_function_args: "",
        wrapping_function_body: "return func(originalDateConstructor.now.call(Date), precision);",
    },
]
  • Export a function from the wrapping namespace (currently not used, the following code exposes the unwrapped, i.e. original, version of Date.now to page scripts)
[
    {
        code_type: "function_export",
        parent_object: "window.Date",
        parent_object_property: "now",
        export_function_name: "originalDateConstructor.now",
    },
]
  • Redefine an object property (used to prevent leaking iframe properties to the unwrapped objects):
{
    code_type: "object_properties",
    parent_object: "HTMLIFrameElement.prototype",
    parent_object_property: "contentWindow",
    wrapped_objects: [
        {
            original_name: "HTMLIFrameElement.prototype.__lookupGetter__('contentWindow')",
            wrapped_name: "cw",
        },
    ],
    wrapped_properties: [
        {
            property_name: "get",
            property_value: `
                function() {
                    var parent=cw.call(this);
                    try {
                        parent.HTMLCanvasElement;
                    }
                    catch(d) {
                        return; // HTMLIFrameElement.contentWindow properties could not be accessed anyway
                    }
                    wrapping(parent);
                    return parent;
                }`,
        },
    ],
}
  • Deleting a property (used when you want to completely disable an API):
                {
                    code_type: "delete_properties",
                    parent_object: "navigator",
                    apply_if: "!enableGeolocation",
                    delete_properties: ["geolocation"],
                }

The generated wrapper structure

To get a better idea how the code is generated see the following pseudo code. Please do not refer to any name created by the code builders from your wrapper. Use your custom names. If it is not available, please, open an issue where you explain what you are trying to achieve. It is probable that we will introduce a new property that allows to provide your name to the code builders. The code lives in an anonymous namespaces, so variables introduced here do not directly leak to page scripts.

// Define wrapped_name(s) variables holding the original JS objects
helping_code // if present
function wrapping_code_function_name(param) {
    // Store original function: either original_function which defaults to parent_object.parent_object_property
    function replacement(wrapping_function_args) {
        wrapping_function_body
    }
    if (replace_original_function)
        original_function = replacement
    else
        parent_object.parent_object_property = replacement
    // Modify toString
    post_replacement_code
}
wrapping_code_function_name(window if wrapping_code_function_call_window) // The function is called even if the name is not explicitely provided
// wrappings generated by post wrapping code

Compiling the wrappers

Of course the wrappers need to be compiled to JavaScript before inserting the code to page scripts. See code_builders.js and ffbug1267027.js.

Registering a new wrapper

fix_manifest.sh automatically adds all modules with file name of wrapping*.js to the manifest.json of the extension. There is no need for any additional action.

Register new wrapper to be used by the extension in a level or available in the GUI.

See levels.js and its list wrapping_groups.groups. Once you add your wrapper to an existing group or create a new group, the wrapper becomes available in the built-in levels containing the group and in the GUI for custom levels.

Describe the wrapper for Doygen documentation

Describe what the wrapper tries to accomplish and its approach:

  • Add Doxygen comment at the top of the file with the following structure:
/** \file
 * \brief This file contains wrappers for the X API (link to the standard or MDN)
 * \ingroup wrappers
 *
 * Put at least one paragraph describing the goal of the wrapper. Describe a possible attack vector.
 * Link to further resources/readings.
 *
 * Describe the options of the wrapper. Mention examples when a user should want to use the
 *  available options.
 *
 * Describe any \note, \bug ór use other suitable Doxygen commands
 * (https://www.doxygen.nl/manual/commands.html)
 */
  • Describe helping functions and wrapping_function_body
  • Use Doxygen command \fn fake wrapped.object for each wrapping_function_body
  • Use the following structure for function comments:
/**
 * \brief Short description
 *
 * \param X Descripton
 * \param Y Descripton
 *
 * \returns Description
 *
 * Main description of the aim of the function, algorithm, etc.
 *
 * Describe any \note, \bug ór use other suitable Doxygen commands
 * (https://www.doxygen.nl/manual/commands.html)
 */

Write unit tests or integgration tests for the wrapper

Follow instructions for unit testing and integration testing (see the tests directory in the repository).