mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 19:01:47 +03:00
fix(config): log config overwrite audits
This commit is contained in:
@@ -56,6 +56,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Signal/Install: auto-install `signal-cli` via Homebrew on non-x64 Linux architectures, avoiding x86_64 native binary `Exec format error` failures on arm64/arm hosts. (#15443) Thanks @jogvan-k.
|
- Signal/Install: auto-install `signal-cli` via Homebrew on non-x64 Linux architectures, avoiding x86_64 native binary `Exec format error` failures on arm64/arm hosts. (#15443) Thanks @jogvan-k.
|
||||||
- Discord: avoid misrouting numeric guild allowlist entries to `/channels/<guildId>` by prefixing guild-only inputs with `guild:` during resolution. (#12326) Thanks @headswim.
|
- Discord: avoid misrouting numeric guild allowlist entries to `/channels/<guildId>` by prefixing guild-only inputs with `guild:` during resolution. (#12326) Thanks @headswim.
|
||||||
- Config: preserve `${VAR}` env references when writing config files so `openclaw config set/apply/patch` does not persist secrets to disk. Thanks @thewilloftheshadow.
|
- Config: preserve `${VAR}` env references when writing config files so `openclaw config set/apply/patch` does not persist secrets to disk. Thanks @thewilloftheshadow.
|
||||||
|
- Config: log overwrite audit entries (path, backup target, and hash transition) whenever an existing config file is replaced, improving traceability for unexpected config clobbers.
|
||||||
- Process/Exec: avoid shell execution for `.exe` commands on Windows so env overrides work reliably in `runCommandWithTimeout`. Thanks @thewilloftheshadow.
|
- Process/Exec: avoid shell execution for `.exe` commands on Windows so env overrides work reliably in `runCommandWithTimeout`. Thanks @thewilloftheshadow.
|
||||||
- Web tools/web_fetch: prefer `text/markdown` responses for Cloudflare Markdown for Agents, add `cf-markdown` extraction for markdown bodies, and redact fetched URLs in `x-markdown-tokens` debug logs to avoid leaking raw paths/query params. (#15376) Thanks @Yaxuan42.
|
- Web tools/web_fetch: prefer `text/markdown` responses for Cloudflare Markdown for Agents, add `cf-markdown` extraction for markdown bodies, and redact fetched URLs in `x-markdown-tokens` debug logs to avoid leaking raw paths/query params. (#15376) Thanks @Yaxuan42.
|
||||||
- Config: keep legacy audio transcription migration strict by rejecting non-string/unsafe command tokens while still migrating valid custom script executables. (#5042) Thanks @shayan919293.
|
- Config: keep legacy audio transcription migration strict by rejecting non-string/unsafe command tokens while still migrating valid custom script executables. (#5042) Thanks @shayan919293.
|
||||||
|
|||||||
@@ -725,6 +725,19 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
// Do NOT apply runtime defaults when writing — user config should only contain
|
// Do NOT apply runtime defaults when writing — user config should only contain
|
||||||
// explicitly set values. Runtime defaults are applied when loading (issue #6070).
|
// explicitly set values. Runtime defaults are applied when loading (issue #6070).
|
||||||
const json = JSON.stringify(stampConfigVersion(outputConfig), null, 2).trimEnd().concat("\n");
|
const json = JSON.stringify(stampConfigVersion(outputConfig), null, 2).trimEnd().concat("\n");
|
||||||
|
const nextHash = hashConfigRaw(json);
|
||||||
|
const previousHash = resolveConfigSnapshotHash(snapshot);
|
||||||
|
const changedPathCount = changedPaths?.size;
|
||||||
|
const logConfigOverwrite = () => {
|
||||||
|
if (!snapshot.exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const changeSummary =
|
||||||
|
typeof changedPathCount === "number" ? `, changedPaths=${changedPathCount}` : "";
|
||||||
|
deps.logger.warn(
|
||||||
|
`Config overwrite: ${configPath} (sha256 ${previousHash ?? "unknown"} -> ${nextHash}, backup=${configPath}.bak${changeSummary})`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const tmp = path.join(
|
const tmp = path.join(
|
||||||
dir,
|
dir,
|
||||||
@@ -756,6 +769,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
await deps.fs.promises.unlink(tmp).catch(() => {
|
await deps.fs.promises.unlink(tmp).catch(() => {
|
||||||
// best-effort
|
// best-effort
|
||||||
});
|
});
|
||||||
|
logConfigOverwrite();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await deps.fs.promises.unlink(tmp).catch(() => {
|
await deps.fs.promises.unlink(tmp).catch(() => {
|
||||||
@@ -763,6 +777,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
});
|
});
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
logConfigOverwrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { createConfigIO } from "./io.js";
|
import { createConfigIO } from "./io.js";
|
||||||
import { withTempHome } from "./test-helpers.js";
|
import { withTempHome } from "./test-helpers.js";
|
||||||
|
|
||||||
@@ -174,4 +174,66 @@ describe("config io write", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("logs an overwrite audit entry when replacing an existing config file", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const configPath = path.join(home, ".openclaw", "openclaw.json");
|
||||||
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
configPath,
|
||||||
|
JSON.stringify({ gateway: { port: 18789 } }, null, 2),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
const warn = vi.fn();
|
||||||
|
const io = createConfigIO({
|
||||||
|
env: {} as NodeJS.ProcessEnv,
|
||||||
|
homedir: () => home,
|
||||||
|
logger: {
|
||||||
|
warn,
|
||||||
|
error: vi.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = await io.readConfigFileSnapshot();
|
||||||
|
expect(snapshot.valid).toBe(true);
|
||||||
|
const next = structuredClone(snapshot.config);
|
||||||
|
next.gateway = {
|
||||||
|
...next.gateway,
|
||||||
|
auth: { mode: "token" },
|
||||||
|
};
|
||||||
|
|
||||||
|
await io.writeConfigFile(next);
|
||||||
|
|
||||||
|
const overwriteLog = warn.mock.calls
|
||||||
|
.map((call) => call[0])
|
||||||
|
.find((entry) => typeof entry === "string" && entry.startsWith("Config overwrite:"));
|
||||||
|
expect(typeof overwriteLog).toBe("string");
|
||||||
|
expect(overwriteLog).toContain(configPath);
|
||||||
|
expect(overwriteLog).toContain(`${configPath}.bak`);
|
||||||
|
expect(overwriteLog).toContain("sha256");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not log an overwrite audit entry when creating config for the first time", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const warn = vi.fn();
|
||||||
|
const io = createConfigIO({
|
||||||
|
env: {} as NodeJS.ProcessEnv,
|
||||||
|
homedir: () => home,
|
||||||
|
logger: {
|
||||||
|
warn,
|
||||||
|
error: vi.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await io.writeConfigFile({
|
||||||
|
gateway: { mode: "local" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const overwriteLogs = warn.mock.calls.filter(
|
||||||
|
(call) => typeof call[0] === "string" && call[0].startsWith("Config overwrite:"),
|
||||||
|
);
|
||||||
|
expect(overwriteLogs).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user