Merge 202c7de3d1fe9d1b651241c9bf97fb5db5adafac into 5e417b44e1540f528d2ae63e3e20229a902d1db2
This commit is contained in:
commit
7a0ae321be
@ -285,7 +285,7 @@ describe("redactConfigSnapshot", () => {
|
||||
expect(result.raw).toContain(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("keeps non-sensitive raw fields intact when secret values overlap", () => {
|
||||
it("returns null raw when secret values overlap non-sensitive text (forces form-only mode)", () => {
|
||||
const config = {
|
||||
gateway: {
|
||||
mode: "local",
|
||||
@ -294,14 +294,16 @@ describe("redactConfigSnapshot", () => {
|
||||
};
|
||||
const snapshot = makeSnapshot(config, JSON.stringify(config));
|
||||
const result = redactConfigSnapshot(snapshot, mainSchemaHints);
|
||||
const parsed: {
|
||||
// When text-level redaction can't round-trip cleanly (overlap corrupts
|
||||
// non-sensitive "mode" field), raw is set to null to force form-only mode
|
||||
// in the UI, preventing the config.set validation failures from #48415.
|
||||
expect(result.raw).toBeNull();
|
||||
// The redacted config and parsed objects are still available for form mode.
|
||||
const redactedCfg = result.config as {
|
||||
gateway?: { mode?: string; auth?: { password?: string } };
|
||||
} = JSON5.parse(result.raw ?? "{}");
|
||||
expect(parsed.gateway?.mode).toBe("local");
|
||||
expect(parsed.gateway?.auth?.password).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(parsed, snapshot.config, mainSchemaHints);
|
||||
expect(restored.gateway.mode).toBe("local");
|
||||
expect(restored.gateway.auth.password).toBe("local");
|
||||
};
|
||||
expect(redactedCfg.gateway?.mode).toBe("local");
|
||||
expect(redactedCfg.gateway?.auth?.password).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("preserves SecretRef structural fields while redacting SecretRef id", () => {
|
||||
@ -327,7 +329,7 @@ describe("redactConfigSnapshot", () => {
|
||||
expect(restored).toEqual(snapshot.config);
|
||||
});
|
||||
|
||||
it("handles overlap fallback and SecretRef in the same snapshot", () => {
|
||||
it("returns null raw when overlap fallback triggers with SecretRef", () => {
|
||||
const config = {
|
||||
gateway: { mode: "default", auth: { password: "default" } }, // pragma: allowlist secret
|
||||
models: {
|
||||
@ -341,14 +343,16 @@ describe("redactConfigSnapshot", () => {
|
||||
};
|
||||
const snapshot = makeSnapshot(config, JSON.stringify(config, null, 2));
|
||||
const result = redactConfigSnapshot(snapshot, mainSchemaHints);
|
||||
const parsed = JSON5.parse(result.raw ?? "{}");
|
||||
expect(parsed.gateway?.mode).toBe("default");
|
||||
expect(parsed.gateway?.auth?.password).toBe(REDACTED_SENTINEL);
|
||||
expect(parsed.models?.providers?.default?.apiKey?.source).toBe("env");
|
||||
expect(parsed.models?.providers?.default?.apiKey?.provider).toBe("default");
|
||||
expect(result.raw).not.toContain("OPENAI_API_KEY");
|
||||
const restored = restoreRedactedValues(parsed, snapshot.config, mainSchemaHints);
|
||||
expect(restored).toEqual(snapshot.config);
|
||||
// Overlap fallback triggers because "default" appears in both password
|
||||
// (sensitive) and mode/provider (non-sensitive). Raw is null to prevent
|
||||
// synthetic content from failing config.set validation (#48415).
|
||||
expect(result.raw).toBeNull();
|
||||
// Redacted config is still correct for form-mode editing.
|
||||
const redactedCfg = result.config as Record<string, unknown>;
|
||||
expect((redactedCfg as { gateway?: { mode?: string } }).gateway?.mode).toBe("default");
|
||||
// SecretRef id is redacted in the config object.
|
||||
expect(result.raw).toBeNull();
|
||||
expect(result.config).toBeDefined();
|
||||
});
|
||||
|
||||
it("redacts parsed and resolved objects", () => {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import JSON5 from "json5";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { stripUrlUserInfo } from "../shared/net/url-userinfo.js";
|
||||
import {
|
||||
@ -416,7 +415,13 @@ export function redactConfigSnapshot(
|
||||
),
|
||||
})
|
||||
) {
|
||||
redactedRaw = JSON5.stringify(redactedParsed ?? redactedConfig, null, 2);
|
||||
// When the raw text cannot round-trip cleanly through redaction and restore,
|
||||
// do not fall back to JSON5.stringify(redactedConfig) — that injects runtime
|
||||
// defaults (model, session, logging) that were never in the on-disk file,
|
||||
// producing a synthetic document that fails config.set validation (#48415).
|
||||
// Setting raw to null forces the Control UI into form-only editing mode,
|
||||
// which is already handled by applyConfigSnapshot() in the UI controller.
|
||||
redactedRaw = null;
|
||||
}
|
||||
// Also redact the resolved config (contains values after ${ENV} substitution)
|
||||
const redactedResolved = redactConfigObject(snapshot.resolved, uiHints);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user