mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
Add prototype pollution guard to resolveConfigPath (#59694)
* config: block prototype keys in config path resolver * config: guard config defaults fallback
This commit is contained in:
@@ -59,6 +59,19 @@ describe("config-eval helpers", () => {
|
||||
expect(resolveConfigPath("not-an-object", "browser.enabled")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("blocks prototype keys while resolving config paths", () => {
|
||||
const config = {
|
||||
safe: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveConfigPath(config, "safe.enabled")).toBe(true);
|
||||
expect(resolveConfigPath(config, "__proto__")).toBeUndefined();
|
||||
expect(resolveConfigPath(config, "constructor.name")).toBeUndefined();
|
||||
expect(resolveConfigPath(config, "prototype.polluted")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses defaults only when config paths are unresolved", () => {
|
||||
const config = {
|
||||
browser: {
|
||||
@@ -75,6 +88,12 @@ describe("config-eval helpers", () => {
|
||||
expect(isConfigPathTruthyWithDefaults(config, "browser.other", {})).toBe(false);
|
||||
});
|
||||
|
||||
it("does not use inherited defaults for blocked config paths", () => {
|
||||
expect(isConfigPathTruthyWithDefaults({}, "constructor", {})).toBe(false);
|
||||
expect(isConfigPathTruthyWithDefaults({}, "__proto__.enabled", {})).toBe(false);
|
||||
expect(isConfigPathTruthyWithDefaults({}, "prototype.enabled", {})).toBe(false);
|
||||
});
|
||||
|
||||
it("returns the active runtime platform", () => {
|
||||
setPlatform("darwin");
|
||||
expect(resolveRuntimePlatform()).toBe("darwin");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Config evaluation helpers load dynamic config modules with guarded evaluation.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
|
||||
|
||||
/** Normalizes primitive config values into the truthiness rules used by requirements checks. */
|
||||
export function isTruthy(value: unknown): boolean {
|
||||
@@ -27,11 +28,21 @@ export function resolveConfigPath(config: unknown, pathStr: string): unknown {
|
||||
if (typeof current !== "object" || current === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (isBlockedObjectKey(part)) {
|
||||
return undefined;
|
||||
}
|
||||
current = (current as Record<string, unknown>)[part];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function hasBlockedConfigPathSegment(pathStr: string): boolean {
|
||||
return pathStr
|
||||
.split(".")
|
||||
.filter(Boolean)
|
||||
.some((part) => isBlockedObjectKey(part));
|
||||
}
|
||||
|
||||
/** Checks a config path with fallback defaults only when the path is unresolved. */
|
||||
export function isConfigPathTruthyWithDefaults(
|
||||
config: unknown,
|
||||
@@ -39,7 +50,11 @@ export function isConfigPathTruthyWithDefaults(
|
||||
defaults: Record<string, boolean>,
|
||||
): boolean {
|
||||
const value = resolveConfigPath(config, pathStr);
|
||||
if (value === undefined && pathStr in defaults) {
|
||||
if (
|
||||
value === undefined &&
|
||||
!hasBlockedConfigPathSegment(pathStr) &&
|
||||
Object.hasOwn(defaults, pathStr)
|
||||
) {
|
||||
return defaults[pathStr] ?? false;
|
||||
}
|
||||
return isTruthy(value);
|
||||
|
||||
Reference in New Issue
Block a user