JavaScript Restrictor
Browser extension that improves privacy and security
|
Title: 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.
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.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.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.
Once you are set with the file name for your wrappers, you can start coding. The basic structure of the file is:
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
can be optionally called on parent_object.parent_object_property
.apply_if
optionally provides a condition that needs to be fullfilled to apply the wrapper. For example if a wrapper should be applied only when an API already provides some information. For example, apply_if: "navigator.plugins.length > 0"
.wrapped_objects
is a list of objects, each having the following properties (1 mandatory, 2 optional, typically, wrappers use one of the optional names in the wrapper code to access the original result of the call):original_name
(mandatory) - the original name of the object to be wrapped. Do not mention window
here!wrapped_name
- the variable name that points to the actual original object. Wrappers might need it for identity comparisons or other instance-specific use cases. The variable name 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.callable_name
- is similar to the wrapped_name
but the variable refers to a proxy of the original object. The proxy intercepts calls and automatically marshals parameters and return values. Wrappers MUST define callable_name
if the object is passed to other code like Promise
objects or callbacks. The variable is available only in the code generate by this wrapper. callable_name
is specifically meant to be used for native methods and functions which the wrapper needs to call. This is especially important if it accepts callback arguments or returns Promise
objects: invoking them through their callable_name
automates complex steps otherwise required for sandboxed browser extensions to interact with web pages.Generally speaking, use wrapped_name
whenever you need to access the original objects only inside the wrapper and you do not need to pass the object to other code, such as Promise
objects or callbacks. Compared to callable_name
, wrapped_name
has less overhead and is the preferred way.
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 replacedpost_replacement_code
Allows to provide additional code with the access to the original function and to the wrapped functionfreeze
if set to true
causes the API to be freezed. It should not be necessary if the wrapping is performed at the right level of the object's prototype chain (i.e. where the property to be modified was originally defined, i.e. usually in the object's prototype). Use this option with caution, only if strictly needed, because native APIs are configurable (otherwise our wrapping couldn't work) and therefore freezing them introduces a "weirdness", exploitable for fingerprinting.post_wrapping_code
is a list of additional wrapping objects with a similar structure to the wrappers, see the section bellow.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.:
(enableGeolocation
is a Booloean variable)
Currently jShelter supports additional wrapping of:
Date.now()
function to the wrapped Date
object) A WrapHelper
object is globally available to wrappers code, exposing some methods and properties which are mostly used internally by the code builders to automate tasks such as handling Firefox's content script sandbox or making replacement objects look as native as possible. However a few of them may be useful to complex wrappers or in edge case not covered by callable_name
and other declarative object replacement / property definition wrapper constructs:
WrapHelper.shared: {}
- a "bare" JavaScript object which a wrapper can use to share information with other wrappers (e.g. to coordinate behavior between related parts of the same API requiring multiple wrappings) by attaching its own data objects as properties. Warning: namespacing is not enforced and up to the wrapper implementor, but obviously recommended.WrapHelper.overlay(obj, data)
- Proxies the prototype of the obj
object in order to return the properties of the data
object as if they were native properties (e.g. as if they were returned by getters on the prototype chain, rather than defined on the instance). This allows spoofing some native objects data in a less detectable / fingerprintable way than by using Object.defineProperty()
. See wrappingS-MCS.js
for an example.WrapHelper.forPage(obj)
- it's mostly used internally and transparently by code_builder.js
, but may be useful to very complex proxies in edge cases needing to explicitly prepare an object/function created in Firefox's sandboxed content script environment to be consumed/called from the page context, and to make replacements for native objects and functions provided by the wrappers look as much native as possible (on Chromium, too). In most cases, however, this gets automated by the code builders replacing Object methods with their WrapHelper counterpart in the wrapper sources and by proxying "callable_name" function references through WrapHelper.pageAPI()
(see below).WrapHelper.pageAPI(f)
- proxies the function/method f so that arguments and return values, and especially callbacks and Promise
objects, are recursively managed in order to transparently marshal objects back and forth [Firefox's sandbox for extensions (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). Wrapper implementors should almost never need to use this API directly, since any function referenced via its "callable_name" goes automatically through it.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.
Of course the wrappers need to be compiled to JavaScript before inserting the code to page scripts. See code_builders.js
.
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.
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 what the wrapper tries to accomplish and its approach:
wrapping_function_body
\fn fake wrapped.object
for each wrapping_function_body
Follow instructions for unit testing and integration testing (see the tests
directory in the repository).
Title: 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.
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.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.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.
Once you are set with the file name for your wrappers, you can start coding. The basic structure of the file is:
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
can be optionally called on parent_object.parent_object_property
.apply_if
optionally provides a condition that needs to be fullfilled to apply the wrapper. For example if a wrapper should be applied only when an API already provides some information. For example, apply_if: "navigator.plugins.length > 0"
.wrapped_objects
is a list of objects, each having the following properties (1 mandatory, 2 optional, typically, wrappers use one of the optional names in the wrapper code to access the original result of the call):original_name
(mandatory) - the original name of the object to be wrapped. Do not mention window
here!wrapped_name
- the variable name that points to the actual original object. Wrappers might need it for identity comparisons or other instance-specific use cases. The variable name 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.callable_name
- is similar to the wrapped_name
but the variable refers to a proxy of the original object. The proxy intercepts calls and automatically marshals parameters and return values. Wrappers MUST define callable_name
if the object is passed to other code like Promise
objects or callbacks. The variable is available only in the code generate by this wrapper. callable_name
is specifically meant to be used for native methods and functions which the wrapper needs to call. This is especially important if it accepts callback arguments or returns Promise
objects: invoking them through their callable_name
automates complex steps otherwise required for sandboxed browser extensions to interact with web pages.Generally speaking, use wrapped_name
whenever you need to access the original objects only inside the wrapper and you do not need to pass the object to other code, such as Promise
objects or callbacks. Compared to callable_name
, wrapped_name
has less overhead and is the preferred way.
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 replacedpost_replacement_code
Allows to provide additional code with the access to the original function and to the wrapped functionfreeze
if set to true
causes the API to be freezed. It should not be necessary if the wrapping is performed at the right level of the object's prototype chain (i.e. where the property to be modified was originally defined, i.e. usually in the object's prototype). Use this option with caution, only if strictly needed, because native APIs are configurable (otherwise our wrapping couldn't work) and therefore freezing them introduces a "weirdness", exploitable for fingerprinting.post_wrapping_code
is a list of additional wrapping objects with a similar structure to the wrappers, see the section bellow.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.:
(enableGeolocation
is a Booloean variable)
Currently jShelter supports additional wrapping of:
Date.now()
function to the wrapped Date
object) A WrapHelper
object is globally available to wrappers code, exposing some methods and properties which are mostly used internally by the code builders to automate tasks such as handling Firefox's content script sandbox or making replacement objects look as native as possible. However a few of them may be useful to complex wrappers or in edge case not covered by callable_name
and other declarative object replacement / property definition wrapper constructs:
WrapHelper.shared: {}
- a "bare" JavaScript object which a wrapper can use to share information with other wrappers (e.g. to coordinate behavior between related parts of the same API requiring multiple wrappings) by attaching its own data objects as properties. Warning: namespacing is not enforced and up to the wrapper implementor, but obviously recommended.WrapHelper.overlay(obj, data)
- Proxies the prototype of the obj
object in order to return the properties of the data
object as if they were native properties (e.g. as if they were returned by getters on the prototype chain, rather than defined on the instance). This allows spoofing some native objects data in a less detectable / fingerprintable way than by using Object.defineProperty()
. See wrappingS-MCS.js
for an example.WrapHelper.forPage(obj)
- it's mostly used internally and transparently by code_builder.js
, but may be useful to very complex proxies in edge cases needing to explicitly prepare an object/function created in Firefox's sandboxed content script environment to be consumed/called from the page context, and to make replacements for native objects and functions provided by the wrappers look as much native as possible (on Chromium, too). In most cases, however, this gets automated by the code builders replacing Object methods with their WrapHelper counterpart in the wrapper sources and by proxying "callable_name" function references through WrapHelper.pageAPI()
(see below).WrapHelper.pageAPI(f)
- proxies the function/method f so that arguments and return values, and especially callbacks and Promise
objects, are recursively managed in order to transparently marshal objects back and forth [Firefox's sandbox for extensions (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). Wrapper implementors should almost never need to use this API directly, since any function referenced via its "callable_name" goes automatically through it.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.
Of course the wrappers need to be compiled to JavaScript before inserting the code to page scripts. See code_builders.js
.
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.
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 what the wrapper tries to accomplish and its approach:
wrapping_function_body
\fn fake wrapped.object
for each wrapping_function_body
Follow instructions for unit testing and integration testing (see the tests
directory in the repository).