Skip to content

Idea: Compartment Sandbox without with-statement or proxy #1561

@kumavis

Description

@kumavis

This is a description for an alternate Compartment sandboxing approach relying on static analysis and code generation.
The generated code wraps the untrusted code and no transformation of the untrusted code is required.
This construction does not require a with-statement or a Proxy and is strict-mode compatible. Feral eval is optional and can be replaced with templating.

This approach may be useful when working in situations:

  • that require strict mode
  • where Proxy or with-statement are not available
  • where the performance impact of Proxy or with-statement is prohibitive

SES shim:

SES Sandboxing is composed from with-statement + proxy + eval

however:

  • you can replace eval with templating
  • you can replace with-statement + proxy with static analysis and code generation

SES Sandboxing shim uses the with-statement for both of its primary sandboxing tasks:

  1. scope denial of variables / references in above scope (uses a proxy)
  2. scope population of compartment globalThis properties

This approach:

The scope denial uses a Proxy in order to handle any possible uttered value. However if you have access to the code you can perform static analysis on it and as long as direct eval is not available, all potential utterances can be trivially known. Given a list of utterances you can prepare code to prevent them from accessing values in higher scopes.

// deny all unallowed utterances
const fetch = undefined;

// untrusted code here
{
  // not able to access global fetch
  fetch();
}

The scope population of Compartment properties can be accomplished by creating variables in scope. The synchronization of values between the Compartment globalThis properties and the variables can be accomplished by using setters and getters on the globalThis. Only utterable properties need to be synchronized, un-utterable properties can be defined directly on the Compartment globalThis. While the untrused code would be able to modify the property descriptors on the Compartment globalThis, this would only reduce shim fidelity and not reduce security.

// expose utterable globals as variables
let xyz = sandboxKit.endowments.xyz;
const globalThis = {
  // expose allowed un-uttered (and un-utterable) endowments
  ...sandboxKit.endowments,
  // synchronized uttered globals with variables
  get xyz() {
    return xyz;
  },
  set xyz(value) {
    xyz = value;
  },
}

{
  // deny access to internal property
  const sandboxKit = undefined;
  // untrusted code here
  {
    // variables are synchronized with their globalThis properties
    globalThis.xyz = 123;
    console.log(xyz) // 123
    // un-uttered properties are accessible
    console.log(globalThis['abc']) // as endowed
    // un-utterable properties are also accessible
    console.log(globalThis['api-key']) // as endowed
  }
}

Endowments do not need to be known at the time of code generation. We don't need to distinguish between allowed and disallowed utterances.

// expose utterable globals as variables
let fetch = sandboxKit.endowments.fetch;
let xyz = sandboxKit.endowments.xyz;
const globalThis = {
  // expose allowed un-uttered (and un-utterable) endowments
  ...sandboxKit.endowments,
  // utterable globals
  get xyz() {
    return xyz;
  },
  set xyz(value) {
    xyz = value;
  },
  // if fetch is undefined in endowments it will be present on
  // the Compartment globalThis, but be undefined
  get fetch() {
    return fetch;
  },
  set fetch(value) {
    fetch = value;
  },
}

Multiple evaluations in the same Compartment can be accomplished with some restrictions. In order to have a single Compartment globalThis object and have it correctly synchronize its properties with global variables, the evaluations must be evaluated within the same scope below the Compartment scope machinery.

// expose utterable globals as variables
let xyz = sandboxKit.endowments.xyz;
const globalThis = {
  // expose allowed un-uttered (and un-utterable) endowments
  ...sandboxKit.endowments,
  // utterable globals
  get xyz() {
    return xyz;
  },
  set xyz(value) {
    xyz = value;
  },
}

{
  // deny internal sandboxKit
  const sandboxKit = undefined;

  // evaluation 1 (untrusted code)
  {
    globalThis.xyz = 123;
    console.log(xyz) // 123
  }

  // evaluation 2 (untrusted code)
  {
    console.log(globalThis['api-key']) // as endowed
  }
}

Dynamic evaluations (requested at runtime, eg indirect eval) in the same compartment may be possible with some restrictions. Each evaluation may be unable to have utterances in common. ⚠️ Further research required.

// expose utterable globals as variables
let xyz = sandboxKit.endowments.xyz;
const globalThis = {
  // expose allowed un-uttered (and un-utterable) endowments
  ...sandboxKit.endowments,
  // utterable globals
  get xyz() {
    return xyz;
  },
  set xyz(value) {
    xyz = value;
  },
}

// dynamic eval space
{
  sandboxKit.exports.safeEval = function (code) {
    const utterances = getUtterances(code)
    const safeCode = wrapCodeUtterancesAsVars(code, utterances)
    defineUtterancesOnGlobalThis(utterances, globalThis)
    feralEvalOnce(safeCode)
    // may be able to get a new further nested safeEval here that evaluates
    // in a scope with the new utterances in scope
  }
}

Limitations:

  • Relies on correct static analysis of utterances
  • Relies on correct code generation of wrapper
  • Relies on SES lockdown, though not discussed here
  • Untrusted code can reduce shim fidelity and detect shim environment
  • Relies on rejecting dynamic import expressions, as does the SES shim
  • Fidelity issue: Only supports values (not getters/setters) for global variables. Eg. can not support the browser's location = '(target url)'

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions