Comparing version 0.1.2 to 0.1.3
@@ -23,8 +23,13 @@ console.log('starting'); | ||
function delayMS(count, value) { | ||
// return a Promise that fires (with 'value') 'count' milliseconds in the | ||
// future | ||
return new Promise((resolve, reject) => window.setTimeout(resolve, count, value)); | ||
function delayMS(count) { | ||
// busywait for 'count' milliseconds | ||
const target = Date.now() + count; | ||
while (Date.now() < target) { | ||
} | ||
} | ||
function refreshUI() { | ||
return new Promise((resolve, reject) => window.setTimeout(resolve, 0, undefined)); | ||
} | ||
const attackerGuess = document.getElementById('guess'); | ||
@@ -35,10 +40,33 @@ function setAttackerGuess(text) { | ||
function launch() { | ||
document.getElementById("guess-box").className = "code-box launched"; | ||
function setLaunch(status) { | ||
if (status) { | ||
document.getElementById("guess-box").className = "code-box launched"; | ||
} else { | ||
document.getElementById("guess-box").className = "code-box"; | ||
} | ||
} | ||
function log(...args) { | ||
// TODO: can console.log be coerced into throwing an exception, or | ||
// returning any other primal-realm objects? If so, the attacker could | ||
// use their log() access to corrupt the primal realm and escape. | ||
console.log(...args); | ||
} | ||
// two approaches: | ||
// * force in-order delivery: | ||
// maintain queue of (delay, resolver) pairs | ||
// when guess() is called: compute delay, build promise, append (delay,resolver) | ||
// - if queue was empty, call setTimeout(queue[0].delay) | ||
// - then return promise | ||
// when setTimeout fires: pop queue, fire resolver on next turn | ||
// - if queue is non-empty, start new timer for queue[0].delay | ||
// * better: don't give UI queue access to attacker | ||
// attacker provides stateful object with go() method | ||
// framework calls go(guess_func) | ||
// attacker can use Promises and call guess_func(guess) | ||
// guess_func returns synchronously (after busywait) | ||
// limit attacker to some finite number of calls per go() | ||
// framework updates UI (with setTimeout(0)), then calls go() again | ||
// build the SES Realm and evaluate the defender inside | ||
@@ -53,3 +81,3 @@ const options = {}; | ||
const defenderSrc = buildDefenderSrc(); | ||
const d = r.evaluate(defenderSrc, { getRandomValues, setMacguffinText, delayMS, setAttackerGuess, launch, log }); | ||
const d = r.evaluate(defenderSrc, { getRandomValues, setMacguffinText, delayMS, refreshUI, setAttackerGuess, setLaunch, log }); | ||
@@ -64,3 +92,6 @@ // now create the form that lets the user submit attacker code | ||
console.log('executing attacker code:', code); | ||
d.submitProgram(code); | ||
d.stopAttacker(); | ||
// wait a moment to make sure the running program notices the stop request | ||
const wait = new Promise((resolve, reject) => window.setTimeout(resolve, 10, undefined)); | ||
wait.then(() => d.submitProgram(code)); | ||
}); | ||
@@ -99,92 +130,74 @@ aStop.addEventListener('click', function stop(event) { | ||
// actually use these values. | ||
let guess, checkEnabled, log; | ||
let guess, log; | ||
function allZeros() { | ||
guess('0000000000'); | ||
function* allZeros() { | ||
guess('0000000000'); | ||
} | ||
} | ||
function counter() { | ||
let i = 0; | ||
function submitNext(correct) { | ||
if (correct || !checkEnabled()) { | ||
return; | ||
} | ||
let guessedCode = i.toString(36).toUpperCase(); | ||
while (guessedCode.length < 10) { | ||
guessedCode = '0' + guessedCode; | ||
} | ||
i += 1; | ||
guess(guessedCode).then(submitNext); | ||
function *counter() { | ||
for (let i = 0; true; i++) { | ||
let guessedCode = i.toString(36).toUpperCase(); | ||
while (guessedCode.length < 10) { | ||
guessedCode = '0' + guessedCode; | ||
} | ||
guess(guessedCode); | ||
yield; | ||
}; | ||
} | ||
submitNext(false); | ||
} | ||
function timing() { | ||
function checkOneGuess(guessedCode) { | ||
let start = Date.now(); | ||
return guess(guessedCode).then(correct => { | ||
let elapsed = Date.now() - start; | ||
return { elapsed, correct }; | ||
}); | ||
} | ||
function toChar(c) { | ||
return c.toString(36).toUpperCase(); | ||
} | ||
function fastestChar(delays) { | ||
return Array.from(delays.keys() | ||
).reduce((a, b) => delays.get(a) > delays.get(b) ? a : b); | ||
} | ||
function *timing() { | ||
function toChar(c) { | ||
return c.toString(36).toUpperCase(); | ||
} | ||
let known = ''; | ||
let c = 0; | ||
let delays; | ||
function fastestChar(delays) { | ||
const pairs = Array.from(delays.entries()); | ||
pairs.sort((a,b) => b[1] - a[1]); | ||
return pairs[0][0]; | ||
} | ||
function reset() { | ||
known = ''; | ||
c = 0; | ||
} | ||
function insert(into, offset, char) { | ||
return into.slice(0, offset) + char + into.slice(offset+1, into.length); | ||
} | ||
function checkNext() { | ||
if (c === 0) { | ||
delays = new Map(); | ||
function buildCode(base, offset, c) { | ||
// keep the first 'offset' chars of base, set [offset] to c, fill the | ||
// rest with random-looking junk to make the demo look cool | ||
// (random-looking, not truly random, because we're deterministic) | ||
let code = insert(base, offset, toChar(c)); | ||
for (let off2 = offset+1; off2 < 10; off2++) { | ||
code = insert(code, off2, toChar((off2*3 + c*7) % 36)); | ||
} | ||
return code; | ||
} | ||
const guessCharacter = toChar(c); | ||
let guessedCode = known + guessCharacter; | ||
while (guessedCode.length < 10) { | ||
guessedCode = guessedCode + '0'; | ||
} | ||
return checkOneGuess(guessedCode).then(o => { | ||
const { elapsed, correct } = o; | ||
if (correct || !checkEnabled()) { | ||
return true; | ||
} | ||
//log(`delay(${guessedCode}) was ${elapsed}`); | ||
delays.set(guessCharacter, elapsed); | ||
c += 1; | ||
if (c === 36) { | ||
const next = fastestChar(delays); | ||
log(`Adding ${known} + ${next}`); | ||
known = known + next; | ||
if (known.length === 10) { | ||
// if we're right, we never actually reach here, since we guessed | ||
// correctly earlier, and a correct guess disables the attacker | ||
log(`I think the code is ${known}`); | ||
return guess(known).then(correct => { | ||
if (correct) { | ||
log(`I was right, muahaha`); | ||
return true; | ||
} | ||
log('we must have measured the timings wrong, try again'); | ||
reset(); | ||
return checkNext(); | ||
}); | ||
let base = '0000000000'; | ||
while (true) { | ||
for (let offset = 0; offset < 10; offset++) { | ||
const delays = new Map(); | ||
for (let c = 0; c < 36; c++) { | ||
const guessedCode = buildCode(base, offset, c); | ||
const start = Date.now(); | ||
guess(guessedCode); | ||
const elapsed = Date.now() - start; | ||
delays.set(toChar(c), elapsed); | ||
yield; // allow UI to refresh | ||
// if our guess was right, then on the last character | ||
// (offset===9) we never actually reach here, since we guessed | ||
// correctly earlier, and when the attacker guesses correctly, | ||
// the defender stops calling go() | ||
} | ||
c = 0; | ||
log(delays); | ||
const nextChar = fastestChar(delays); | ||
base = insert(base, offset, nextChar); | ||
log(`Setting code[${offset}]=${nextChar} -> ${base}`); | ||
} | ||
return checkNext(); | ||
}); | ||
log('we must have measured the timings wrong, try again'); | ||
} | ||
} | ||
checkNext(); | ||
} | ||
@@ -198,4 +211,4 @@ | ||
// actually use these values. | ||
let getRandomValues, setMacguffinText, delayMS, setAttackerGuess, launch, log; | ||
let SES, def; | ||
let getRandomValues, setMacguffinText, delayMS, setAttackerGuess, setLaunch, log; | ||
let SES, def, refreshUI; | ||
@@ -228,32 +241,54 @@ // this is stringified and loaded in the SES realm, with several endowments | ||
function attackerLog(...args) { | ||
log(...args); | ||
} | ||
function guess(guessedCode) { | ||
// To demonstrate how deterministic attacker code cannot sense covert | ||
// channels, we provide a pretty obvious covert channel. | ||
// channels, we provide a pretty obvious covert channel: we compare one | ||
// character at a time, and busy-wait a long time between characters. | ||
// The time we take indicates how many leading characters they got | ||
// right, enabling a linear-time guessing attack. | ||
guessedCode = `${guessedCode}`; // force into a String | ||
setAttackerGuess(guessedCode); | ||
let delay = 0; | ||
for (let i=0; i < 10; i++) { | ||
if (secretCode.slice(i, i+1) !== guessedCode.slice(i, i+1)) { | ||
return delayMS(delay, false); | ||
return false; | ||
} | ||
delay += 10; | ||
delayMS(10); | ||
} | ||
// they guessed correctly | ||
enableAttacker = false; | ||
launch(); | ||
return delayMS(delay, true); | ||
setLaunch(true); | ||
return true; | ||
} | ||
function checkEnabled() { | ||
return enableAttacker; | ||
} | ||
function submitProgram(program) { | ||
// the attacker's code will be submitted here. We expect it to be a | ||
// generator function, starting with 'function*' and ending with the | ||
// closing curly brace | ||
function attackerLog(...args) { | ||
log(...args); | ||
} | ||
program = `(${program})`; // turn it into an expression | ||
function submitProgram(program) { | ||
// the attacker's code will be submitted here | ||
enableAttacker = true; | ||
SES.confine(program, { guess, checkEnabled, log: attackerLog }); | ||
setLaunch(false); | ||
const attacker = SES.confine(program, { guess: guess, log: attackerLog }); | ||
const attackGen = attacker(); // build the generator | ||
function nextGuess() { | ||
if (!enableAttacker) { | ||
return; // attacker was interrupted, so don't ask | ||
} | ||
// give the attacker another chance to run | ||
if (attackGen.next().done) { | ||
return; // attacker gave up, so stop asking | ||
} | ||
if (!enableAttacker) { | ||
return; // attacker was correct, so stop asking | ||
} | ||
// now let the UI refresh before we call attacker again | ||
refreshUI().then(nextGuess); | ||
} | ||
nextGuess(); | ||
} | ||
@@ -260,0 +295,0 @@ |
@@ -8,4 +8,233 @@ # SES Demo | ||
For local testing, run a web server and serve the entire git tree (the demo | ||
accesses the generated `ROOT/dist/ses-shim.js` file, so serving just this | ||
`demo/` directory is not enough). Re-run `npm run-script build` after any | ||
changes to the source code to rebuild `ses-shim.js`. | ||
accesses the generated ``ROOT/dist/ses-shim.js`` file, so serving just this | ||
``demo/`` directory is not enough). Re-run ``npm run-script build`` after any | ||
changes to the source code to rebuild ``ses-shim.js``. | ||
## Would You Like To Play A Game? | ||
Remember the scene from [WarGames](https://www.imdb.com/title/tt0086567/) | ||
where the computer is trying to guess the launch codes? Here, you get to play | ||
the computer. | ||
## Attacker | ||
You provide the attacker's program by pasting its source code into the box | ||
and pressing the Execute button. The attack program is confined in an SES | ||
environment (shared with the defender), which means it is limited to | ||
``strict`` mode and has access to the usual ECMAScript primordials (Object, | ||
String, Array, Math, Map, Date (but see below), and so on). But it does not | ||
have access to platform objects (``window``, ``document``, or ``XHR`` on a | ||
web browser, or ``require`` on Node.js). | ||
In addition to the primordials, the attacker's program gets access to two | ||
endowments: ``guess(code)`` and ``log(message)``. These are provided by the | ||
defender. | ||
This source code must evaluate to a generator function (starting with | ||
`function*` and ending with a closing curly brace), which will be called once | ||
to produce a generator. That generator will be iterated again and again until | ||
it exits or the secret code is guessed correctly. | ||
```js | ||
function* guessZeros() { | ||
guess('0000000000'); | ||
} | ||
``` | ||
The attacker uses ``guess()`` to try and guess the launch code. This guess is | ||
displayed on the bottom panel. When the guess matches the code on the top | ||
panel, the attacker wins and the game is over. The codes are ten characters | ||
long, and each character is a capital letter from A to Z, or a digit from 0 | ||
to 9. There are ``36**10`` possibilities (about three quadrillion, or about | ||
``2**51``, which also happens to be roughly how many ants are alive on Earth | ||
at any given moment). It takes at least a few milliseconds to check each one, | ||
so brute-force guessing would take thousands of years to try all possible | ||
combinations. | ||
We use a generator function, rather than an ordinary function, as a | ||
concession to make the demo look more interesting. In a more realistic setup, | ||
the attacker code would do all its work during its singular evaluation (it | ||
could make as many calls to guess() as it liked), with something like this: | ||
```js | ||
for (let i = 0; true; i++) { | ||
let guessedCode = i.toString(36).toUpperCase(); | ||
while (guessedCode.length < 10) { | ||
guessedCode = '0' + guessedCode; // pad to 10 characters | ||
} | ||
guess(guessedCode); | ||
} | ||
``` | ||
But in a single-threaded browser context, that wouldn't give the framework an | ||
opportunity to update the UI. In addition, the web browser would pop a "this | ||
script is taking too long" dialog box after maybe 15 seconds of constant | ||
execution, since it is effectively stuck in an infinite loop. | ||
By using a generator, we can update the UI once per iteration. Thus the | ||
attack function should call ``guess()`` once, then yield from the generator, | ||
then loop back around if it wants to make more guesses. The above program | ||
should be rewritten like this: | ||
```js | ||
function* counter() { | ||
for (let i = 0; true; i++) { | ||
let guessedCode = i.toString(36).toUpperCase(); | ||
while (guessedCode.length < 10) { | ||
guessedCode = '0' + guessedCode; // pad to 10 characters | ||
} | ||
guess(guessedCode); | ||
yield; | ||
} | ||
``` | ||
Despite this quirk, the attacker's program is still essentially being | ||
evaluated as purely transformational code: the way we load the attacker isn't | ||
quite as important as the way we allow it to call ``guess()``. | ||
## Defender | ||
The defender in this game has slightly more power than the attacker: it is | ||
SES-confined as well, but it gets additional endowments from the setup code. | ||
One of these is a form of ``getRandomValues`` so it can select a random | ||
target code: SES programs are normally denied access to non-determinism, so | ||
it would have no way of choosing a different code on each pageload. A few | ||
more endowments provide control over the DOM: ``setMacguffinText`` sets the | ||
target code in the top box, ``setAttackerGuess`` sets the guessed code in the | ||
bottom box, ``setLaunch`` changes the CSS class of the bottom box to change | ||
the text color when the guess is correct. Note that, for this demo, none of | ||
these endowments are particularly defensive: the defender code could use them | ||
to break out of the SES sandbox. | ||
``refreshUI`` returns a Promise that resolves after a ``setTimeout(0)``. This | ||
yields control back to the browser's UI event queue, giving it a chance to | ||
repaint the screen with the updated codes. ``log`` simply delivers its | ||
arguments to the browser's usual ``console.log``. | ||
``delayMS`` performs a busy-wait for the given number of milliseconds by | ||
polling ``Date.now`` until it reaches some target value. | ||
We use ``delayMS()`` to introduce an egregious timing-channel vulnerability | ||
into the defender's ``guess()`` function: it checks the attacker's guess one | ||
character at a time, waiting a full 10ms between each test, and returns | ||
immediately upon the first incorrect failure. If the attacker can measure how | ||
long ``guess()`` takes to run, they can mount a classic timing-oracle attack | ||
which runs in linear (rather than exponential) time. A safer form of | ||
``guess()`` would do a constant-time comparison (for which the most practical | ||
approach is to just hash both sides and compare the hashes). Our vulnerable | ||
``guess()`` looks like this: | ||
```js | ||
function guess(guessedCode) { | ||
guessedCode = `${guessedCode}`; // force into a String | ||
setAttackerGuess(guessedCode); | ||
for (let i=0; i < 10; i++) { | ||
if (secretCode.slice(i, i+1) !== guessedCode.slice(i, i+1)) { | ||
return false; | ||
} | ||
delayMS(10); | ||
} | ||
// they guessed correctly | ||
enableAttacker = false; | ||
setLaunch(true); | ||
return true; | ||
} | ||
``` | ||
The defender creates the ``guess()`` function, then provides it (and ``log``) | ||
as endowments to the attacker. It invokes ``SES.confine`` to evaluate the | ||
attacker's code with the endowments as the second argument: | ||
```js | ||
function attackerLog(...args) { | ||
log(...args); | ||
} | ||
const attacker = SES.confine(program, { guess: guess, log: attackerLog }); | ||
``` | ||
The defender then invokes the attacker in a loop, using ``refreshUI()`` to | ||
delay each pass until the UI had a chance to be updated, with something like | ||
this: | ||
```js | ||
program = `(${program})`; // turn it into an expression | ||
const attacker = SES.confine(program, { guess: guess, log: attackerLog }); | ||
const attackGen = attacker(); // build the generator | ||
function nextGuess() { | ||
// give the attacker another chance to run | ||
if (attackGen.next().done) { | ||
return; // attacker gave up, so stop asking | ||
} | ||
// now let the UI refresh before we call attacker again | ||
refreshUI().then(nextGuess); | ||
} | ||
nextGuess(); | ||
``` | ||
(some additional lines exist to stop the loop if/when the attacker gets the | ||
code right). | ||
## Taming Date.now | ||
The SES environment normally replaces ``Date.now()`` with a function that | ||
only returns ``NaN``. But this can be disabled by setting a configuration | ||
option named ``dateNowTrap`` to ``false``. | ||
(Note that this API is still in flux, and we might change it in the future. | ||
One interesting option might be to set ``Date.now`` to return a constant, | ||
pre-selected value, enabling more code to run mostly-correctly, while still | ||
limiting its use as an attack vector) | ||
SES replaces the ``new Date()`` constructor with a tamed version that acts as | ||
if you wrote ``new Date(NaN)``. It does not currently have an option to | ||
change this behavior, but that will probably change too. | ||
By prohibiting access to a clock, we can prevent the attacker code from | ||
sensing timing-based covert channel. Fully-deterministic execution cannot | ||
sense any covert channel, since the output must be a strict function of the | ||
declared input, and the covert channel is (by definition) not part of the | ||
input arguments. | ||
However we must be careful to not inadvertently provide access to a clock. | ||
[Fantastic Timers and Where to Find | ||
Them](https://gruss.cc/files/fantastictimers.pdf) (by Schwarz, Maurice, | ||
Gruss, and Mangard) enumerates a variety of surprising clocks that might be | ||
available to Javascript code. They all depend upon forms of non-determinism, | ||
such as: | ||
* shared-state mutability: a ``WebWorker`` writes sequential integers to a | ||
shared ``ArrayBuffer`` as fast as it can, while a second thread simply | ||
reads from that location when desired | ||
* platform-provided UI features: initiate a CSS animation and monitor its | ||
progress | ||
* message-passing: two separate frames exchange ``postMessage`` calls and | ||
compare their arrival order with ones sent to themselves | ||
* explicit platform features: the ``performance.now()`` call offers | ||
microsecond-level timing | ||
SES omits all platform features, which removes all the timers listed in this | ||
paper. However it is easy to accidentally reintroduce some through | ||
endowments. For example you might give the confined code the ability to send | ||
messages to a remote system (which itself has access to a clock). When | ||
multiple sources send messages to a common recipient, those messages will be | ||
interleaved in some nondeterminisic fashion that depends upon the arrival | ||
times: that ordering can also be used as a clock. | ||
The demo page has two flavors: by changing the URL slightly, the attacker can | ||
be allowed or denied access to ``Date.now()``. This makes it easy to | ||
demonstrate the success or failure of a timing-based attack. | ||
The first version of this demo implemented ``delayMS()`` with a Promise that | ||
resolved at some point in the future (via a ``setTimeout()`` callback). In | ||
that version, ``guess()`` returned a Promise. Richard Gibson exploited this | ||
in a [clever attack](https://github.com/Agoric/SES/issues/8) that submitted | ||
multiple guesses in parallel and sensed the order of their resolution: it | ||
didn't reveal exactly how long ``guess()`` took, but knowing which guess took | ||
the longest was enough to mount the attack. We thought we were only using | ||
``setTimeout()`` for internal purposes, but by exposing a function that used | ||
it, we accidentally gave the attacker code enough tools to build a clock of | ||
their own. | ||
The lesson is to be careful when building endowments, especially if you build | ||
them from powerful components that live outside the SES environment. |
{ | ||
"name": "ses", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"description": "Secure ECMAScript", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
@@ -20,3 +20,3 @@ # Secure EcmaScript (SES) | ||
flavors of confined EcmaScript execution. And visit | ||
https://cdn.rawgit.com/Agoric/SES/0.1.0/demo/ for a demo. | ||
https://rawgit.com/Agoric/SES/master/demo/ for a demo. | ||
@@ -23,0 +23,0 @@ Derived from the Caja project, https://github.com/google/caja/wiki/SES . |
@@ -24,2 +24,3 @@ // Copyright (C) 2018 Agoric | ||
r.global.def = b.def; | ||
r.global.Nat = b.Nat; | ||
@@ -108,2 +109,4 @@ b.deepFreezePrimordials(r.global); | ||
() => parentRealm.evaluate(code, endowments)); | ||
global.SES.confineExpr = (code, endowments) => callAndWrapError( | ||
() => parentRealm.evaluate(`(${code})`, endowments)); | ||
} |
@@ -21,6 +21,7 @@ // Copyright (C) 2018 Agoric | ||
import { def } from './def.js'; | ||
import { Nat } from './nat.js'; | ||
export { createSESWithRealmConstructor, createSESInThisRealm, | ||
deepFreezePrimordials, removeProperties, tamePrimordials, getAnonIntrinsics, | ||
def | ||
def, Nat | ||
}; |
@@ -109,2 +109,3 @@ // Copyright (C) 2011 Google Inc. | ||
var t = true; | ||
var j = true; // included in the Jessie runtime | ||
var TypedArrayWhitelist; // defined and used below | ||
@@ -208,8 +209,8 @@ | ||
constFunc: t, | ||
Nat: t, | ||
def: t, | ||
Nat: j, | ||
def: j, | ||
is: t, | ||
compileExpr: t, | ||
confine: t, | ||
confine: j, | ||
compileModule: t, // experimental | ||
@@ -249,5 +250,5 @@ compileProgram: t, // Cannot be implemented in just ES5.1. | ||
// 18.1 | ||
Infinity: t, | ||
NaN: t, | ||
undefined: t, | ||
Infinity: j, | ||
NaN: j, | ||
undefined: j, | ||
@@ -266,3 +267,3 @@ // 18.2 | ||
// 19 Fundamental Objects | ||
@@ -276,3 +277,3 @@ | ||
entries: t, // ES-Harmony | ||
freeze: t, | ||
freeze: j, | ||
getOwnPropertyDescriptor: t, | ||
@@ -283,3 +284,3 @@ getOwnPropertyDescriptors: t, // proposed ES-Harmony | ||
getPrototypeOf: t, | ||
is: t, // ES-Harmony | ||
is: j, // ES-Harmony | ||
isExtensible: t, | ||
@@ -289,4 +290,4 @@ isFrozen: t, | ||
keys: t, | ||
preventExtensions: t, | ||
seal: t, | ||
preventExtensions: j, | ||
seal: j, | ||
setPrototypeOf: t, // ES-Harmony | ||
@@ -319,3 +320,3 @@ values: t, // ES-Harmony | ||
}, | ||
Function: { // 19.2 | ||
@@ -339,7 +340,7 @@ length: t, | ||
}, | ||
Boolean: { // 19.3 | ||
prototype: t | ||
}, | ||
Symbol: { // 19.4 all ES-Harmony | ||
@@ -399,12 +400,12 @@ asyncIterator: t, // proposed? ES-Harmony | ||
// 20 Numbers and Dates | ||
Number: { // 20.1 | ||
EPSILON: t, // ES-Harmony | ||
isFinite: t, // ES-Harmony | ||
isFinite: j, // ES-Harmony | ||
isInteger: t, // ES-Harmony | ||
isNaN: t, // ES-Harmony | ||
isSafeInteger: t, // ES-Harmony | ||
MAX_SAFE_INTEGER: t, // ES-Harmony | ||
isNaN: j, // ES-Harmony | ||
isSafeInteger: j, // ES-Harmony | ||
MAX_SAFE_INTEGER: j, // ES-Harmony | ||
MAX_VALUE: t, | ||
MIN_SAFE_INTEGER: t, // ES-Harmony | ||
MIN_SAFE_INTEGER: j, // ES-Harmony | ||
MIN_VALUE: t, | ||
@@ -424,12 +425,12 @@ NaN: t, | ||
Math: { // 20.2 | ||
E: t, | ||
LN10: t, | ||
LN2: t, | ||
E: j, | ||
LN10: j, | ||
LN2: j, | ||
LOG10E: t, | ||
LOG2E: t, | ||
PI: t, | ||
PI: j, | ||
SQRT1_2: t, | ||
SQRT2: t, | ||
abs: t, | ||
abs: j, | ||
acos: t, | ||
@@ -443,3 +444,3 @@ acosh: t, // ES-Harmony | ||
cbrt: t, // ES-Harmony | ||
ceil: t, | ||
ceil: j, | ||
clz32: t, // ES-Harmony | ||
@@ -450,22 +451,22 @@ cos: t, | ||
expm1: t, // ES-Harmony | ||
floor: t, | ||
floor: j, | ||
fround: t, // ES-Harmony | ||
hypot: t, // ES-Harmony | ||
imul: t, // ES-Harmony | ||
log: t, | ||
log: j, | ||
log1p: t, // ES-Harmony | ||
log10: t, // ES-Harmony | ||
log2: t, // ES-Harmony | ||
max: t, | ||
min: t, | ||
pow: t, | ||
log10: j, // ES-Harmony | ||
log2: j, // ES-Harmony | ||
max: j, | ||
min: j, | ||
pow: j, | ||
random: t, // questionable | ||
round: t, | ||
round: j, | ||
sign: t, // ES-Harmony | ||
sin: t, | ||
sinh: t, // ES-Harmony | ||
sqrt: t, | ||
sqrt: j, | ||
tan: t, | ||
tanh: t, // ES-Harmony | ||
trunc: t // ES-Harmony | ||
trunc: j // ES-Harmony | ||
}, | ||
@@ -533,5 +534,5 @@ | ||
String: { // 21.2 | ||
fromCharCode: t, | ||
fromCharCode: j, | ||
fromCodePoint: t, // ES-Harmony | ||
raw: t, // ES-Harmony | ||
raw: j, // ES-Harmony | ||
prototype: { | ||
@@ -542,6 +543,6 @@ charAt: t, | ||
concat: t, | ||
endsWith: t, // ES-Harmony | ||
endsWith: j, // ES-Harmony | ||
includes: t, // ES-Harmony | ||
indexOf: t, | ||
lastIndexOf: t, | ||
indexOf: j, | ||
lastIndexOf: j, | ||
localeCompare: t, | ||
@@ -555,5 +556,5 @@ match: t, | ||
search: t, | ||
slice: t, | ||
slice: j, | ||
split: t, | ||
startsWith: t, // ES-Harmony | ||
startsWith: j, // ES-Harmony | ||
substring: t, | ||
@@ -569,18 +570,18 @@ toLocaleLowerCase: t, | ||
anchor: t, | ||
big: t, | ||
blink: t, | ||
bold: t, | ||
fixed: t, | ||
fontcolor: t, | ||
fontsize: t, | ||
italics: t, | ||
link: t, | ||
small: t, | ||
strike: t, | ||
sub: t, | ||
sup: t, | ||
big: t, | ||
blink: t, | ||
bold: t, | ||
fixed: t, | ||
fontcolor: t, | ||
fontsize: t, | ||
italics: t, | ||
link: t, | ||
small: t, | ||
strike: t, | ||
sub: t, | ||
sup: t, | ||
trimLeft: t, // non-standard | ||
trimRight: t, // non-standard | ||
// 21.1.4 instances | ||
@@ -604,3 +605,3 @@ length: '*' | ||
sticky: 'maybeAccessor', | ||
test: t, | ||
test: t, | ||
unicode: 'maybeAccessor', // ES-Harmony | ||
@@ -619,5 +620,5 @@ dotAll: 'maybeAccessor', // proposed ES-Harmony | ||
Array: { // 22.1 | ||
from: t, | ||
from: j, | ||
isArray: t, | ||
of: t, // ES-Harmony? | ||
of: j, // ES-Harmony? | ||
prototype: { | ||
@@ -629,23 +630,23 @@ concat: t, | ||
fill: t, // ES-Harmony | ||
filter: t, | ||
filter: j, | ||
find: t, // ES-Harmony | ||
findIndex: t, // ES-Harmony | ||
forEach: t, | ||
forEach: j, | ||
includes: t, // ES-Harmony | ||
indexOf: t, | ||
indexOf: j, | ||
join: t, | ||
keys: t, // ES-Harmony | ||
lastIndexOf: t, | ||
map: t, | ||
pop: t, | ||
push: t, | ||
reduce: t, | ||
reduceRight: t, | ||
lastIndexOf: j, | ||
map: j, | ||
pop: j, | ||
push: j, | ||
reduce: j, | ||
reduceRight: j, | ||
reverse: t, | ||
shift: t, | ||
slice: t, | ||
shift: j, | ||
slice: j, | ||
some: t, | ||
sort: t, | ||
splice: t, | ||
unshift: t, | ||
unshift: j, | ||
values: t, // ES-Harmony | ||
@@ -663,3 +664,3 @@ | ||
// TODO: Not yet organized according to spec order | ||
Int8Array: TypedArrayWhitelist, | ||
@@ -680,12 +681,12 @@ Uint8Array: TypedArrayWhitelist, | ||
prototype: { | ||
clear: t, | ||
delete: t, | ||
entries: t, | ||
forEach: t, | ||
get: t, | ||
has: t, | ||
keys: t, | ||
set: t, | ||
clear: j, | ||
delete: j, | ||
entries: j, | ||
forEach: j, | ||
get: j, | ||
has: j, | ||
keys: j, | ||
set: j, | ||
size: 'maybeAccessor', | ||
values: t | ||
values: j | ||
} | ||
@@ -696,21 +697,21 @@ }, | ||
prototype: { | ||
add: t, | ||
clear: t, | ||
delete: t, | ||
entries: t, | ||
forEach: t, | ||
has: t, | ||
keys: t, | ||
add: j, | ||
clear: j, | ||
delete: j, | ||
entries: j, | ||
forEach: j, | ||
has: j, | ||
keys: j, | ||
size: 'maybeAccessor', | ||
values: t | ||
values: j | ||
} | ||
}, | ||
WeakMap: { // 23.3 | ||
prototype: { | ||
// Note: coordinate this list with maintenance of repairES5.js | ||
delete: t, | ||
get: t, | ||
has: t, | ||
set: t | ||
delete: j, | ||
get: j, | ||
has: j, | ||
set: j | ||
} | ||
@@ -721,5 +722,5 @@ }, | ||
prototype: { | ||
add: t, | ||
delete: t, | ||
has: t | ||
add: j, | ||
delete: j, | ||
has: j | ||
} | ||
@@ -773,4 +774,4 @@ }, | ||
JSON: { // 24.5 | ||
parse: t, | ||
stringify: t | ||
parse: j, | ||
stringify: j | ||
}, | ||
@@ -782,9 +783,9 @@ | ||
Promise: { // 25.4 | ||
all: t, | ||
race: t, | ||
reject: t, | ||
resolve: t, | ||
all: j, | ||
race: j, | ||
reject: j, | ||
resolve: j, | ||
prototype: { | ||
catch: t, | ||
then: t, | ||
then: j, | ||
finally: t, // proposed ES-Harmony | ||
@@ -850,3 +851,3 @@ | ||
}, | ||
Proxy: { // 26.2 | ||
@@ -865,3 +866,3 @@ revocable: t | ||
// Other | ||
StringMap: { // A specialized approximation of ES-Harmony's Map. | ||
@@ -868,0 +869,0 @@ prototype: {} // Technically, the methods should be on the prototype, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
1003765
128
10396
24