Skip to content

Commit 5a72378

Browse files
committed
fix: keep plugin metadata out of config snapshots
1 parent ab28cfa commit 5a72378

8 files changed

Lines changed: 92 additions & 23 deletions

src/config/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export {
1515
parseConfigJson5,
1616
promoteConfigSnapshotToLastKnownGood,
1717
readConfigFileSnapshot,
18+
readConfigFileSnapshotWithPluginMetadata,
1819
readConfigFileSnapshotForWrite,
1920
readSourceConfigSnapshot,
2021
readSourceConfigSnapshotForWrite,

src/config/io.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,12 @@ function resolveLegacyConfigForRead(
11571157
type ReadConfigFileSnapshotInternalResult = {
11581158
snapshot: ConfigFileSnapshot;
11591159
envSnapshotForRestore?: Record<string, string | undefined>;
1160+
pluginMetadataSnapshot?: PluginMetadataSnapshot;
1161+
};
1162+
1163+
export type ReadConfigFileSnapshotWithPluginMetadataResult = {
1164+
snapshot: ConfigFileSnapshot;
1165+
pluginMetadataSnapshot?: PluginMetadataSnapshot;
11601166
};
11611167

11621168
function createConfigFileSnapshot(params: {
@@ -1171,7 +1177,6 @@ function createConfigFileSnapshot(params: {
11711177
issues: ConfigFileSnapshot["issues"];
11721178
warnings: ConfigFileSnapshot["warnings"];
11731179
legacyIssues: LegacyConfigIssue[];
1174-
pluginMetadataSnapshot?: PluginMetadataSnapshot;
11751180
}): ConfigFileSnapshot {
11761181
const sourceConfig = asResolvedSourceConfig(params.sourceConfig);
11771182
const runtimeConfig = asRuntimeConfig(params.runtimeConfig);
@@ -1189,9 +1194,6 @@ function createConfigFileSnapshot(params: {
11891194
issues: params.issues,
11901195
warnings: params.warnings,
11911196
legacyIssues: params.legacyIssues,
1192-
...(params.pluginMetadataSnapshot
1193-
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
1194-
: {}),
11951197
};
11961198
}
11971199

@@ -1812,9 +1814,9 @@ export function createConfigIO(
18121814
issues: [],
18131815
warnings: [...validated.warnings, ...envVarWarnings],
18141816
legacyIssues: legacyResolution.sourceLegacyIssues,
1815-
pluginMetadataSnapshot,
18161817
}),
18171818
envSnapshotForRestore: readResolution.envSnapshotForRestore,
1819+
pluginMetadataSnapshot,
18181820
}),
18191821
);
18201822
} catch (err) {
@@ -1860,6 +1862,16 @@ export function createConfigIO(
18601862
return result.snapshot;
18611863
}
18621864

1865+
async function readConfigFileSnapshotWithPluginMetadata(): Promise<ReadConfigFileSnapshotWithPluginMetadataResult> {
1866+
const result = await readConfigFileSnapshotInternal();
1867+
return {
1868+
snapshot: result.snapshot,
1869+
...(result.pluginMetadataSnapshot
1870+
? { pluginMetadataSnapshot: result.pluginMetadataSnapshot }
1871+
: {}),
1872+
};
1873+
}
1874+
18631875
async function promoteConfigSnapshotToLastKnownGood(
18641876
snapshot: ConfigFileSnapshot,
18651877
): Promise<boolean> {
@@ -2250,6 +2262,7 @@ export function createConfigIO(
22502262
readBestEffortConfig,
22512263
readSourceConfigBestEffort,
22522264
readConfigFileSnapshot,
2265+
readConfigFileSnapshotWithPluginMetadata,
22532266
readConfigFileSnapshotForWrite,
22542267
promoteConfigSnapshotToLastKnownGood,
22552268
recoverConfigFromLastKnownGood,
@@ -2358,6 +2371,14 @@ export async function readConfigFileSnapshot(options?: {
23582371
).readConfigFileSnapshot();
23592372
}
23602373

2374+
export async function readConfigFileSnapshotWithPluginMetadata(options?: {
2375+
measure?: ConfigSnapshotReadMeasure;
2376+
}): Promise<ReadConfigFileSnapshotWithPluginMetadataResult> {
2377+
return await createConfigIO(
2378+
options?.measure ? { measure: options.measure } : {},
2379+
).readConfigFileSnapshotWithPluginMetadata();
2380+
}
2381+
23612382
export async function promoteConfigSnapshotToLastKnownGood(
23622383
snapshot: ConfigFileSnapshot,
23632384
): Promise<boolean> {

src/config/redact-snapshot.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,32 @@ function expectGatewayAuthFieldValue(
3232
}
3333

3434
describe("redactConfigSnapshot", () => {
35+
it("does not expose internal plugin metadata snapshot fields", () => {
36+
const snapshot = {
37+
...makeSnapshot({
38+
plugins: {
39+
allow: ["demo"],
40+
},
41+
}),
42+
pluginMetadataSnapshot: {
43+
manifestRegistry: {
44+
plugins: [
45+
{
46+
id: "demo",
47+
rootDir: "/private/plugin/root",
48+
manifestPath: "/private/plugin/root/openclaw.plugin.json",
49+
},
50+
],
51+
diagnostics: [],
52+
},
53+
},
54+
};
55+
56+
const result = redactConfigSnapshot(snapshot);
57+
58+
expect("pluginMetadataSnapshot" in result).toBe(false);
59+
});
60+
3561
it("redacts common secret field patterns across config sections", () => {
3662
const snapshot = makeSnapshot({
3763
gateway: {

src/config/redact-snapshot.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,9 +455,13 @@ export function redactConfigSnapshot(
455455
}
456456
// Also redact the resolved config (contains values after ${ENV} substitution)
457457
const redactedResolved = redactConfigObject(snapshot.resolved, uiHints);
458+
const { pluginMetadataSnapshot: _pluginMetadataSnapshot, ...publicSnapshot } =
459+
snapshot as typeof snapshot & {
460+
pluginMetadataSnapshot?: unknown;
461+
};
458462

459463
return {
460-
...snapshot,
464+
...publicSnapshot,
461465
sourceConfig: redactedResolved,
462466
runtimeConfig: redactedConfig,
463467
config: redactedConfig,

src/config/types.openclaw.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
21
import type {
32
SilentReplyPolicyShape,
43
SilentReplyRewriteShape,
@@ -185,5 +184,4 @@ export type ConfigFileSnapshot = {
185184
issues: ConfigValidationIssue[];
186185
warnings: ConfigValidationIssue[];
187186
legacyIssues: LegacyConfigIssue[];
188-
pluginMetadataSnapshot?: PluginMetadataSnapshot;
189187
};

src/gateway/server-startup-config.recovery.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ vi.mock("../config/config.js", () => ({
5656
applyConfigOverrides: vi.fn((config: OpenClawConfig) => config),
5757
isNixMode: false,
5858
readConfigFileSnapshot: vi.fn(),
59+
readConfigFileSnapshotWithPluginMetadata: vi.fn(),
5960
recoverConfigFromLastKnownGood: vi.fn(),
6061
recoverConfigFromJsonRootSuffix: vi.fn(),
6162
isPluginLocalInvalidConfigSnapshot: vi.fn((snapshot: ConfigFileSnapshot) => {
@@ -128,6 +129,9 @@ describe("gateway startup config recovery", () => {
128129

129130
beforeEach(() => {
130131
vi.clearAllMocks();
132+
vi.mocked(configIo.readConfigFileSnapshotWithPluginMetadata).mockImplementation(async () => ({
133+
snapshot: await vi.mocked(configIo.readConfigFileSnapshot)(),
134+
}));
131135
});
132136

133137
it("runs startup plugin auto-enable against source config without persisting runtime defaults", async () => {
@@ -172,9 +176,11 @@ describe("gateway startup config recovery", () => {
172176
resolved: sourceConfig,
173177
runtimeConfig,
174178
config: runtimeConfig,
175-
pluginMetadataSnapshot,
176179
} satisfies ConfigFileSnapshot;
177-
vi.mocked(configIo.readConfigFileSnapshot).mockResolvedValueOnce(snapshot);
180+
vi.mocked(configIo.readConfigFileSnapshotWithPluginMetadata).mockResolvedValueOnce({
181+
snapshot,
182+
pluginMetadataSnapshot,
183+
});
178184
const log = { info: vi.fn(), warn: vi.fn() };
179185

180186
await expect(
@@ -185,9 +191,10 @@ describe("gateway startup config recovery", () => {
185191
).resolves.toEqual({
186192
snapshot,
187193
wroteConfig: false,
194+
pluginMetadataSnapshot,
188195
});
189196

190-
expect(configIo.readConfigFileSnapshot).toHaveBeenCalledTimes(1);
197+
expect(configIo.readConfigFileSnapshotWithPluginMetadata).toHaveBeenCalledTimes(1);
191198
expect(applyPluginAutoEnable).toHaveBeenCalledWith({
192199
config: sourceConfig,
193200
env: process.env,

src/gateway/server-startup-config.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
type OpenClawConfig,
77
applyConfigOverrides,
88
isNixMode,
9-
readConfigFileSnapshot,
9+
readConfigFileSnapshotWithPluginMetadata,
1010
recoverConfigFromLastKnownGood,
1111
recoverConfigFromJsonRootSuffix,
1212
replaceConfigFile,
@@ -18,6 +18,7 @@ import { formatConfigIssueLines } from "../config/issue-format.js";
1818
import { asResolvedSourceConfig, materializeRuntimeConfig } from "../config/materialize.js";
1919
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
2020
import { isTruthyEnvValue } from "../infra/env.js";
21+
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
2122
import {
2223
GATEWAY_AUTH_SURFACE_PATHS,
2324
evaluateGatewayAuthSurfaceStates,
@@ -61,6 +62,7 @@ type GatewayStartupConfigMeasure = <T>(name: string, run: () => T | Promise<T>)
6162
export type GatewayStartupConfigSnapshotLoadResult = {
6263
snapshot: ConfigFileSnapshot;
6364
wroteConfig: boolean;
65+
pluginMetadataSnapshot?: PluginMetadataSnapshot;
6466
degradedProviderApi?: boolean;
6567
degradedPluginConfig?: boolean;
6668
};
@@ -192,9 +194,11 @@ export async function loadGatewayStartupConfigSnapshot(params: {
192194
measure?: GatewayStartupConfigMeasure;
193195
}): Promise<GatewayStartupConfigSnapshotLoadResult> {
194196
const measure = params.measure ?? (async (_name, run) => await run());
195-
let configSnapshot = await measure("config.snapshot.read", () =>
196-
readConfigFileSnapshot({ measure }),
197+
let snapshotRead = await measure("config.snapshot.read", () =>
198+
readConfigFileSnapshotWithPluginMetadata({ measure }),
197199
);
200+
let configSnapshot = snapshotRead.snapshot;
201+
let pluginMetadataSnapshot = snapshotRead.pluginMetadataSnapshot;
198202
let wroteConfig = false;
199203
let degradedStartupConfig = false;
200204
let degradedPluginConfig = false;
@@ -242,9 +246,11 @@ export async function loadGatewayStartupConfigSnapshot(params: {
242246
params.log.warn(
243247
`gateway: invalid config was restored from last-known-good backup: ${configSnapshot.path}`,
244248
);
245-
configSnapshot = await measure("config.snapshot.recovery-read", () =>
246-
readConfigFileSnapshot({ measure }),
249+
snapshotRead = await measure("config.snapshot.recovery-read", () =>
250+
readConfigFileSnapshotWithPluginMetadata({ measure }),
247251
);
252+
configSnapshot = snapshotRead.snapshot;
253+
pluginMetadataSnapshot = snapshotRead.pluginMetadataSnapshot;
248254
if (configSnapshot.valid) {
249255
enqueueConfigRecoveryNotice({
250256
cfg: configSnapshot.config,
@@ -259,9 +265,11 @@ export async function loadGatewayStartupConfigSnapshot(params: {
259265
params.log.warn(
260266
`gateway: invalid config was repaired by stripping a non-JSON prefix: ${configSnapshot.path}`,
261267
);
262-
configSnapshot = await measure("config.snapshot.prefix-recovery-read", () =>
263-
readConfigFileSnapshot({ measure }),
268+
snapshotRead = await measure("config.snapshot.prefix-recovery-read", () =>
269+
readConfigFileSnapshotWithPluginMetadata({ measure }),
264270
);
271+
configSnapshot = snapshotRead.snapshot;
272+
pluginMetadataSnapshot = snapshotRead.pluginMetadataSnapshot;
265273
}
266274
}
267275
assertValidGatewayStartupConfigSnapshot(configSnapshot, { includeDoctorHint: true });
@@ -274,15 +282,16 @@ export async function loadGatewayStartupConfigSnapshot(params: {
274282
applyPluginAutoEnable({
275283
config: configSnapshot.sourceConfig,
276284
env: process.env,
277-
...(configSnapshot.pluginMetadataSnapshot?.manifestRegistry
278-
? { manifestRegistry: configSnapshot.pluginMetadataSnapshot.manifestRegistry }
285+
...(pluginMetadataSnapshot?.manifestRegistry
286+
? { manifestRegistry: pluginMetadataSnapshot.manifestRegistry }
279287
: {}),
280288
}),
281289
);
282290
if (autoEnable.changes.length === 0) {
283291
return {
284292
snapshot: configSnapshot,
285293
wroteConfig,
294+
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
286295
...(degradedStartupConfig ? { degradedProviderApi: true } : {}),
287296
...(degradedPluginConfig ? { degradedPluginConfig: true } : {}),
288297
};
@@ -294,9 +303,11 @@ export async function loadGatewayStartupConfigSnapshot(params: {
294303
afterWrite: { mode: "auto" },
295304
});
296305
wroteConfig = true;
297-
configSnapshot = await measure("config.snapshot.auto-enable-read", () =>
298-
readConfigFileSnapshot({ measure }),
306+
snapshotRead = await measure("config.snapshot.auto-enable-read", () =>
307+
readConfigFileSnapshotWithPluginMetadata({ measure }),
299308
);
309+
configSnapshot = snapshotRead.snapshot;
310+
pluginMetadataSnapshot = snapshotRead.pluginMetadataSnapshot;
300311
assertValidGatewayStartupConfigSnapshot(configSnapshot);
301312
params.log.info(
302313
`gateway: auto-enabled plugins:\n${autoEnable.changes.map((entry) => `- ${entry}`).join("\n")}`,
@@ -308,6 +319,7 @@ export async function loadGatewayStartupConfigSnapshot(params: {
308319
return {
309320
snapshot: configSnapshot,
310321
wroteConfig,
322+
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
311323
...(degradedStartupConfig ? { degradedProviderApi: true } : {}),
312324
...(degradedPluginConfig ? { degradedPluginConfig: true } : {}),
313325
};

src/gateway/server.impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ export async function startGatewayServer(
411411
cfgAtStart,
412412
activationSourceConfig: startupActivationSourceConfig,
413413
startupRuntimeConfig,
414-
pluginMetadataSnapshot: configSnapshot.pluginMetadataSnapshot,
414+
pluginMetadataSnapshot: startupConfigLoad.pluginMetadataSnapshot,
415415
minimalTestGateway,
416416
log,
417417
}),

0 commit comments

Comments
 (0)