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

Censoring code should only be possible for native code, not userland code #16

Open
getify opened this Issue Dec 4, 2018 · 3 comments

Comments

3 participants
@getify

getify commented Dec 4, 2018

I understand why a JS engine wants to be able to censor any self-hosted JS code from view. I don't love that in the first place, but I at least understand its motivations.

But I am opposed to adding a feature to userland JS that allows censorship of userland code.

  1. This proposal does not make it clear whether devtools' inspection of such censored code will be prevented. I saw one mention in another thread that it won't "necessarily" affect it, but that's not a definitive position one way or the other.

    It is imperative, before this proposal should advance, that it be clarified whether any of the censoring would be contained to runtime-only, or whether it could affect devtools usage as well, and also how those two "modes" interact, if there are two modes.

    For example, what will be the behavior of step-debugging through censored code? If the runtime censoring would have prevented inspection, but now all of a sudden that devtools is open and engaged, the censoring is NOT happening, then you're actually changing the behavior of the application. For example, a feature may break when run on the page (because it's trying to parse censored code) and then mysteriously start working once devtools is open.

    I should note that any censorship of userland code from devtools inspection is highly objectionable, not the least of which reasons is, for security purposes. I absolutely must be able to see any of the userland code that may be running on a page. Otherwise, malicious code can trivially hide itself and make detection and removal hard or impossible. This is unacceptable.

  2. Even if there's some way to safely and sanely navigate the runtime/devtools mode divide, I still object to this proposal because it unnecessarily handcuffs certain userland utilities.

    For example, I am currently exploring building a utility/library that, for a variety of reasons, unfortunately will need to inspect, and even possibly lightly modify, the code from a userland function, returning back a new function that's been "re-compiled". I won't go in all the motivations behind this, but it's in the area of creating better management of cancelation states in application code, both from a convenience perspective and from a functionality perspective. Think of this as sort of the userland runtime equivalent of some of the same kinds controls and verifications that some frameworks apply to code during build step transformations and linting.

    Another example, I have a publicly shipped FP library (FPO that, for convenience of use of one of its utilities, uses a regex to make a best-effort parse of a function's parameter list to default a set of options for its behavior. Of course, it's not perfect or 100% reliable, so the default has to be overridden for more complicated function signatures. But in the 80% case, it works great.

    In both of these cases, plus many others out there, there's a hazard to a developer who uses such tools if they are able to rely on them working based on the ability to get at the .toString() output, and then all of a sudden one of the functions they pass in starts being censored, and now it breaks.

    How can a developer be expected to keep up with whether the library they're using decides at some point to start shipping with censorship turned on? And that hazard is even significantly higher if there are other out-of-band mechanisms for the censorship, such as HTTP headers returned from a CDN, or whatever.

Censorship of native code is less than ideal, but reasonable.

Censorship of userland code is unacceptable.

@ljharb

This comment has been minimized.

ljharb commented Dec 4, 2018

Censorship of userland code is already possible in ES5 with .bind().

@getify

This comment has been minimized.

getify commented Dec 5, 2018

AFAICT, calling bind() on a function produces a new function that actually came from the JS engine, which indeed censors itself. The "native code" I see there isn't a lie; the bound function is indeed native code. It's sitting in front of a non-censored userland function that I may, or may not, be able to separately access. So it may, or may not, have the effect of preventing me from seeing the original function's code.

NOTE: that trick doesn't come without a price. bind() on a function strips the function of being able to take advantage of dynamic this binding, by forcing it to be hard-bound to some specific this context. In other words, userland code can't "just" censor itself, it has to make the deliberate choice to cut off normal dynamic this behavior in doing so. That certainly limits its suitability, at least some.

Furthermore, the fact that you can hack a censorship of a userland function does not put that on equal footing with a feature literally designed specifically to pave the path for it. This feature would actively encourage this behavior, indeed endorse it direct from the language spec.


But I'll turn this around: if it's sufficiently "hiding" of code for a function by just only exposing the bind() result of it, then this pragma isn't actually needed and the engine can easily (and probably super efficiently) just do exactly the same thing as is done with a .bind() call, and have been able to do so since ES5, as you point out.

@michaelficarra

This comment has been minimized.

michaelficarra commented Dec 5, 2018

You can censor with full fidelity using a closure instead of bind

const safeApply = hide.call.bind(hide.call, hide.apply);
function hide(fn) {
  return function() {
    return safeApply(fn, this, arguments);
  }
}

let hidden = hide(function hidden(param) {
  // you can't see me
  console.log(param);
});

hidden('test');
console.log(hidden.toString());

With a little extra effort, you can preserve the name and length as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment