mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:40:43 +00:00
harden: drop prototype-pollution keys in configPatch merge
Skip `__proto__`, `prototype`, and `constructor` keys while recursively merging provider-auth `configPatch` payloads. Plugins construct the patch in-process today, but JSON-parsed sources can preserve these keys and the assignment `next[key] = value` would otherwise mutate the merge target's prototype chain. Made-with: Cursor
This commit is contained in:
committed by
Peter Steinberger
parent
14d1c9c4f0
commit
a849283f80
@@ -42,6 +42,14 @@ describe("applyProviderAuthConfigPatch", () => {
|
||||
expect(next.agents?.defaults?.model).toEqual(base.agents.defaults.model);
|
||||
});
|
||||
|
||||
it("drops prototype-pollution keys from the merge", () => {
|
||||
const patch = JSON.parse('{"__proto__":{"polluted":true},"agents":{"defaults":{}}}');
|
||||
const next = applyProviderAuthConfigPatch(base, patch);
|
||||
expect(next.agents?.defaults?.models).toEqual(base.agents.defaults.models);
|
||||
expect(({} as Record<string, unknown>).polluted).toBeUndefined();
|
||||
expect(Object.getPrototypeOf(next).polluted).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps normal recursive merges for unrelated provider auth patch fields", () => {
|
||||
const base = {
|
||||
agents: {
|
||||
|
||||
@@ -46,6 +46,10 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
// Guard the recursive merge against prototype-pollution payloads if a
|
||||
// patch ever arrives from a JSON-parsed source that preserves these keys.
|
||||
const BLOCKED_MERGE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
||||
|
||||
export function mergeConfigPatch<T>(base: T, patch: unknown): T {
|
||||
if (!isPlainRecord(base) || !isPlainRecord(patch)) {
|
||||
return patch as T;
|
||||
@@ -53,6 +57,9 @@ export function mergeConfigPatch<T>(base: T, patch: unknown): T {
|
||||
|
||||
const next: Record<string, unknown> = { ...base };
|
||||
for (const [key, value] of Object.entries(patch)) {
|
||||
if (BLOCKED_MERGE_KEYS.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const existing = next[key];
|
||||
if (isPlainRecord(existing) && isPlainRecord(value)) {
|
||||
next[key] = mergeConfigPatch(existing, value);
|
||||
|
||||
Reference in New Issue
Block a user