The compiler marshals an object value before it passes that value to a
decorator, and it adds each member by assignment. An assignment to __proto__
sets the prototype instead of adding a member, so that member never reaches the
decorator and no diagnostic reports it.
The reproduction emits this, with no warning and no error:
x-c-control: # the control, an ordinary member name
normal:
polluted: true
ok: 1
x-b-string: # `__proto__` holding a string
ok: 1
x-a-object: # `__proto__` holding an object
ok: 1
The control keeps both members. Only the two cases that name a member
__proto__ lose one, so the name decides it — not the nesting, and not the
emitter.
The document cannot show the second effect. When the lost member holds an
object or an array, that value becomes the prototype of the marshalled object.
Measured in a decorator of my own, and still present in 1.15.0:
own=["ok"] prototypeOwnNames=["polluted"] value.polluted === true
polluted is never a member of that object. It is a name the .tsp author
chose, so a decorator that reads any name it did not declare can read the
author's data.
Every decorator taking an object value is affected.
The assignment is objectValueToJs, still the same on main
today:
function objectValueToJs(type: ObjectValue): Record<string, unknown> {
const result: Record<string, unknown> = {};
for (const [key, value] of type.properties) {
result[key] = marshalTypeForJs(value.value, undefined);
}
return result;
}
The other direction in the same file already handles the name.
unmarshalJsToValue collects members into a Map, which
takes __proto__ as an ordinary key. So the two directions disagree about it.
Suggested fix: build the object with Object.create(null), or add each
member with Object.defineProperty.
I keep the case as a failing test
here,
so it turns green if this changes.
Playground Link
The compiler marshals an object value before it passes that value to a
decorator, and it adds each member by assignment. An assignment to
__proto__sets the prototype instead of adding a member, so that member never reaches the
decorator and no diagnostic reports it.
The reproduction emits this, with no warning and no error:
The control keeps both members. Only the two cases that name a member
__proto__lose one, so the name decides it — not the nesting, and not theemitter.
The document cannot show the second effect. When the lost member holds an
object or an array, that value becomes the prototype of the marshalled object.
Measured in a decorator of my own, and still present in 1.15.0:
pollutedis never a member of that object. It is a name the.tspauthorchose, so a decorator that reads any name it did not declare can read the
author's data.
Every decorator taking an object value is affected.
The assignment is
objectValueToJs, still the same on maintoday:
The other direction in the same file already handles the name.
unmarshalJsToValuecollects members into aMap, whichtakes
__proto__as an ordinary key. So the two directions disagree about it.Suggested fix: build the object with
Object.create(null), or add eachmember with
Object.defineProperty.I keep the case as a failing test
here,
so it turns green if this changes.
Playground Link