The problem
packages/core/src/context/index.ts:651 assigns into a plain object with a caller-supplied key:
context.plugins[plugin.name] = …
A plugin named __proto__ therefore sets the object's prototype rather than adding a member. A plugin named constructor or toString collides with an inherited name, so a later lookup of a different plugin's services can find an inherited function instead of failing cleanly.
This is the same hazard that has now been fixed twice elsewhere in this package: once in a provider-name lookup, and once in a plugin-owned field write where a prototype key walked past two guards and reached the database as a column name.
Why it is worth closing rather than shrugging at
Plugin names are author-supplied, so this is not attacker-controlled in the usual sense, and nobody is likely to name a plugin __proto__ on purpose. But the failure is silent and its symptom appears far away — a plugin's services resolving to an inherited function rather than being absent, which reads as a corrupt registry rather than a naming mistake.
The remedy is one word, and the same one both prior fixes used.
What to do
Build the registry with a null-prototype object, or gate the write and every read with Object.hasOwn, and refuse a reserved name by name at registration so the author is told rather than left to debug it.
While there: sweep for other assignments into a plain object under a config-supplied key. Two instances of this shape have already been found in this package by separate reviews, which suggests looking rather than assuming these were the last.
Provenance
Pre-existing. Found while fixing the same class in the plugin-owned field write on #1332, and deliberately left out of that pull request's scope.
Context
_Generated by Claude Code
The problem
packages/core/src/context/index.ts:651assigns into a plain object with a caller-supplied key:A plugin named
__proto__therefore sets the object's prototype rather than adding a member. A plugin namedconstructorortoStringcollides with an inherited name, so a later lookup of a different plugin's services can find an inherited function instead of failing cleanly.This is the same hazard that has now been fixed twice elsewhere in this package: once in a provider-name lookup, and once in a plugin-owned field write where a prototype key walked past two guards and reached the database as a column name.
Why it is worth closing rather than shrugging at
Plugin names are author-supplied, so this is not attacker-controlled in the usual sense, and nobody is likely to name a plugin
__proto__on purpose. But the failure is silent and its symptom appears far away — a plugin's services resolving to an inherited function rather than being absent, which reads as a corrupt registry rather than a naming mistake.The remedy is one word, and the same one both prior fixes used.
What to do
Build the registry with a null-prototype object, or gate the write and every read with
Object.hasOwn, and refuse a reserved name by name at registration so the author is told rather than left to debug it.While there: sweep for other assignments into a plain object under a config-supplied key. Two instances of this shape have already been found in this package by separate reviews, which suggests looking rather than assuming these were the last.
Provenance
Pre-existing. Found while fixing the same class in the plugin-owned field write on #1332, and deliberately left out of that pull request's scope.
Context
_Generated by Claude Code