mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(usage-bar): use Object.hasOwn instead of in operator to avoid prototype chain pollution (#98503)
The in operator traverses the prototype chain, causing keys like toString, constructor, valueOf, and __proto__ to incorrectly match Object.prototype inherited properties in alias table lookups and map segment case lookups. Replace with Object.hasOwn (ES2022) which only checks own properties. Fixes #98466 Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,17 @@ describe("usage-bar verbs", () => {
|
||||
expect(render([{ text: "{m|alias:models}" }], { m: "some-new-model" })).toBe("some-new-model");
|
||||
});
|
||||
|
||||
it("alias — prototype keys (toString, constructor) do not match inherited properties", () => {
|
||||
// When a model is named "toString" or "constructor", the `in` operator
|
||||
// would match Object.prototype inherited properties and return
|
||||
// Object.prototype.toString (a function) instead of the raw key.
|
||||
// After the fix (Object.hasOwn), these should echo through unchanged.
|
||||
expect(render([{ text: "{m|alias:models}" }], { m: "toString" })).toBe("toString");
|
||||
expect(render([{ text: "{m|alias:models}" }], { m: "constructor" })).toBe("constructor");
|
||||
expect(render([{ text: "{m|alias:models}" }], { m: "valueOf" })).toBe("valueOf");
|
||||
expect(render([{ text: "{m|alias:models}" }], { m: "__proto__" })).toBe("__proto__");
|
||||
});
|
||||
|
||||
it("fallback when path is missing/empty", () => {
|
||||
expect(render([{ text: "{identity.emoji|🤖} hi" }], {})).toBe("🤖 hi");
|
||||
expect(render([{ text: "{identity.emoji|🤖} hi" }], { identity: { emoji: "🩺" } })).toBe(
|
||||
@@ -87,6 +98,18 @@ describe("usage-bar segment forms", () => {
|
||||
expect(render(seg, { state: {} })).toBe("");
|
||||
});
|
||||
|
||||
it("map — prototype keys (toString, constructor) do not match inherited properties", () => {
|
||||
// When the map key is "toString" or "constructor", the `in` operator
|
||||
// would incorrectly match Object.prototype inherited properties and
|
||||
// return undefined (Object.prototype.toString is a function, not a
|
||||
// string case value) instead of falling through to _default.
|
||||
const seg = [
|
||||
{ map: "state.mode", cases: { toString: "should-not-match", _default: "fallback" } },
|
||||
];
|
||||
expect(render(seg, { state: { mode: "toString" } })).toBe("should-not-match");
|
||||
expect(render(seg, { state: { mode: "constructor" } })).toBe("fallback");
|
||||
});
|
||||
|
||||
it("each with item_scales picks a scale per window by position", () => {
|
||||
const seg = [
|
||||
{
|
||||
|
||||
@@ -130,11 +130,11 @@ function applyVerb(name: string, args: string[], value: unknown, vocab: Vocab):
|
||||
const table =
|
||||
args[0] && isObject(aliases[args[0]]) ? (aliases[args[0]] as Record<string, unknown>) : {};
|
||||
const key = String(value);
|
||||
if (key in table) {
|
||||
if (Object.hasOwn(table, key)) {
|
||||
return table[key];
|
||||
}
|
||||
const lower = key.toLowerCase();
|
||||
return lower in table ? table[lower] : value;
|
||||
return Object.hasOwn(table, lower) ? table[lower] : value;
|
||||
}
|
||||
case "meter": {
|
||||
const width = args[0] ? Number.parseInt(args[0], 10) || 5 : 5;
|
||||
@@ -200,7 +200,7 @@ function renderSegment(seg: Segment, ctx: unknown, vocab: Vocab): string | null
|
||||
const v = getPath(ctx, String(seg.map));
|
||||
const key = typeof v === "boolean" ? String(v) : String(v);
|
||||
const cases = isObject(seg.cases) ? seg.cases : {};
|
||||
const hit = key in cases ? cases[key] : cases["_default"];
|
||||
const hit = Object.hasOwn(cases, key) ? cases[key] : cases["_default"];
|
||||
return typeof hit === "string" ? hit : null;
|
||||
}
|
||||
if ("each" in seg) {
|
||||
|
||||
Reference in New Issue
Block a user