CLI: prune inactive gateway auth credentials on mode set (#50639)

This commit is contained in:
Josh Avant
2026-03-19 16:05:43 -05:00
committed by GitHub
parent ca757b6b77
commit 1878272f67
2 changed files with 142 additions and 0 deletions

View File

@@ -209,6 +209,94 @@ describe("config cli", () => {
apiKey: "ollama-local", // pragma: allowlist secret
});
});
it("drops gateway.auth.password when switching mode to token", async () => {
const resolved: OpenClawConfig = {
gateway: {
auth: {
mode: "password",
token: "token-keep",
password: "password-drop", // pragma: allowlist secret
allowTailscale: true,
},
},
};
setSnapshot(resolved, resolved);
await runConfigCommand(["config", "set", "gateway.auth.mode", "token"]);
expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
const written = mockWriteConfigFile.mock.calls[0]?.[0];
expect(written.gateway?.auth).toEqual({
mode: "token",
token: "token-keep",
allowTailscale: true,
});
expect(mockLog).toHaveBeenCalledWith(
expect.stringContaining(
"Removed inactive gateway.auth.password for gateway.auth.mode=token",
),
);
});
it("drops gateway.auth.token when switching mode to password", async () => {
const resolved: OpenClawConfig = {
gateway: {
auth: {
mode: "token",
token: "token-drop",
password: "password-keep", // pragma: allowlist secret
},
},
};
setSnapshot(resolved, resolved);
await runConfigCommand(["config", "set", "gateway.auth.mode", "password"]);
expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
const written = mockWriteConfigFile.mock.calls[0]?.[0];
expect(written.gateway?.auth).toEqual({
mode: "password",
password: "password-keep", // pragma: allowlist secret
});
expect(mockLog).toHaveBeenCalledWith(
expect.stringContaining(
"Removed inactive gateway.auth.token for gateway.auth.mode=password",
),
);
});
it("applies mode-based credential cleanup using the final batch result", async () => {
const resolved: OpenClawConfig = {
gateway: {
auth: {
mode: "password",
token: "token-keep",
password: "password-drop", // pragma: allowlist secret
},
},
};
setSnapshot(resolved, resolved);
await runConfigCommand([
"config",
"set",
"--batch-json",
'[{"path":"gateway.auth.password","value":"password-updated"},{"path":"gateway.auth.mode","value":"token"}]',
]);
expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
const written = mockWriteConfigFile.mock.calls[0]?.[0];
expect(written.gateway?.auth).toEqual({
mode: "token",
token: "token-keep",
});
expect(mockLog).toHaveBeenCalledWith(
expect.stringContaining(
"Removed inactive gateway.auth.password for gateway.auth.mode=token",
),
);
});
});
describe("config get", () => {

View File

@@ -69,6 +69,7 @@ type ConfigSetOperation = {
const OLLAMA_API_KEY_PATH: PathSegment[] = ["models", "providers", "ollama", "apiKey"];
const OLLAMA_PROVIDER_PATH: PathSegment[] = ["models", "providers", "ollama"];
const GATEWAY_AUTH_MODE_PATH: PathSegment[] = ["gateway", "auth", "mode"];
const SECRET_PROVIDER_PATH_PREFIX: PathSegment[] = ["secrets", "providers"];
const CONFIG_SET_EXAMPLE_VALUE = formatCliCommand(
"openclaw config set gateway.port 19001 --strict-json",
@@ -352,6 +353,48 @@ function ensureValidOllamaProviderForApiKeySet(
});
}
function pruneInactiveGatewayAuthCredentials(params: {
root: Record<string, unknown>;
operations: ConfigSetOperation[];
}): string[] {
const touchedGatewayAuthMode = params.operations.some((operation) =>
pathEquals(operation.requestedPath, GATEWAY_AUTH_MODE_PATH),
);
if (!touchedGatewayAuthMode) {
return [];
}
const gatewayRaw = params.root.gateway;
if (!gatewayRaw || typeof gatewayRaw !== "object" || Array.isArray(gatewayRaw)) {
return [];
}
const gateway = gatewayRaw as Record<string, unknown>;
const authRaw = gateway.auth;
if (!authRaw || typeof authRaw !== "object" || Array.isArray(authRaw)) {
return [];
}
const auth = authRaw as Record<string, unknown>;
const mode = typeof auth.mode === "string" ? auth.mode.trim() : "";
const removedPaths: string[] = [];
const remove = (key: "token" | "password") => {
if (Object.hasOwn(auth, key)) {
delete auth[key];
removedPaths.push(`gateway.auth.${key}`);
}
};
if (mode === "token") {
remove("password");
} else if (mode === "password") {
remove("token");
} else if (mode === "trusted-proxy") {
remove("token");
remove("password");
}
return removedPaths;
}
function toDotPath(path: PathSegment[]): string {
return path.join(".");
}
@@ -964,6 +1007,10 @@ export async function runConfigSet(opts: {
ensureValidOllamaProviderForApiKeySet(next, operation.setPath);
setAtPath(next, operation.setPath, operation.value);
}
const removedGatewayAuthPaths = pruneInactiveGatewayAuthCredentials({
root: next,
operations,
});
const nextConfig = next as OpenClawConfig;
if (opts.cliOptions.dryRun) {
@@ -1051,6 +1098,13 @@ export async function runConfigSet(opts: {
}
await writeConfigFile(next);
if (removedGatewayAuthPaths.length > 0) {
runtime.log(
info(
`Removed inactive ${removedGatewayAuthPaths.join(", ")} for gateway.auth.mode=${String(nextConfig.gateway?.auth?.mode ?? "<unset>")}.`,
),
);
}
if (operations.length === 1) {
runtime.log(
info(