-
Notifications
You must be signed in to change notification settings - Fork 112
Closed
Labels
Description
The current semantics for the PrivateName
Object can result in decorators that leak shared custom PrivateName
values, breaking "hard privacy". For example:
// a.js
const sharedPrivateName = new PrivateName("shared");
export function dec(desc) {
desc.elements.push({
kind: "field",
placement: "own",
key: sharedPrivateName
});
desc.elements.push({
kind: "method",
key: "setPrivateData",
placement: "prototype",
descriptor: {
value: function(data) {
sharedPrivateName.set(this, data);
}
}
});
return desc;
}
// b.js
import { dec } from "./a.js";
// apply the decorator, adds `sharedPrivateName` to `C`
@dec
class C {
}
// leak the private name
const desc = { kind: "class", elements: [] };
dec(desc);
const leakedPrivateName = desc.elements[0].key;
// demonstrate leak
const obj = new C();
obj.setPrivateData("private data"); // method attached by decorator
const privateData = leakedPrivateName.get(obj);
privateData; // "private data";
One possible approach to avoid this is to break PrivateName
into two parts: a property "mutator" and an opaque "key". For example:
interface PrivateName {
key;
get(obj);
set(obj, value);
}
Then the decorator above could be rewritten:
// a.js
const sharedPrivateName = new PrivateName("shared");
export function dec(desc) {
desc.elements.push({
kind: "field",
placement: "own",
key: sharedPrivateName.key // only send opaque key
});
desc.elements.push({
kind: "method",
key: "setPrivateData",
placement: "prototype",
descriptor: {
value: function(data) {
sharedPrivateName.set(this, data); // uses mutator
}
}
});
return desc;
}
While this still results in a key that could be added to any object, it prevents an untrusted third party from reading or writing to that state.