-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Surface SES on globalThis #307
Changes from all commits
48510ed
8b60f40
82f7b25
9be0b37
71b1d97
0fc17e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,69 +27,130 @@ npm install ses | |
|
||
## Usage | ||
|
||
### Module | ||
### Lockdown | ||
|
||
This example locks down the current realm, turning it into a starting | ||
compartment. | ||
Within a compartment, there is a `Compartment` constructor that conveys | ||
"endownments" into the new compartment's global scope, and a `harden` method | ||
that that object and any object reachable from its surface. | ||
The compartment can import modules and evaluate programs. | ||
SES introduces the `lockdown()` function. | ||
Calling `lockdown()` alters the surrounding execution enviornment, or | ||
**realm**, such that no two programs running in the same realm can observe or | ||
interfere with each other until they have been introduced. | ||
|
||
To this end, `lockdown()` freezes all objects accessible to any program in the | ||
realm. | ||
The set of accessible objects includes but is not limited to: `globalThis`, | ||
`[].__proto__`, `{}.__proto__`, `(() => {}).__proto__` `(async () => | ||
{}).__proto__`, and the properties of any accessible object. | ||
|
||
The `lockdown()` function also **tames** some of those accessible objects | ||
that have powers that would otherwise allow programs to observe or interfere | ||
with one another like clocks, random number generators, and regular | ||
expressions. | ||
|
||
```js | ||
import 'ses'; | ||
import 'my-vetted-shim'; | ||
|
||
lockdown(); | ||
|
||
console.log(Object.isFrozen([].__proto__)); | ||
// true | ||
``` | ||
|
||
### Harden | ||
|
||
SES introduces the `harden` function. | ||
*After* calling `lockdown`, the `harden` function ensures that every object in | ||
the transitive closure over property and prototype access starting with that | ||
object has been **frozen** by `Object.freeze`. | ||
This means that the object can be passed among programs and none of those | ||
programs will be able to tamper with the **surface** of that object graph. | ||
They can only read the surface data and call the surface functions. | ||
|
||
```js | ||
import {lockdown} from "ses"; | ||
import 'ses'; | ||
|
||
lockdown(); | ||
|
||
let counter = 0; | ||
const capability = harden({ | ||
inc() { | ||
counter++; | ||
}, | ||
}); | ||
|
||
console.log(Object.isFrozen(capability)); | ||
// true | ||
console.log(Object.isFrozen(capability.inc)); | ||
// true | ||
``` | ||
|
||
Note that although the **surface** of the capability is frozen, the capability | ||
still closes over the mutable counter. | ||
Hardening an object graph makes the surface immutable, but does not make | ||
methods pure. | ||
|
||
|
||
### Compartment | ||
|
||
SES introduces the `Compartment` constructor. | ||
A compartment is an evaluation and execution environment with its own | ||
`globalThis` and wholly independent system of modules, but otherwise shares | ||
the same batch of intrinsics like `Array` with the surrounding compartment. | ||
The concept of a compartment implies the existence of a "start compartment", | ||
the initial execution environment of a **realm**. | ||
|
||
In the following example, we create a compartment endowed with a `print()` | ||
function on `globalThis`. | ||
|
||
```js | ||
import 'ses'; | ||
|
||
const c = new Compartment({ | ||
print: harden(console.log), | ||
print: harden(console.log), | ||
}); | ||
|
||
c.evaluate(` | ||
print("Hello! Hello?"); | ||
print('Hello! Hello?'); | ||
`); | ||
``` | ||
|
||
The new compartment has a different global object than the start compartment. | ||
The global object is initially mutable. | ||
Locking down the start compartment hardened many of the intrinsics in global | ||
scope. | ||
After lockdown, no compartment can tamper with these intrinsics. | ||
Many of these intrinsics are identical in the new compartment. | ||
Locking down the realm hardened the objects in global scope. | ||
After `lockdown`, no compartment can tamper with these **intrinsics** and | ||
**undeniable** objects. | ||
Many of these are identical in the new compartment. | ||
|
||
```js | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid terms like "the property holds" below when you're not referring to object properties but in a context where you might be. |
||
const c = new Compartment(); | ||
c.global === global; // false | ||
c.global.JSON === JSON; // true | ||
c.globalThis === globalThis; // false | ||
c.globalThis.JSON === JSON; // true | ||
``` | ||
|
||
The property holds among any other compartments. | ||
Other pairs of compartments also share many identical intrinsics and undeniable | ||
objects of the realm. | ||
Each has a unique, initially mutable, global object. | ||
Many intrinsics are shared. | ||
|
||
```js | ||
const c1 = new Compartment(); | ||
const c2 = new Compartment(); | ||
c1.global === c2.global; // false | ||
c1.global.JSON === c2.global.JSON; // true | ||
c1.globalThis === c2.globalThis; // false | ||
c1.globalThis.JSON === c2.globalThis.JSON; // true | ||
``` | ||
|
||
### Compartments | ||
|
||
Any code executed within a compartment shares a set of module instances. | ||
For modules to work within a compartment, the creator must provide | ||
a `resolveHook` and an `importHook`. | ||
The `resolveHook` determines how the compartment will infer the full module | ||
specifier for another module from a referrer module and the module specifier | ||
imported within that module. | ||
The `importHook` accepts a module specifier and asynchronously returns a | ||
specifier for another module from a referrer module and the import specifier. | ||
The `importHook` accepts a full specifier and asynchronously returns a | ||
`ModuleStaticRecord` for that module. | ||
|
||
```js | ||
import { Compartment, ModuleStaticRecord } from 'ses'; | ||
import 'ses'; | ||
|
||
const c1 = new Compartment({}, {}, { | ||
resolveHook: (moduleSpecifier, moduleReferrer) => { | ||
return new URL(moduleSpecifier, moduleReferrer).toString(); | ||
return resolve(moduleSpecifier, moduleReferrer); | ||
}, | ||
importHook: async moduleSpecifier => { | ||
const moduleLocation = locate(moduleSpecifier); | ||
|
@@ -101,14 +162,14 @@ const c1 = new Compartment({}, {}, { | |
|
||
A compartment can also link a module in another compartment. | ||
Each compartment has a `module` function that accepts a module specifier | ||
and returns the ModuleNamespace for that module. | ||
The ModuleNamespace is not useful for inspecting the exports of the | ||
and returns the module exports namespace for that module. | ||
The module exports namespace is not useful for inspecting the exports of the | ||
module until that module has been imported, but it can be passed into the | ||
module map of another Compartment, creating a link. | ||
|
||
```js | ||
const c2 = new Compartment({}, { | ||
'https://example.com/packages/example/': c1.module('./main.js'), | ||
'c1': c1.module('./main.js'), | ||
}, { | ||
resolveHook, | ||
importHook, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,5 +12,12 @@ | |
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
export { lockdown } from './lockdown-shim.js'; | ||
export { Compartment, ModuleStaticRecord } from './compartment-shim.js'; | ||
import { lockdown, harden } from './lockdown-shim.js'; | ||
import { Compartment, ModuleStaticRecord } from './compartment-shim.js'; | ||
|
||
Object.assign(globalThis, { | ||
lockdown, | ||
harden, | ||
Compartment, | ||
ModuleStaticRecord, | ||
Comment on lines
+19
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All shimmed pervasively global names must be added to the appropriate whitelists. I don't see I do see
We should have a test that fails on such omission, because it is an easy mistake to keep making. TODO(markm): the whole intrinsic management system now consists of nine separate files with too much redundancy, e.g., between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filed TODO as #317 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the kind of thing that I worry most about. Thanks for catching this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok. But make sure the state as of merging this PR is consistent with itself. |
||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make the reference to #293 a markdown link to #293