question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Questions and feedback

See original GitHub issue

Hey all!

Apologies that this will be a github issue with multiple topics. @erights invited feedback and I’d love to give it and ask questions, and I wanted to move out of this thread as this is about this project and not Caja itself.

Feedback

  • I had some issues getting started, and would find a getting started guide useful. I had to try many things and ran into issues such as this in just trying to get a basic SES snippet to run. I had to fall back to importing ses/dist/ses-shim.js, but that will work browser only. Fortunately I have vm2 in the meantime for server side. I would love to be able to just import SES from 'ses' and be able to write client and server safe code with typical es/cjs modules
  • I would love to have typescript typings for this, as it makes learning a new project (and using it correctly) much easier

Questions

  • What is the full API? Is this docced anywhere?
  • What are endownments and how do I use them? I saw the term on the README and didn’t know what it refers to exactly
  • What are the options that can be passed to SES.makeSESRootRealm? I see an example of allowConsole (is this just synonymous with realm.evaluate(code, { console })?)
  • What is the difference between SES and realms/frozen realms. On surface level they seem to say they do the same thing - safe code execution, so I’m curious where exactly they differ given this project depends on one realms shim implementation
  • What is Jessie and how does it fit in here? Looks very interesting, but even with the long readme I don’t fully understand what it is and what it’s useful for compared to this, but I imagine if it is what I think I may have a use for it, but I need a good example of what it is exactly useful for would be helpful
  • How is async code handled? I’ve had use cases where I want third party code to do something along the lines of safeFetch('....json').then(data => done(data)), where I pass as context a safeFetch function (that, for example, fetches from another domain via a webworker in a sandboxes iframe) and then can call a done() function I pass to receive the result. Is this blocked in SES? Or is the async code unprotected? Or somehow it is handle? It is understandable if not, there may be ways I can work around this, but just curious
  • Do I need to be safe about what I pass in? E.g. if I wanted to pass a dom node to the evaluate, would there be nothing something stopping the given code to call domNode.ownerDocument.defaultView.eval('bad code')

Additionally, this and realms combined is quite a few kb of code. I understand that there may be no way around this, but I had a naive implementation prior to this that was quite small and went along the lines of -

function naiveSafeEvaluate(cod, context = {}) {
  const ctx = new Proxy(context, {
    get(target, key) {
      if (!Reflect.has(context, key)) {
        return undefined;
      }
      return Reflect.get(context, key);
    },
    set() {
      // No setting
      return false;
    },
  });
  const isExpression = !(code.includes('\n') || code.includes(';'));

  try {
    const result = new Function(
      'context',
      `with (context) {
        ${isExpression ? `return (${code});` : code};
      }`
    ).call(ctx, ctx);
    return result;
  } catch (err) {
    console.warn('Evaluation error:', err);
  }
}

What is the major advantage of using both realms and this project on top of realms when, in my naive understanding, my reference code below accomplishes a similar end. Obviously using with() is ugly and hacky, but I can only imagine for any real polyfill of SES/realms for today’s ES5 environments there is some arguably hacky stuff in there too

Additionally, is there any way to have protection against references? I can imagine in my code I’d always keep it safe by only passing in POJOs as context. But it would be interesting if I could still be protected with proxies, e.g. for every get request or function call, the return value (if an object) is wrapped in a proxy, that for every get, checks if the type of what is being returned is safe (e.g. plain object) and is not, say, a reference to the window object

const toString = Object.prototype.toString;
function naiveIsSafe(val) {
  return !['[object Window]', '[object Document]'].includes(toString.call(val)); // + a additional checks, perhaps users can provide hooks to make certain things flagged safe or unsafe too
}

function naiveSafeWrap(obj) {
  if (obj && (typeof obj === 'object' || typeof obj === 'function')) {
    return new Proxy(obj, {
      get(target, key) {
        const response = Reflect.get(obj, key);
        if (!naiveIsSafe(response)) {
          return undefined;
        }
        return naiveSafeWrap(response);
      },
      call() {
        // similar to get above for return types
      },
    });
  }
  return obj;
}

function naiveSafeEvaluate(code, context = {}) {
  const ctx = new Proxy(context, {
    get(target, key) {
      if (!Reflect.has(context, key)) {
        return undefined;
      }
      return naiveSafeWrap(Reflect.get(context, key));
    },
    set() {
      // No setting
      return false;
    },
  });
  const isExpression = !(code.includes('\n') || code.includes(';'));

  try {
    const result = new Function(
      'context',
      `with (context) {
        ${isExpression ? `return (${code});` : code};
      }`
    ).call(ctx, ctx);
    return result;
  } catch (err) {
    console.warn('Evaluation error:', err);
  }
}

In this case, my safeEvaluate function using SES calls eval safeEvaluate('node.ownerDocument.defaultView.eval("bad code")', { node: document.body })

but naiveSafeEvaluate does not. Same here goes for things like accessing cookies if someone accidentally passes something that could possibly reference the window or document even several property accesses away.

Anyway, just wanted to toss this out for thoughts, I can totally see the argument where it is user error to pass unsafe objects to untrusted code and that this is not part of the scope of this project as well.

I guess there is a possibility of having an extra safe mode where everything passed in gets cloned to plain objects, if anyone ever has any worry of issues of any chance of passing in an object with any deep reference to something unsafe

Also, apologies for my any naiveness in my understanding here. This is a very exciting subject to me as I want to safely allow 3rd party code on my website for the tool I make, Builder and I don’t think I’ve seen anything truly safe and viable until now. I am no security expert and I apologize if I am wasting any of your time. Additionally if this is better suited for multiple separate issues just let me know

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
warnercommented, Feb 23, 2019

Those are excellent questions, and touch on practically every aspect of SES and Caja and the object-capability world from which they come. There’s a deep body of thought here, and you’re following exactly the right path. We have a lot of lore that needs to get written down and organized to help folks along this path (it’s currently scattered among mailing list archives, source code comments, presentations, and inside people’s heads).

In particular, I think we need some docs with something like “why doesn’t X work?” paired with an attack or an inflexibility about that particular approach, to motivate/justify the complexity of SES.

One concern about that naiveSafeEvaluate is that the evaluated code can modify your primordials to change how e.g. Number or Array work, which would compromise the rest of your code (I think there’s a way to do that without flunking your isExpression check but I’m not positive). Doing this in a separate Realm is a good start, but by doing it inside SES is safer because SES freezes the primordials, so two different pieces of code inside the same frozen SES realm can interact with each other and not worry about such tampering.

The secondary concern is that it might use syntax to get access to the original function constructor ((() => 0).__proto__.constructor) and then use that to build functions that access the global scope, bypassing your with block. Your Proxy/with construct is pretty similar to what Realms does internally (see https://github.com/tc39/proposal-realms/blob/5626dcd350392dc09eeeb17669d7f0795395fe34/shim/src/evaluators.js#L59 and the Proxy defined later in that file), but Realms does extra work to protect eval and Function.

Limiting the evaluated code to an expression is kind of a drag too… you can get a lot of functionality out of that, but it’d be even nicer to be able to define multi-line named functions. Realms makes it safe to evaluate entire programs (including function bodies), which opens up some larger use cases.

I’m not sure if naiveSafeEvaluate is meant to run on Realms or not. SES runs on top of Realms (but there’s work underway on a “RealmCompartment” which would basically apply all the protection of Realms to the current environment, instead of making a brand new one, which should be faster/easier at the cost of compatibility with libraries that expect to augment Array or modify other primordials).

The Realms shim is pretty big, but the hope is that it will be turned into a native platform feature (https://github.com/tc39/proposal-realms is tracking the standardization process), so that code will “go away” eventually. SES sits on top of Realms (except maybe for the RealmCompartment project, once that is done), and is mostly concerned with freezing everything and making it easier to evaluate things safely.

For “protection against references”, you’ll want to read up about “Membranes”. The SES/Realms evaluate() is sufficient to execute untrusted code safely, but of course you actually want to interact with the result somehow, and you need to be able to do that safely. Passing a Plain Old Javascript Object is not safe, alas, because they can use it to climb your inheritance chain, get access to your Object constructor, then either modify how Object.prototype works or use it to get an unconfined Function constructor and then access the global from there.

The SES approach is to run almost all your code under SES, both the “trusted” code that you write and the “untrusted” code you get from somewhere else. And to use Harden (https://github.com/Agoric/Harden) on everything before you let it pass the border. Of course for this combined program to actually do anything, you need your trusted code to close over “real” outside/“primal”-Realm objects (which aren’t frozen and so aren’t safe to reveal to untrusted code), and part of the job of your trusted code is to safely mediate access to these powerful exterior things. Every single object that passes this boundary needs to be wrapped somehow, hence the notion of a “membrane” (think of a glovebox, like in a chemistry lab, and you can take objects out of the box but they automatically get wrapped in a new glove as they exit).

This is a hassle and takes a lot of thought, so the easier approach is to limit the API available to the untrusted code so you don’t have to manage all that (strings are safe to pass, so one hack is to JSON-serialize everything and then unserialize it on the other side, which obviously prohibits references). There’s a Caja project named “Domado” that is all about applying this “taming” process to the DOM. The code for that will be larger than Realms and SES combined (it’s a big gnarly job). We’ll be working on Domado in some new form eventually, but it’s a big job so it’s not going to happen right away.

Hope that gets you started. We should add some pointers to relevant talks here as a jumping off point for more questions, and build some of this into docs/ for future reference.

Welcome to the community!

0reactions
steve8708commented, Apr 8, 2019

Great, thanks @katelynsills!

Read more comments on GitHub >

github_iconTop Results From Across the Web

28 Customer Feedback Questions for Customer Surveys - Hotjar
Customer feedback questions that help you understand your customers · 1. How would you describe yourself in one sentence? · 2. What is...
Read more >
75 Customer Feedback Questions to Improve Your Business
1. How did you hear about us? · 2. Were your expectations met, unmet, or exceeded? · 3. Did our employees or customer...
Read more >
30 Customer Feedback Questions to Ask in 2023 (& Tools to ...
How well do your products or services meet your customers' needs, wants, and expectations? Use these questions to find out. 11) How satisfied ......
Read more >
15 Questions You Need to Ask on Your Next Customer ...
15 Questions You Need to Ask on Your Next Customer Feedback Survey · 1. Did we meet your expectations? · 2. How would...
Read more >
7 Customer Feedback Questions Examples - ReviewTrackers
Use these examples of customer feedback questions to get an accurate picture of how customers perceive your brand. View them here.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found