Merge 546491bbe13e262dadbefb848aabeb1d8018a69a into 5e417b44e1540f528d2ae63e3e20229a902d1db2
This commit is contained in:
commit
df918f4d40
@ -12196,7 +12196,7 @@
|
||||
"filename": "src/config/io.write-config.test.ts",
|
||||
"hashed_secret": "13951588fd3325e25ed1e3b116d7009fb221c85e",
|
||||
"is_verified": false,
|
||||
"line_number": 289
|
||||
"line_number": 290
|
||||
}
|
||||
],
|
||||
"src/config/model-alias-defaults.test.ts": [
|
||||
|
||||
@ -47,6 +47,7 @@ import { normalizeExecSafeBinProfilesInConfig } from "./normalize-exec-safe-bin.
|
||||
import { normalizeConfigPaths } from "./normalize-paths.js";
|
||||
import { resolveConfigPath, resolveDefaultConfigCandidates, resolveStateDir } from "./paths.js";
|
||||
import { isBlockedObjectKey } from "./prototype-keys.js";
|
||||
import { REDACTED_SENTINEL } from "./redact-snapshot.js";
|
||||
import { applyConfigOverrides } from "./runtime-overrides.js";
|
||||
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
|
||||
import {
|
||||
@ -1188,6 +1189,20 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
// explicitly set values. Runtime defaults are applied when loading (issue #6070).
|
||||
const stampedOutputConfig = stampConfigVersion(outputConfig);
|
||||
const json = JSON.stringify(stampedOutputConfig, null, 2).trimEnd().concat("\n");
|
||||
|
||||
if (json.includes(REDACTED_SENTINEL)) {
|
||||
const sentinel_err = new Error(
|
||||
`Refusing to write config for "${configPath}": found redaction sentinel ` +
|
||||
`"${REDACTED_SENTINEL}". This is a bug in the calling code shown in the attached stacktrace — credentials would be permanently lost. ` +
|
||||
`The config file on disk was not changed.`,
|
||||
);
|
||||
deps.logger.error(
|
||||
`Config write blocked for "${configPath}": redaction sentinel "${REDACTED_SENTINEL}" ` +
|
||||
`found in output — writing would destroy credentials. Config file was NOT modified.\n${sentinel_err.stack}`,
|
||||
);
|
||||
throw sentinel_err;
|
||||
}
|
||||
|
||||
const nextHash = hashConfigRaw(json);
|
||||
const previousHash = resolveConfigSnapshotHash(snapshot);
|
||||
const changedPathCount = changedPaths?.size;
|
||||
|
||||
@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { createConfigIO } from "./io.js";
|
||||
import { REDACTED_SENTINEL } from "./redact-snapshot.js";
|
||||
import type { OpenClawConfig } from "./types.js";
|
||||
|
||||
describe("config io write", () => {
|
||||
@ -548,4 +549,35 @@ describe("config io write", () => {
|
||||
expect(last.watchCommand).toBe("gateway --force");
|
||||
});
|
||||
});
|
||||
|
||||
it("refuses to write config containing redaction sentinel (issue #18102)", async () => {
|
||||
await withSuiteHome(async (home) => {
|
||||
const initialConfig = {
|
||||
gateway: { port: 18789 },
|
||||
channels: { telegram: { botToken: "real-bot-token-123" } },
|
||||
};
|
||||
const errorFn = vi.fn();
|
||||
const { configPath, io, snapshot } = await writeConfigAndCreateIo({
|
||||
home,
|
||||
initialConfig,
|
||||
logger: { warn: vi.fn(), error: errorFn },
|
||||
});
|
||||
|
||||
const poisoned = structuredClone(snapshot.config);
|
||||
(poisoned as Record<string, unknown>).channels = {
|
||||
...poisoned.channels,
|
||||
telegram: {
|
||||
...poisoned.channels?.telegram,
|
||||
botToken: REDACTED_SENTINEL,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(io.writeConfigFile(poisoned)).rejects.toThrow(/redaction sentinel/i);
|
||||
|
||||
const ondisk = JSON.parse(await fs.readFile(configPath, "utf-8")) as Record<string, unknown>;
|
||||
expect(JSON.stringify(ondisk)).not.toContain(REDACTED_SENTINEL);
|
||||
expect(ondisk).toEqual(initialConfig);
|
||||
expect(errorFn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user