mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-02 22:57:15 +00:00
fix(config): harden unsetPaths traversal guards
This commit is contained in:
@@ -39,6 +39,7 @@ import { applyMergePatch } from "./merge-patch.js";
|
||||
import { normalizeExecSafeBinProfilesInConfig } from "./normalize-exec-safe-bin.js";
|
||||
import { normalizeConfigPaths } from "./normalize-paths.js";
|
||||
import { resolveConfigPath, resolveDefaultConfigCandidates, resolveStateDir } from "./paths.js";
|
||||
import { isBlockedObjectKey } from "./prototype-keys.js";
|
||||
import { applyConfigOverrides } from "./runtime-overrides.js";
|
||||
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
|
||||
import {
|
||||
@@ -143,6 +144,10 @@ function isWritePlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function hasOwnObjectKey(value: Record<string, unknown>, key: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(value, key);
|
||||
}
|
||||
|
||||
const WRITE_PRUNED_OBJECT = Symbol("write-pruned-object");
|
||||
|
||||
type UnsetPathWriteResult = {
|
||||
@@ -187,7 +192,11 @@ function unsetPathForWriteAt(
|
||||
return { changed: true, value: next };
|
||||
}
|
||||
|
||||
if (!isWritePlainObject(value) || !(segment in value)) {
|
||||
if (
|
||||
isBlockedObjectKey(segment) ||
|
||||
!isWritePlainObject(value) ||
|
||||
!hasOwnObjectKey(value, segment)
|
||||
) {
|
||||
return { changed: false, value };
|
||||
}
|
||||
if (isLeaf) {
|
||||
@@ -216,9 +225,9 @@ function unsetPathForWriteAt(
|
||||
}
|
||||
|
||||
function unsetPathForWrite(
|
||||
root: Record<string, unknown>,
|
||||
root: OpenClawConfig,
|
||||
pathSegments: string[],
|
||||
): { changed: boolean; next: Record<string, unknown> } {
|
||||
): { changed: boolean; next: OpenClawConfig } {
|
||||
if (pathSegments.length === 0) {
|
||||
return { changed: false, next: root };
|
||||
}
|
||||
@@ -230,7 +239,7 @@ function unsetPathForWrite(
|
||||
return { changed: true, next: {} };
|
||||
}
|
||||
if (isWritePlainObject(result.value)) {
|
||||
return { changed: true, next: result.value };
|
||||
return { changed: true, next: coerceConfig(result.value) };
|
||||
}
|
||||
return { changed: false, next: root };
|
||||
}
|
||||
@@ -1041,9 +1050,9 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
if (!Array.isArray(unsetPath) || unsetPath.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const unsetResult = unsetPathForWrite(outputConfig as Record<string, unknown>, unsetPath);
|
||||
const unsetResult = unsetPathForWrite(outputConfig, unsetPath);
|
||||
if (unsetResult.changed) {
|
||||
outputConfig = unsetResult.next as OpenClawConfig;
|
||||
outputConfig = unsetResult.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,36 @@ describe("config io write", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores blocked prototype-key unset path segments", async () => {
|
||||
await withTempHome("openclaw-config-io-", async (home) => {
|
||||
const { configPath, io } = await writeConfigAndCreateIo({
|
||||
home,
|
||||
initialConfig: {
|
||||
gateway: { mode: "local" },
|
||||
commands: { ownerDisplay: "hash" },
|
||||
},
|
||||
});
|
||||
|
||||
const input: Record<string, unknown> = {
|
||||
gateway: { mode: "local" },
|
||||
commands: { ownerDisplay: "hash" },
|
||||
};
|
||||
await io.writeConfigFile(input, {
|
||||
unsetPaths: [
|
||||
["commands", "__proto__"],
|
||||
["commands", "constructor"],
|
||||
["commands", "prototype"],
|
||||
],
|
||||
});
|
||||
|
||||
expect((input.commands as Record<string, unknown>).ownerDisplay).toBe("hash");
|
||||
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as {
|
||||
commands?: Record<string, unknown>;
|
||||
};
|
||||
expect(persisted.commands?.ownerDisplay).toBe("hash");
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves env var references when writing", async () => {
|
||||
await withTempHome("openclaw-config-io-", async (home) => {
|
||||
const { configPath, io, snapshot } = await writeConfigAndCreateIo({
|
||||
|
||||
Reference in New Issue
Block a user