See the README for a description of the global lockdown
function
installed by the SES-shim.
Essentially, calling lockdown
turns a JavaScript system into a SES system,
with enforced ocap (object-capability) security.
Here we explain the configuration options to the lockdown function.
For every safety-relevant options setting, if the option is omitted
it defaults to 'safe'
. For these options, the tradeoff is safety vs
compatibility, though note that a tremendous amount of legacy code, not
written to run under SES, does run compatibly under SES even with all of these
options set to 'safe'
. You should only consider an 'unsafe'
option if
you find you need it and are able to evaluate the risks.
The stackFiltering
option trades off stronger filtering of stack traceback to
minimize distractions vs completeness for tracking down a bug hidden in
obscure places. The overrideTaming
option trades off better code
compatibility vs better tool compatibility.
Each option is explained in its own section below.
option | default setting | other settings | about |
---|---|---|---|
regExpTaming |
'safe' |
'unsafe' |
RegExp.prototype.compile |
localeTaming |
'safe' |
'unsafe' |
toLocaleString |
consoleTaming |
'safe' |
'unsafe' |
deep stacks |
errorTaming |
'safe' |
'unsafe' |
errorInstance.stack |
errorTrapping |
'platform' |
'exit' 'abort' 'report' |
handling of uncaught exceptions |
stackFiltering |
'concise' |
'verbose' |
deep stacks signal/noise |
overrideTaming |
'moderate' |
'min' or 'severe' |
override mistake antidote |
overrideDebug |
[] |
array of property names | detect override mistake |
domainTaming |
'unsafe' |
'safe' |
bans the Node.js domain module, to be 'safe' by default in the next breaking-changes release |
__allowUnsafeMonkeyPatching__ |
'safe' |
'unsafe' |
run unsafe code unsafely |
In the absence of any of these options in lockdown arguments, lockdown will
attempt to read these from the Node.js process.env
environment.
The corresponding environment variables are the same but in UPPER_SNAKE_CASE
.
The options mathTaming
and dateTaming
are deprecated.
Math.random
, Date.now
, and the new Date()
are disabled within
compartments and can be injected as globalThis
endowments if necessary, as in
this example where we inject an independent pseudo-random-number generator in
this single-tenant compartment.
new Compartment({
Math: harden({
...Math,
random: harden(makeRandom(seed)),
}),
})
Background: In standard plain JavaScript, the builtin
RegExp.prototype.compile
method may violate the object invariants of frozen
RegExp
instances. This violates assumptions elsewhere, and so can be
used to corrupt other guarantees. For example, the JavaScript Proxy
abstraction preserves the object invariants only if its target does. It was
designed under the assumption that these invariants are never broken. If a
non-conforming object is available, it can be used to construct a proxy
object that is also non-conforming.
lockdown(); // regExpTaming defaults to 'safe'
// or
lockdown({ regExpTaming: 'safe' }); // Delete RegExp.prototype.compile
// vs
lockdown({ regExpTaming: 'unsafe' }); // Preserve RegExp.prototype.compile
The regExpTaming
default 'safe'
setting deletes this dangerous method. The
'unafe'
setting preserves it for maximal compatibility at the price of some
risk.
Background: In de facto plain JavaScript, the legacy RegExp
static
methods like RegExp.lastMatch
are an unsafe global
overt communications channel.
They reveal on the RegExp
constructor information derived from the last match
made by any RegExp
instance—a bizarre form of non-local causality.
These static methods are currently part of de facto
JavaScript but not yet part of the standard. The
Legacy RegExp static methods
proposal would standardize them as normative optional and deletable, meaning
- A conforming JavaScript engine may omit them
- A shim may delete them and have the resulting state still conform to the specification of an initial JavaScript state.
All these legacy RegExp
static methods are currently removed under all
settings of the regExpTaming
option.
So far this has not caused any compatibility problems.
If it does, then we may decide to support them, but only under the
'unsafe'
setting and only on the RegExp
constructor of the start
compartment. The RegExp
constructor shared by other compartments will remain
safe and powerless.
Background: In standard plain JavaScript, the builtin methods with
"Locale
" or "locale
" in their name—toLocaleString
,
toLocaleDateString
, toLocaleTimeString
, toLocaleLowerCase
,
toLocaleUpperCase
, and localeCompare
—have a global behavior that is
not fully determined by the language spec, but rather varies with location and
culture, which is their point. However, by placing this information of shared
primordial prototypes, it cannot differ per comparment, and so one compartment
cannot virtualize the locale for code running in another compartment. Worse, on
some engines the behavior of these methods may change at runtime as the machine
is "moved" between different locales,
i.e., if the operating system's locale is reconfigured while JavaScript
code is running.
lockdown(); // localeTaming defaults to 'safe'
// or
lockdown({ localeTaming: 'safe' }); // Alias toLocaleString to toString, etc
// vs
lockdown({ localeTaming: 'unsafe' }); // Allow locale-specific behavior
The localeTaming
default 'safe'
option replaces each of these methods with
the corresponding non-locale-specific method. Object.prototype.toLocaleString
becomes just another name for Object.prototype.toString
. The 'unsafe'
setting preserves the original behavior for maximal compatibility at the price
of reproducibility and fingerprinting. Aside from fingerprinting, the risk that
this slow non-determinism opens a
communications channel
is negligible.
Background: Most JavaScript environments provide a console
object on the
global object with interesting information hiding properties. JavaScript code
can use the console
to send information to the console's logging output, but
cannot see that output. The console
is a write-only device. The logging
output is normally placed where a human programmer, who is in a controlling
position over that computation, can see the output. This output is, accordingly,
formatted mostly for human consumption; typically for diagnosing problems.
Given these constraints, it is both safe and helpful for the console
to reveal
to the human programmer information that it would not reveal to the objects it
interacts with. SES amplifies this special relationship to reveal
to the programmer much more information than would be revealed by the normal
console
. To do so, by default during lockdown
SES virtualizes the builtin
console
, by replacing it with a wrapper. The wrapper is a virtual console
that implements the standard console
API mostly by forwarding to the original
wrapped console
.
In addition, the virtual console
has a special relationship with
error objects and with the SES assert
package, so that errors can report yet
more diagnostic information that should remain hidden from other objects. See
the error README for an in depth explanation of this
relationship between errors, assert
and the virtual console
.
lockdown(); // consoleTaming defaults to 'safe'
// or
lockdown({ consoleTaming: 'safe' }); // Wrap start console to show deep stacks
// vs
lockdown({ consoleTaming: 'unsafe' }); // Leave original start console in place
// or
lockdown({
consoleTaming: 'unsafe', // Leave original start console in place
overrideTaming: 'min', // Until https://github.com/endojs/endo/issues/636
});
The consoleTaming: 'unsafe'
setting leaves the original console in place.
The assert
package and error objects will continue to work, but the console
logging output will not show any of this extra information.
The risk is that the original platform-provided console
object often has
additional methods beyond the de facto console
"standards". Under the
'unsafe'
setting we do not remove them.
We do not know whether any of these additional
methods violate ocap security. Until we know otherwise, we should assume these
are unsafe. Such a raw console
object should only be handled by very
trustworthy code.
Until the bug
Node console gets confused if .constructor is an accessor (#636)
is fixed, if you use the consoleTaming: 'unsafe'
setting and might be running
with the Node console
, we advise you to also set overrideTaming: 'min'
so
that no builtin constructor
properties are turned into accessors.
Examples from test-deep-send.js of the eventual-send shim:
Expand for { consoleTaming: 'safe' } log output
expected failure (Error#1)
Nested error
Error#1: Wut?
at Object.bar (packages/eventual-send/test/test-deep-send.js:13:21)
Error#1 ERROR_NOTE: Thrown from: (Error#2) : 2 . 0
Error#1 ERROR_NOTE: Rejection from: (Error#3) : 1 . 1
Nested 2 errors under Error#1
Error#2: Event: 1.1
at Object.foo (packages/eventual-send/test/test-deep-send.js:17:28)
Error#2 ERROR_NOTE: Caused by: (Error#3)
Nested error under Error#2
Error#3: Event: 0.1
at Object.test (packages/eventual-send/test/test-deep-send.js:21:22)
at packages/eventual-send/test/test-deep-send.js:25:19
at async Promise.all (index 0)
Expand for { consoleTaming: 'unsafe', overrideTaming: 'min' } log output
expected failure [Error: Wut?
at Object.bar (packages/eventual-send/test/test-deep-send.js:13:21)]
Background: The error system of JavaScript has several safety problems.
In most JavaScript engines running normal JavaScript, if err
is an
Error instance, the expression err.stack
will produce a string
revealing the stack trace. This is an
overt information leak, a confidentiality
violation.
This stack
property reveals information about the call stack that violates
the encapsulation of the callers.
This stack
is part of de facto JavaScript, is not yet part
of the official standard, and is proposed at
Error Stacks proposal.
Because it is unsafe, we propose that the stack
property be "normative
optional", meaning that a conforming implementation may omit it. Further,
if present, it should be present only as a deletable accessor property
inherited from Error.prototype
so that it can be deleted. The actual
stack information would be available by other means, the getStack
and
getStackString
functions—special powers available only in the start
compartment—so the SES console can still operate as described above.
On v8—the JavaScript engine powering Chrome, Brave, and Node—the
default error behavior is much more dangerous. The v8 Error
constructor
provides a set of
static methods for accessing the raw stack
information that are used to create
error stack string. Some of this information is consistent with the level
of disclosure provided by the proposed getStack
special power above.
Some go well beyond it.
The errorTaming
option of lockdown
do not affect the safety of the Error
constructor. In all cases, after calling lockdown
, the tamed Error
constructor in the start compartment follows ocap rules.
Under v8 it emulates most of the
magic powers of the v8 Error
constructor—those consistent with the
level of disclosure of the proposed getStack
. In all cases, the Error
constructor shared by all other compartments is both safe and powerless.
See the error README for an in depth explanation of the
relationship between errors, assert
and the virtual console
.
lockdown(); // errorTaming defaults to 'safe'
// or
lockdown({ errorTaming: 'safe' }); // Deny unprivileged access to stacks, if possible
// vs
lockdown({ errorTaming: 'unsafe' }); // stacks also available by errorInstance.stack
The errorTaming
default 'safe'
setting makes the stack trace inaccessible
from error instances alone, when possible. It currently does this only on
v8 (Chrome, Brave, Node). It will also do so on SpiderMonkey (Firefox).
Currently is it not possible for the SES-shim to hide it on other
engines, leaving this information leak available. Note that it is only an
information leak. It reveals the magic information only as a powerless
string. This leak threatens
confidentiality but not integrity.
Since the current JavaScript de facto reality is that the stack is only
available by saying err.stack
, a number of development tools assume they
can find it there. When the information leak is tolerable, the 'unsafe'
setting will preserve the filtered stack information on the err.stack
.
Like hiding the stack, the purpose of the details
template literal tag (often
spelled X
or d
) together with the quote
function (often spelled q
) is
to redact data from the error messages carried by error instances. The same
{errorTaming: 'unsafe'}
suppresses that redaction as well, so that all
substitution values would act like they've been quoted. With this setting
assert(false, X`literal part ${secretData} with ${q(publicData)}.`);
acts like
assert(false, X`literal part ${q(secretData)} with ${q(publicData)}.`);
The lockdown({ errorTaming: 'unsafe' })
call has this effect by replacing
the global assert
object with one whose assert.details
does not redact.
So be sure to sample assert
and assert.details
only after such a call to
lockdown:
lockdown({ errorTaming: 'unsafe' });
// Grab `details` only after lockdown
const { details: X, quote: q } = assert;
Like with the stack, the SES shim console
object always
shows the unredacted detailed error message independent of the setting of
errorTaming
.
Background: With safe error taming and console taming, after lockdown,
errors are born without an attached stack
string.
Logging the error with the tamed console
will safely reveal the stack to the
debugger or terminal.
However, an uncaught exception gets logged to the console without the
benefit of the tamed console
.
lockdown(); // defaults to 'platform'
// or
lockdown({ errorTrapping: 'platform' }); // 'exit' on Node, 'report' on the web.
// or
lockdown({ errorTrapping: 'exit' }); // report and exit
// or
lockdown({ errorTrapping: 'abort' }); // report and drop a core dump
// or
lockdown({ errorTrapping: 'report' }); // just report
// or
lockdown({ errorTrapping: 'none' }); // no platform error traps
On the web, the window
event emitter has a trap for error
events.
In the absence of a trap, the platform logs the error to the debugger console
and continues.
This is consistent with the security ethos that a sandboxed program should not
have the ambient power of causing the surrounding process to exit.
However, setting errorTrapping
to 'exit'
or 'abort'
will cause the
web equivalent of halting the page: the error will cause navigation to
a blank page, immediately halting execution in the window.
In Node.js, the process
event emitter has a trap for uncaughtException
.
In the absence of a trap, the platform logs the error and immediately exits the
process.
To be consistent with the underlying platform, the SES default errorTrapping
of
'platform'
registers an uncaughtException
handler that feeds the
error to the tamed console so you can observe the stack trace, then exits
with a non-zero status code, favoring the existing value in process.exitCode
,
but defaulting to -1.
The default on Node.js is consistent with the underlying platform but
inconsistent with the principle of only granting the authority to cause
the container to exit explicitly, and we highly recommend setting
errorTrapping
to 'report'
explicitly.
'platform'
: is the default and is equivalent to'report'
on the Web or'exit'
on Node.js.'report'
: just report errors to the tamed console so stack traces appear.'exit'
: reports and exits on Node.js, reports and navigates away on the web.'abort'
: reports and aborts a Node.js process, leaving a core dump for postmortem analysis, reports and navigates away on the web.'none'
: do not install traps for uncaught exceptions. Errors are likely to appear as{}
when they are reported by the default trap.
Background: The error stacks shown by many JavaScript engines are
voluminous.
They contain many stack frames of functions in the infrastructure, that is
usually irrelevant to the programmer trying to disagnose a bug. The SES-shim's
console
, under the default consoleTaming
option of 'safe'
, is even more
voluminous—displaying "deep stack" traces, tracing back through the
eventually sent messages
from other turns of the event loop. In Endo (TODO docs forthcoming) these deep
stacks even cross vat/process and machine boundaries, to help debug distributed
bugs.
lockdown(); // stackFiltering defaults to 'concise'
// or
lockdown({ stackFiltering: 'concise' }); // Preserve important deep stack info
// vs
lockdown({ stackFiltering: 'verbose' }); // Console shows full deep stacks
When looking at deep distributed stacks, in order to debug distributed computation, seeing the full stacks is overwhelmingly noisy. The error stack proposal leaves it to the host what stack trace info to show. SES virtualizes elements of the host. With this freedom in mind, when possible, the SES-shim filters and transforms the stack trace information it shows to be more useful, by removing information that is more an artifact of low level infrastructure. The SES-shim currently does so only on v8.
However, sometimes your bug might be in that infrastrusture, in which case
that information is no longer an extraneous distraction. Sometimes the noise
you filter out actually contains the signal you're looking for. The
'verbose'
setting shows, on the console, the full raw stack information
for each level of the deep stacks.
Either setting of stackFiltering
setting is safe. Stack information will
or will not be available from error objects according to the errorTaming
option and the platform error behavior.
Examples from test-deep-send.js of the eventual-send shim:
Expand for { stackFiltering: 'concise' } log output
expected failure (Error#1)
Nested error
Error#1: Wut?
at Object.bar (packages/eventual-send/test/test-deep-send.js:13:21)
Error#1 ERROR_NOTE: Thrown from: (Error#2) : 2 . 0
Error#1 ERROR_NOTE: Rejection from: (Error#3) : 1 . 1
Nested 2 errors under Error#1
Error#2: Event: 1.1
at Object.foo (packages/eventual-send/test/test-deep-send.js:17:28)
Error#2 ERROR_NOTE: Caused by: (Error#3)
Nested error under Error#2
Error#3: Event: 0.1
at Object.test (packages/eventual-send/test/test-deep-send.js:21:22)
at packages/eventual-send/test/test-deep-send.js:25:19
at async Promise.all (index 0)
Expand for { stackFiltering: 'verbose' } log output
expected failure (Error#1)
Nested error
Error#1: Wut?
at makeError (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/install-ses/node_modules/ses/dist/ses.cjs:2976:17)
at Function.fail (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/install-ses/node_modules/ses/dist/ses.cjs:3109:19)
at Object.bar (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/test/test-deep-send.js:13:21)
at /Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:388:23
at Object.applyMethod (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:353:14)
at doIt (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:395:67)
at /Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/track-turns.js:64:22
at win (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:408:19)
at /Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:425:20
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Error#1 ERROR_NOTE: Thrown from: (Error#2) : 2 . 0
Error#1 ERROR_NOTE: Rejection from: (Error#3) : 1 . 1
Nested 2 errors under Error#1
Error#2: Event: 1.1
at trackTurns (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/track-turns.js:47:24)
at handle (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:396:27)
at Function.applyMethod (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:312:14)
at Proxy.<anonymous> (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/E.js:37:49)
at Object.foo (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/test/test-deep-send.js:17:28)
at /Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:388:23
at Object.applyMethod (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:353:14)
at doIt (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:395:67)
at /Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/track-turns.js:64:22
at win (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:408:19)
at /Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:425:20
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Error#2 ERROR_NOTE: Caused by: (Error#3)
Nested error under Error#2
Error#3: Event: 0.1
at trackTurns (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/track-turns.js:47:24)
at handle (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:396:27)
at Function.applyMethod (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/index.js:312:14)
at Proxy.<anonymous> (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/src/E.js:37:49)
at Object.test (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/test/test-deep-send.js:21:22)
at /Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/test/test-deep-send.js:25:19
at Test.callFn (/Users/markmiller/src/ongithub/agoric/agoric-sdk/node_modules/ava/lib/test.js:610:21)
at Test.run (/Users/markmiller/src/ongithub/agoric/agoric-sdk/node_modules/ava/lib/test.js:623:23)
at Runner.runSingle (/Users/markmiller/src/ongithub/agoric/agoric-sdk/node_modules/ava/lib/runner.js:280:33)
at Runner.runTest (/Users/markmiller/src/ongithub/agoric/agoric-sdk/node_modules/ava/lib/runner.js:348:30)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async Promise.all (index 0)
at async /Users/markmiller/src/ongithub/agoric/agoric-sdk/node_modules/ava/lib/runner.js:493:21
at async Runner.start (/Users/markmiller/src/ongithub/agoric/agoric-sdk/node_modules/ava/lib/runner.js:503:15)
Background: JavaScript suffers from the so-called
override mistake,
which prevents lockdown from simply hardening all the primordials. Rather,
for each of
these data properties, we convert it to an accessor
property whose getter and setter emulate a data property without the override
mistake. For non-reflective code
the illusion is perfect. But reflective code sees that it is an accessor
rather than a data property. We add an originalValue
property to the getter
of that accessor, letting reflective code know that a getter alleges that it
results from this transform, and what the original data value was. This enables
a form of cooperative emulation, where that code can decide whether to uphold
the illusion by pretending it sees the data property that would have been there.
The VSCode debugger's object inspector shows the own properties of an object, which is a great aid to debugging. Unfortunately, it also shows the inherited accessor properties, with one line for the getter and another line for the setter. As we enable override on more properties of widely used prototypes, we become compatible with more legacy code, but at the price of a significantly worse debugging experience. Expand the "Expand for..." items at the end of this section for screenshots showing the different experiences.
Enablements have a further debugging cost. When single stepping into code, we now step into every access to an enabled property. Every read steps into the enabling getter. This adds yet more noise to the debugging experience.
The file src/enablements.js exports three different
whitelists definining which data properties to convert to enable override by
assignment, minEnablements
, moderateEnablements
, and severeEnablements
.
lockdown(); // overrideTaming defaults to 'moderate'
// or
lockdown({ overrideTaming: 'moderate' }); // Legacy compat usually good enough
// vs
lockdown({ overrideTaming: 'min' }); // Minimal mitigations for purely modern code
// vs
lockdown({ overrideTaming: 'severe' }); // More severe legacy compat
The overrideTaming
default 'moderate'
option of lockdown
is intended to
be fairly minimal, but we expand it as needed, when we
encounter code which should run under SES but is prevented from doing so
by the override mistake. As we encouter these we list them in the comments
next to each enablement. This process has rapidly converged. We rarely come
across any more such cases. If you find one, please file an issue. Thanks.
The 'min'
enablements setting serves two purposes: it enables a pleasant
debugging experience in VSCode, and it helps ensure that new code does not
depend on anything more than these being enabled, which is good practice.
All code authored by Agoric will be compatible with both settings, but
Agoric currently still pulls in some third party dependencies only compatible
with the 'moderate'
setting.
The 'severe'
setting enables all the properties on at least
Object.prototype
, which is sometimes needed for
compatibility with code generated by rollup or webpack. However, this extra
compatibility comes at the price of a miserable debugging experience.
The following screenshots shows inspection of the { abc: 123 }
object, both
by hover and in the rightmost "VARIABLES" pane.
Only the abc
property is normally useful. All other lines are noise introduced
by our override mitigation.
To help diagnose problems with the override mistake, you can set this option to a list of properties that will print diagnostic information when their override enablement is triggered.
For example, to find the client code that causes a constructor
property override
mistake, set the options as follows:
{
overrideTaming: 'severe',
overrideDebug: ['constructor']
}
The idiom for @agoric/install-ses
when tracking down the override
mistake with the constructor
property is to set the following
environment variable:
LOCKDOWN_OPTIONS='{"errorTaming":"unsafe","stackFiltering":"verbose","overrideTaming":"severe","overrideDebug":["constructor"]}'
Then, when some script deep in the require stack does:
function MyConstructor() { }
MyConstructor.prototype.constructor = XXX;
the caller backtrace will be logged to the console, such as:
(Error#1)
Error#1: Override property constructor
at Object.setter (packages/ses/src/enable-property-overrides.js:114:27)
at packages/ses/test/override-tester.js:26:19
at overrideTester (packages/ses/test/override-tester.js:25:9)
at packages/ses/test/test-enable-property-overrides-severe-debug.js:14:3
The deprecated Node.js domain
module adds domain
properties to callbacks
and promises.
These domain
properties allow communication between client programs that
lockdown
otherwise isolates.
To disable this safety feature, call lockdown
with domainTaming
set to
'unsafe'
explicitly.
lockdown({
domainTaming: 'unsafe'
});
The domainTaming
option, when set to 'safe'
, protects programs
by detecting whether the 'domain'
module has been initialized, and by laying
a trap that prevents it from initializing later.
The domain
module adds a domain
object to the process
global object,
so it's possible to detect without consulting the module system.
Defining a non-configurable domain
property on the process
object
causes any later attempt to initialize domains to fail loudly.
Unfortunately, some modules ultimately depend on the domain
module,
even when they do not actively use its features.
To run multi-tenant applications safely, these dependencies must be carefully
fixed or avoided.
Sometimes SES is used where SES's safety is not required. Some libraries are not compatible with SES because they monkey patch the shared primordials is ways SES cannot allow. We temporarily introduce this option to enable some of these libraries to work in, approximately, a SES environment whose safety was sacrificed in order to allow this monkey patching to succeed.
With this option set to 'unsafe'
, SES initialization
does not harden the primordials, leaving them in their fully
mutable state. But the rest of SES initialization does happen, including
allowing harden
to work. Since harden
is transitively contagious
by inheritance and own property traversal, any use of harden
on any
object will also happen to harden all primordials reachable from that
object. For example, harden({})
will harden Object.prototype
,
Object
, Function.prototype
, Function.prototype.constructor
(which under SES is not the same as the Function
constructor), and all
the methods reachable from any of these.
Because of this transitive hardening
, the kludge enabled by this
option may or may not work for any particular monkey patching library.
React seems to be a library for which this kludge does work, because
React seems to do its monkey patching is ways that either avoid or
precede the freezing of primordial caused by other uses of harden
.
This option thereby enables React to work under SES and after lockdown
in a browser, and should only be used if the compromise of safety on
that browser page is acceptable.
The "__
" in the option name indicates that this option is temporary.
As we encounter libraries that need this option, such as React, we
plan to encourage them to be fixed
so that they work correctly
under SES after SES initialization. Once enough of these are fixed,
we hope to deprecate and eventually remove this option.
For some libraries the monkey patching they are doing can be recast
as a separate shim that could be vetted to not introduce any violation of SES
safety. This may include all the React problems we're seeing! We do plan to
extend the SES initialization mechanism to be able to run vetted shims
during SES initialization: after repairs and before hardening the primordials.
This environment would be much like the environment created by the 'unsafe'
setting of this option but without a working harden
. But we have not yet done
so, and we don't yet know if React's monkey patching could be made into a
separate vetted shim that runs early.