Config: fail closed invalid config loads (#39071)
* Config: fail closed invalid config loads * CLI: keep diagnostics on explicit best-effort config * Tests: cover invalid config best-effort diagnostics * Changelog: note invalid config fail-closed fix * Status: pass best-effort config through status-all gateway RPCs * CLI: pass config through gateway secret RPC * CLI: skip plugin loading from invalid config * Tests: align daemon token drift env precedence
This commit is contained in:
@@ -18,10 +18,17 @@ const { nodesAction, registerNodesCli } = vi.hoisted(() => {
|
||||
return { nodesAction: action, registerNodesCli: register };
|
||||
});
|
||||
|
||||
const configModule = vi.hoisted(() => ({
|
||||
loadConfig: vi.fn(),
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../acp-cli.js", () => ({ registerAcpCli }));
|
||||
vi.mock("../nodes-cli.js", () => ({ registerNodesCli }));
|
||||
vi.mock("../../config/config.js", () => configModule);
|
||||
|
||||
const { registerSubCliByName, registerSubCliCommands } = await import("./register.subclis.js");
|
||||
const { loadValidatedConfigForPluginRegistration, registerSubCliByName, registerSubCliCommands } =
|
||||
await import("./register.subclis.js");
|
||||
|
||||
describe("registerSubCliCommands", () => {
|
||||
const originalArgv = process.argv;
|
||||
@@ -47,6 +54,8 @@ describe("registerSubCliCommands", () => {
|
||||
acpAction.mockClear();
|
||||
registerNodesCli.mockClear();
|
||||
nodesAction.mockClear();
|
||||
configModule.loadConfig.mockReset();
|
||||
configModule.readConfigFileSnapshot.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -79,6 +88,28 @@ describe("registerSubCliCommands", () => {
|
||||
expect(registerAcpCli).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns null for plugin registration when the config snapshot is invalid", async () => {
|
||||
configModule.readConfigFileSnapshot.mockResolvedValueOnce({
|
||||
valid: false,
|
||||
config: { plugins: { load: { paths: ["/tmp/evil"] } } },
|
||||
});
|
||||
|
||||
await expect(loadValidatedConfigForPluginRegistration()).resolves.toBeNull();
|
||||
expect(configModule.loadConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads validated config for plugin registration when the snapshot is valid", async () => {
|
||||
const loadedConfig = { plugins: { enabled: true } };
|
||||
configModule.readConfigFileSnapshot.mockResolvedValueOnce({
|
||||
valid: true,
|
||||
config: loadedConfig,
|
||||
});
|
||||
configModule.loadConfig.mockReturnValueOnce(loadedConfig);
|
||||
|
||||
await expect(loadValidatedConfigForPluginRegistration()).resolves.toBe(loadedConfig);
|
||||
expect(configModule.loadConfig).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("re-parses argv for lazy subcommands", async () => {
|
||||
const program = createRegisteredProgram(["node", "openclaw", "nodes", "list"], "openclaw");
|
||||
|
||||
|
||||
@@ -28,10 +28,15 @@ const shouldEagerRegisterSubcommands = (_argv: string[]) => {
|
||||
return isTruthyEnvValue(process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS);
|
||||
};
|
||||
|
||||
const loadConfig = async (): Promise<OpenClawConfig> => {
|
||||
const mod = await import("../../config/config.js");
|
||||
return mod.loadConfig();
|
||||
};
|
||||
export const loadValidatedConfigForPluginRegistration =
|
||||
async (): Promise<OpenClawConfig | null> => {
|
||||
const mod = await import("../../config/config.js");
|
||||
const snapshot = await mod.readConfigFileSnapshot();
|
||||
if (!snapshot.valid) {
|
||||
return null;
|
||||
}
|
||||
return mod.loadConfig();
|
||||
};
|
||||
|
||||
// Note for humans and agents:
|
||||
// If you update the list of commands, also check whether they have subcommands
|
||||
@@ -217,7 +222,10 @@ const entries: SubCliEntry[] = [
|
||||
// The pairing CLI calls listPairingChannels() at registration time,
|
||||
// which requires the plugin registry to be populated with channel plugins.
|
||||
const { registerPluginCliCommands } = await import("../../plugins/cli.js");
|
||||
registerPluginCliCommands(program, await loadConfig());
|
||||
const config = await loadValidatedConfigForPluginRegistration();
|
||||
if (config) {
|
||||
registerPluginCliCommands(program, config);
|
||||
}
|
||||
const mod = await import("../pairing-cli.js");
|
||||
mod.registerPairingCli(program);
|
||||
},
|
||||
@@ -230,7 +238,10 @@ const entries: SubCliEntry[] = [
|
||||
const mod = await import("../plugins-cli.js");
|
||||
mod.registerPluginsCli(program);
|
||||
const { registerPluginCliCommands } = await import("../../plugins/cli.js");
|
||||
registerPluginCliCommands(program, await loadConfig());
|
||||
const config = await loadValidatedConfigForPluginRegistration();
|
||||
if (config) {
|
||||
registerPluginCliCommands(program, config);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user