Summary
createTokens resolve() walks alias path segments with segment in current, which traverses the prototype chain. When a prefix resolves to a plain-object token value, resolving a path whose final segment names an Object.prototype member (constructor, toString, hasOwnProperty, __proto__, …) returns that inherited member instead of undefined + the "Path not found" warning.
Location
packages/0/src/composables/createTokens/index.ts:234-243 — resolve() uses if (!isObject(current) || !(segment in current)).
- Contrast its twin
flatten() (:419-420, :437-438), which already guards with Object.prototype.hasOwnProperty.call(...) + UNSAFE_KEYS. UNSAFE_KEYS is imported (:36) but used only in flatten.
- Reachable when a whole object is registered as a token value (flat mode,
:455-457) or a { $value: {...} } alias is unwrapped (:232 / :242).
Impact
This is a read of an inherited member, not a write — not prototype pollution, no info disclosure beyond global builtins, no attacker surface (token config and the resolve() argument are developer-authored; createTokens backs useTheme / useLocale / useFeatures). The real-world bite is a correctness inconsistency: resolve() can hand back a builtin function instead of undefined. Low severity — consistency / defense-in-depth.
Suggested fix
Mirror flatten's guard in the resolve loop (:235):
if (!isObject(current) || UNSAFE_KEYS.has(segment) || !Object.prototype.hasOwnProperty.call(current, segment))
Add tests asserting resolve('{obj.constructor}') / resolve('{obj.__proto__}') return undefined. Since flatten can never register an unsafe/inherited key, mirroring the guard carries zero false-negative risk. Describe it in the changelog as "resolve() no longer returns inherited prototype members," not as a security fix.
Summary
createTokensresolve()walks alias path segments withsegment in current, which traverses the prototype chain. When a prefix resolves to a plain-object token value, resolving a path whose final segment names anObject.prototypemember (constructor,toString,hasOwnProperty,__proto__, …) returns that inherited member instead ofundefined+ the "Path not found" warning.Location
packages/0/src/composables/createTokens/index.ts:234-243—resolve()usesif (!isObject(current) || !(segment in current)).flatten()(:419-420,:437-438), which already guards withObject.prototype.hasOwnProperty.call(...)+UNSAFE_KEYS.UNSAFE_KEYSis imported (:36) but used only inflatten.:455-457) or a{ $value: {...} }alias is unwrapped (:232/:242).Impact
This is a read of an inherited member, not a write — not prototype pollution, no info disclosure beyond global builtins, no attacker surface (token config and the
resolve()argument are developer-authored;createTokensbacksuseTheme/useLocale/useFeatures). The real-world bite is a correctness inconsistency:resolve()can hand back a builtin function instead ofundefined. Low severity — consistency / defense-in-depth.Suggested fix
Mirror
flatten's guard in theresolveloop (:235):Add tests asserting
resolve('{obj.constructor}')/resolve('{obj.__proto__}')returnundefined. Sinceflattencan never register an unsafe/inherited key, mirroring the guard carries zero false-negative risk. Describe it in the changelog as "resolve() no longer returns inherited prototype members," not as a security fix.