Skip to content
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

Normative: Make PrivateName a defensible class #134

Merged
merged 1 commit into from Aug 3, 2018
Merged

Conversation

littledan
Copy link
Member

This patch makes PrivateName a deeply frozen, "defensible" class,
whereas in previous iterations, it was represented as a primitive, an
ordinary class, and finally an object with own methods and a null
prototype.

The goal of using a defensible class by default, as opposed to being
an ordinary class that users can freeze, is to protect privacy by
deafult against complex scenarios. In modern JavaScript code, a
decorator which others depend on may be implemented in a deep
dependency and unable to capture/freeze the original value of
PrivateName.prototype proerties. As a result, monkey-patching of that
object can make it difficult to preserve privacy of decorated private
class elements. A defensible-by-default PrivateName achieves the goal.
See [1] for past discussion.

The details of the PrivateName class are as follows, based on advice
[2] from Mark Miller:

  • The constructor, prototype, and all methods are frozen objects.
  • Instances are frozen.
  • The constructor and prototype have null [[Prototype]] values.
  • The constructor, when called, throws a TypeError, matching the
    decision [3] to not expose the PrivateName constructor.

If we were to support new-ing the PrivateName constructor, the semantics
would be such that instance is frozen only if new.target === PrivateName.

[1] #68
[2] #129 (comment)
[3] #68 (comment)

This patch makes PrivateName a deeply frozen, "defensible" class,
whereas in previous iterations, it was represented as a primitive, an
ordinary class, and finally an object with own methods and a null
prototype.

The goal of using a defensible class by default, as opposed to being
an ordinary class that users can freeze, is to protect privacy by
deafult against complex scenarios. In modern JavaScript code, a
decorator which others depend on may be implemented in a deep
dependency and unable to capture/freeze the original value of
PrivateName.prototype proerties. As a result, monkey-patching of that
object can make it difficult to preserve privacy of decorated private
class elements. A defensible-by-default PrivateName achieves the goal.
See [1] for past discussion.

The details of the PrivateName class are as follows, based on advice
[2] from Mark Miller:
- The constructor, prototype, and all methods are frozen objects.
- Instances are frozen.
- The constructor and prototype have null [[Prototype]] values.
- The constructor, when called, throws a TypeError, matching the
  decision [3] to not expose the PrivateName constructor.

If we were to support new-ing the PrivateName constructor, the semantics
would be such that instance is frozen only if new.target === PrivateName.

[1] #68
[2] #129 (comment)
[3] #68 (comment)
@littledan
Copy link
Member Author

We discussed this PR in the decorators call and decided to land it, together with considering a follow-on change to restrict the ability of class decorators to see read and write elements. I'm looking forward to a review from @erights, but will land this patch now and seek a review as a follow-on--we plan to meet August 7th.

@littledan littledan merged commit 4b33d55 into master Aug 3, 2018
littledan added a commit that referenced this pull request Aug 3, 2018
There's been some concern about the trust involved in decorating
private class elements. Decorating a private field or method is
specified to give access to the private name which is declared.
Although this notion of privacy may be defensible in theory, we
take a couple additional precautions to ensure that it's private
in practice:
- #134: PrivateName is a defensive class, which is deeply frozen and
  does not permit monkey patching.
- #133: Class decorators get "restricted" PrivateName instances,
  which cannot be used to read or write private class elements.

Previously, the idea was that class decorators would be trusted,
enabling class decorators to be used to deliberately grant access to
private fields and methods to things outside of the class. For
example, a @testable decorator could expose all private class elements
to a testing framework.

The new idea is, class decorators are less trusted, and can only see
that private class elements are present, insert or delete them, but
not read or write them. This increases the level of privacy in
practice (e.g., if a decorator is used for wrapping purposes and not
expected to be able to trusted reading private fields), but removes
certain use cases.

This patch enables class decorators to add and remove private elements,
which may be considered to have integrity implications with respect
to usages of private class elements as a "brand". The trust level of
class decorators is intermediate: they are trusted with these branding
rights, but not with the ability to actually read and write everything.

In a follow-on proposal, we could permit class decorators to get
un-restricted private names through a `@trusted: decorator` syntax;
we could also decide to "relax" the restriction on all class
decorators.

The logic here could be used to implement "restricted" private fields
as proposed in #24 (comment)
with the following implementation:

```js
function restricted(privateName) {
  let restrictedName;
  function classDecorator({[{key}]}) {
    restrictedName = key;
  }
  function elementDecorator(descriptor) {
    return {...descriptor, key: privateName};
  }
  @classDecorator class X {
    @elementDecorator x;
  }
  return restrictedName;
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant