Labs/Jetpack/Reboot/JEP/106
Contents
JEP 106 - Registered Jetpack URLs
- Champion: Atul Varma - avarma@mozilla.com
- Status: Cancelled/Invalid (see bug)
- Bug Ticket: bug 549319
- Type: API
Problem Statement
Every file in a Jetpack package needs a unique identifier for a variety of reasons:
- If an error occurs in a Jetpack's code, the traceback needs to include a URL that doesn't give away potentially sensitive information about its location on the user's filesystem.
- If a Jetpack wants to embed one of its images in a web page, it probably needs to do so with a URL, although such could also be done with a data: URI.
- If a Jetpack wants to self-host a web-like UI, it needs a URL to display in the URL bar. This URL should preferably be memorable but doesn't need to be.
- If a Jetpack wants to process the data contained in a file in its package, it should do so via a URL (though a relative path can potentially be used).
A prerequisite for this is that each Jetpack must have a unique ID that cannot be spoofed. This is valuable for a number of reasons:
- The host environment needs a way to keep track of Jetpack-specific resources; thus each Jetpack needs a primary key. If a Jetpack is uninstalled and reinstalled later and the user wants to keep their data, then the primary key may need to be preserved.
- The host environment needs a way of telling that one Jetpack is an upgrade for another, while guaranteeing that the upgrade isn't a forgery.
Proposal
Each Jetpack has a keypair associated with it, and a Jetpack's ID is the SHA-1 hash of its public key. Jetpacks must be signed using their private key, and will not be loaded unless signed; thus a Jetpack ID, if present in the system, is guaranteed to be unforgeable.
Resources that are bundled with the addon will be accessible, in certain narrow contexts, with URLs produced by the require("self").data.url(FILENAME) function. We expect these URLs to look something like resource://jetpack/$JID/FILENAME, but the exact syntax is opaque, and addon code should not come to depend upon any specific format. In particular, these URLs are *not* available for publishing arbitrary data: other add-ons and web content cannot successfully dereference a resource: URL, and we may rearrange the URL space to make room for other values.
Exceptions that are raised by jetpack code will be displayed as being associated with a URL that looks like jetpack://$JID/FILENAME, but the details are still being worked out. The URL will be unique, and will contain sufficient information to figure out which exact file was causing the problem. These URLs are for display purposes only: neither add-on nor web content code can successfully dereference a jetpack: URL.
We are not yet defining a URL that can be used by the add-on to publish information (i.e. something the user can type into the address bar to visit an add-on generated page, or something that web content or other add-ons can use in HTML to reference a resource that is produced by this add-on). The ability to publish information in this way shall be controlled by the package-loading manifest restrictions, just like the ability to consume information by performing XHR or other network requests. When we do get around to defining URLs for this purpose, they will certainly contain the unforgeable JID, giving each add-on clear control over some unspoofable portion of the URL space.
Use-Cases
- The wants to display an image/resource/L10n bundled with a Jetpack - option for allowing exposure of resource location needs consideration
- Developer debugging ergonomics
- Dynamically created, cache enabled, resources
Key Issues
There are a number of security issues involved in this.
- Jetpacks should have a way of specifying which of their resources is "private", i.e. accessible only by itself, vs. "public", i.e. accessible from external code like web pages.
- It shouldn't be possible for a Jetpack to "spoof" another by e.g. having the same ID.
- The security policy for HTML code needs to be well-defined; should it follow the Web's standard single-origin policy, or something else? Can scripts be loaded from remote pages?
Dependencies & Requirements
Internal Methods
API Methods
Discussion
My thoughts (developed during a discussion with Atul last week):
- A "jetpack ID" should be the hash of a public verifying key. "jpx create-new-addon" will create a keypair, store the private signing key under ~/.jetpack/, and create a new skeleton add-on directory (with the public key and a manifest, and maybe a hello-world sample).
- There will be a "jpx publish" command which bundles the various files together, signs the bundle, and drops the signed result in a well-known place, perhaps with an option to upload it to AMO
- The main purpose of the signed bundle is to convince the add-on manager/loader to grant the new code the same authorities that were granted to its predecessor. In particular, any data stored by one version of an add-on should be available to the update too.
- A well-known subdirectory of the add-on, perhaps ADDONBASE/resources/, should be used for "bundled resources". These are copied verbatim into the addon bundle.
- Content frames (as described in JEP-115) should have easy access to bundled resources. In particular, creating such a frame with a URL of "/panel.html" should populate the frame with the contents of ADDONBASE/resources/panel.html , and relative URLs inside it should reference bundled resources too.
- There should be a simple call that lets add-on code access the contents of bundled resources (from code, as opposed to from HTML), like:
var resources = require("bundled-resources"); var data = resources.load("panel.html")
- These bundled resources are, in general, *not* available from outside the add-on. However, I think it could be useful to add a "publish" feature, such that an add-on could do something like:
var publisher = require("publisher"); publisher.publish("/foo.txt", function (req) { return "foo!" });
and then the rest of the browser could do a GET of jetpack://id/$JETPACKID/foo.txt and be given "foo!". The use of $JETPACKID here would be unforgeable.
- When an exception occurs and we need put a "filename" property onto the exception object, we can use jetpack://$JETPACKID/jsfilename for debugging purposes. It would not necessarily be possible to paste this into the addressbar and see the source code in question, though (this sounds useful, but I think it's also important to allow add-ons to keep their resources private, so maybe only allow this to work in a debug console of some sort). It might be best for these URLs to have a different protocol, maybe jetpack-exception://, to distinguish it from the URLs managed by the "publisher" feature.
--Brian Warner 06:28, 16 February 2010 (UTC)
Our current plan:
- The Jetpack ID will be made available to add-ons through the "self" package:
var my_id = require("self").id;
- The add-on package's `data` directory will contain the bundled resources. So if the main add-on code lives in PKGROOT/lib/main.js , a typical resource would live in PKGROOT/data/icon.png
- The "self" package has a `data` property which provides access to these resources:
var data = require("self").data; var text = data.load("foo.txt"); // contents of PKGROOT/data/foo.txt var icon_url = data.url("icon.png");
- The URL method will return a URL instance that can be used to load the given resource. This URL can only be used by content frames opened by the same package which provided the resource: it does not provide a way for add-ons to publish data to anyone else (we'll offer some other mechanism for that in the future).
- We expect the URL to look like `resource://jetpack/$JID/main.html`, but the actual syntax is opaque and private: add-on code should not depend upon the details. The only promise made by `data.url()` is that relative links should work correctly: PKGROOT/data/page.html can reference PKGROOT/data/image.png with a normal `<img src="image.png"/>` tag.
- An NSIContentPolicy object will be used to enforce the non-sharedness of these resource URLs. This may not be enforced right away, but eventually it will.
--Brian Warner 22:49, 9 April 2010 (UTC)