Skip to content

Commit d55398f

Browse files
jaredwrayclaude
andauthored
node-cache - fix: prototype pollution vulnerability in mget methods (#1613)
* fix: prevent prototype pollution in node-cache mget() Use Object.create(null) for mget() result objects in both NodeCache and NodeCacheStore so that __proto__ keys are treated as plain properties instead of polluting Object.prototype. Fixes #1612 https://claude.ai/code/session_01MhMFGu517ERhcLM4M5DsM8 * fix: strengthen prototype pollution tests with own-property assertions Replace result.__proto__ value check with Object.getPrototypeOf(result) === null and Object.hasOwn(result, "__proto__") assertions that actually verify the Object.create(null) fix rather than passing vacuously. https://claude.ai/code/session_01MhMFGu517ERhcLM4M5DsM8 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 487d6ff commit d55398f

File tree

4 files changed

+26
-2
lines changed

4 files changed

+26
-2
lines changed

packages/node-cache/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,10 @@ export class NodeCache<T> extends Hookified {
267267
public mget<V = T>(
268268
keys: Array<string | number>,
269269
): Record<string, V | undefined> {
270-
const result: Record<string, V | undefined> = {};
270+
const result: Record<string, V | undefined> = Object.create(null) as Record<
271+
string,
272+
V | undefined
273+
>;
271274

272275
for (const key of keys) {
273276
const value = this.get(key);

packages/node-cache/src/store.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ export class NodeCacheStore<T> extends Hookified {
118118
public async mget<V = T>(
119119
keys: Array<string | number>,
120120
): Promise<Record<string, V | undefined>> {
121-
const result: Record<string, V | undefined> = {};
121+
const result: Record<string, V | undefined> = Object.create(null) as Record<
122+
string,
123+
V | undefined
124+
>;
122125
for (const key of keys) {
123126
const value = await this._keyv.get<V>(key.toString());
124127
if (value === undefined) {

packages/node-cache/test/index.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ describe("NodeCache", () => {
9393
expect(list[key2]).toBe(value2);
9494
});
9595

96+
test("should not pollute Object.prototype via mget with __proto__ key", () => {
97+
cache.set("__proto__", { polluted: true });
98+
const result = cache.mget(["__proto__"]);
99+
// biome-ignore lint/suspicious/noExplicitAny: testing prototype pollution
100+
expect((Object.prototype as any).polluted).toBeUndefined();
101+
expect(Object.getPrototypeOf(result)).toBeNull();
102+
expect(Object.hasOwn(result, "__proto__")).toBe(true);
103+
});
104+
96105
test("should take a key", () => {
97106
const key = faker.string.uuid();
98107
const val = faker.lorem.word();

packages/node-cache/test/store.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ describe("NodeCacheStore", () => {
7272
const result1 = await store.mget(["test1", "test2"]);
7373
expect(result1).toEqual({ test1: "value1", test2: "value2" });
7474
});
75+
test("should not pollute Object.prototype via mget with __proto__ key", async () => {
76+
const store = new NodeCacheStore();
77+
await store.set("__proto__", { polluted: true });
78+
const result = await store.mget(["__proto__"]);
79+
// biome-ignore lint/suspicious/noExplicitAny: testing prototype pollution
80+
expect((Object.prototype as any).polluted).toBeUndefined();
81+
expect(Object.getPrototypeOf(result)).toBeNull();
82+
expect(Object.hasOwn(result, "__proto__")).toBe(true);
83+
});
7584
test("should be able to set multiple keys", async () => {
7685
const store = new NodeCacheStore();
7786
const data = [

0 commit comments

Comments
 (0)