fix: Finish credential redaction that was merged unfinished (#13073)

* Squash

* Removed unused files

Not mine, someone merged that stuff in earlier.

* fix: patch redaction regressions and schema breakages

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Henry Loenwind
2026-02-13 16:19:21 +01:00
committed by GitHub
parent faec6ccb1d
commit 96318641d8
18 changed files with 1641 additions and 1291 deletions

View File

@@ -17,7 +17,7 @@ import {
redactConfigSnapshot,
restoreRedactedValues,
} from "../../config/redact-snapshot.js";
import { buildConfigSchema } from "../../config/schema.js";
import { buildConfigSchema, type ConfigSchemaResponse } from "../../config/schema.js";
import {
formatDoctorNonInteractiveHint,
type RestartSentinelPayload,
@@ -91,6 +91,41 @@ function requireConfigBaseHash(
return true;
}
function loadSchemaWithPlugins(): ConfigSchemaResponse {
const cfg = loadConfig();
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const pluginRegistry = loadOpenClawPlugins({
config: cfg,
cache: true,
workspaceDir,
logger: {
info: () => {},
warn: () => {},
error: () => {},
debug: () => {},
},
});
// Note: We can't easily cache this, as there are no callback that can invalidate
// our cache. However, both loadConfig() and loadOpenClawPlugins() already cache
// their results, and buildConfigSchema() is just a cheap transformation.
return buildConfigSchema({
plugins: pluginRegistry.plugins.map((plugin) => ({
id: plugin.id,
name: plugin.name,
description: plugin.description,
configUiHints: plugin.configUiHints,
configSchema: plugin.configJsonSchema,
})),
channels: listChannelPlugins().map((entry) => ({
id: entry.id,
label: entry.meta.label,
description: entry.meta.blurb,
configSchema: entry.configSchema?.schema,
configUiHints: entry.configSchema?.uiHints,
})),
});
}
export const configHandlers: GatewayRequestHandlers = {
"config.get": async ({ params, respond }) => {
if (!validateConfigGetParams(params)) {
@@ -105,7 +140,8 @@ export const configHandlers: GatewayRequestHandlers = {
return;
}
const snapshot = await readConfigFileSnapshot();
respond(true, redactConfigSnapshot(snapshot), undefined);
const schema = loadSchemaWithPlugins();
respond(true, redactConfigSnapshot(snapshot, schema.uiHints), undefined);
},
"config.schema": ({ params, respond }) => {
if (!validateConfigSchemaParams(params)) {
@@ -119,35 +155,7 @@ export const configHandlers: GatewayRequestHandlers = {
);
return;
}
const cfg = loadConfig();
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const pluginRegistry = loadOpenClawPlugins({
config: cfg,
workspaceDir,
logger: {
info: () => {},
warn: () => {},
error: () => {},
debug: () => {},
},
});
const schema = buildConfigSchema({
plugins: pluginRegistry.plugins.map((plugin) => ({
id: plugin.id,
name: plugin.name,
description: plugin.description,
configUiHints: plugin.configUiHints,
configSchema: plugin.configJsonSchema,
})),
channels: listChannelPlugins().map((entry) => ({
id: entry.id,
label: entry.meta.label,
description: entry.meta.blurb,
configSchema: entry.configSchema?.schema,
configUiHints: entry.configSchema?.uiHints,
})),
});
respond(true, schema, undefined);
respond(true, loadSchemaWithPlugins(), undefined);
},
"config.set": async ({ params, respond }) => {
if (!validateConfigSetParams(params)) {
@@ -179,7 +187,17 @@ export const configHandlers: GatewayRequestHandlers = {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error));
return;
}
const validated = validateConfigObjectWithPlugins(parsedRes.parsed);
const schemaSet = loadSchemaWithPlugins();
const restored = restoreRedactedValues(parsedRes.parsed, snapshot.config, schemaSet.uiHints);
if (!restored.ok) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, restored.humanReadableMessage ?? "invalid config"),
);
return;
}
const validated = validateConfigObjectWithPlugins(restored.result);
if (!validated.ok) {
respond(
false,
@@ -190,27 +208,13 @@ export const configHandlers: GatewayRequestHandlers = {
);
return;
}
let restored: typeof validated.config;
try {
restored = restoreRedactedValues(
validated.config,
snapshot.config,
) as typeof validated.config;
} catch (err) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, String(err instanceof Error ? err.message : err)),
);
return;
}
await writeConfigFile(restored);
await writeConfigFile(validated.config);
respond(
true,
{
ok: true,
path: CONFIG_PATH,
config: redactConfigObject(restored),
config: redactConfigObject(validated.config, schemaSet.uiHints),
},
undefined,
);
@@ -269,19 +273,21 @@ export const configHandlers: GatewayRequestHandlers = {
return;
}
const merged = applyMergePatch(snapshot.config, parsedRes.parsed);
let restoredMerge: unknown;
try {
restoredMerge = restoreRedactedValues(merged, snapshot.config);
} catch (err) {
const schemaPatch = loadSchemaWithPlugins();
const restoredMerge = restoreRedactedValues(merged, snapshot.config, schemaPatch.uiHints);
if (!restoredMerge.ok) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, String(err instanceof Error ? err.message : err)),
errorShape(
ErrorCodes.INVALID_REQUEST,
restoredMerge.humanReadableMessage ?? "invalid config",
),
);
return;
}
const migrated = applyLegacyMigrations(restoredMerge);
const resolved = migrated.next ?? restoredMerge;
const migrated = applyLegacyMigrations(restoredMerge.result);
const resolved = migrated.next ?? restoredMerge.result;
const validated = validateConfigObjectWithPlugins(resolved);
if (!validated.ok) {
respond(
@@ -336,7 +342,7 @@ export const configHandlers: GatewayRequestHandlers = {
{
ok: true,
path: CONFIG_PATH,
config: redactConfigObject(validated.config),
config: redactConfigObject(validated.config, schemaPatch.uiHints),
restart,
sentinel: {
path: sentinelPath,
@@ -379,7 +385,17 @@ export const configHandlers: GatewayRequestHandlers = {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error));
return;
}
const validated = validateConfigObjectWithPlugins(parsedRes.parsed);
const schemaApply = loadSchemaWithPlugins();
const restored = restoreRedactedValues(parsedRes.parsed, snapshot.config, schemaApply.uiHints);
if (!restored.ok) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, restored.humanReadableMessage ?? "invalid config"),
);
return;
}
const validated = validateConfigObjectWithPlugins(restored.result);
if (!validated.ok) {
respond(
false,
@@ -390,21 +406,7 @@ export const configHandlers: GatewayRequestHandlers = {
);
return;
}
let restoredApply: typeof validated.config;
try {
restoredApply = restoreRedactedValues(
validated.config,
snapshot.config,
) as typeof validated.config;
} catch (err) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, String(err instanceof Error ? err.message : err)),
);
return;
}
await writeConfigFile(restoredApply);
await writeConfigFile(validated.config);
const sessionKey =
typeof (params as { sessionKey?: unknown }).sessionKey === "string"
@@ -447,7 +449,7 @@ export const configHandlers: GatewayRequestHandlers = {
{
ok: true,
path: CONFIG_PATH,
config: redactConfigObject(restoredApply),
config: redactConfigObject(validated.config, schemaApply.uiHints),
restart,
sentinel: {
path: sentinelPath,