refactor: dedupe redact snapshot restore prelude

This commit is contained in:
Peter Steinberger
2026-02-19 14:18:28 +00:00
parent ffd4e85873
commit 3179097a1f
2 changed files with 59 additions and 16 deletions

View File

@@ -798,6 +798,37 @@ describe("restoreRedactedValues", () => {
expect(restoreRedactedValues_orig(undefined, { token: "x" }).ok).toBe(false);
});
it("rejects non-object inputs", () => {
expect(restoreRedactedValues_orig("token-value", { token: "x" })).toEqual({
ok: false,
error: "input not an object",
});
});
it("returns a human-readable error when sentinel cannot be restored", () => {
const incoming = {
channels: { newChannel: { token: REDACTED_SENTINEL } },
};
const result = restoreRedactedValues_orig(incoming, {});
expect(result.ok).toBe(false);
expect(result.humanReadableMessage).toContain(REDACTED_SENTINEL);
expect(result.humanReadableMessage).toContain("channels.newChannel.token");
});
it("keeps unmatched wildcard array entries unchanged outside extension paths", () => {
const hints: ConfigUiHints = {
"custom.*": { sensitive: true },
};
const incoming = {
custom: { items: [REDACTED_SENTINEL] },
};
const original = {
custom: { items: ["original-secret-value"] },
};
const result = restoreRedactedValues(incoming, original, hints) as typeof incoming;
expect(result.custom.items[0]).toBe(REDACTED_SENTINEL);
});
it("round-trips config through redact → restore", () => {
const originalConfig = {
gateway: { auth: { token: "gateway-auth-secret-token-value" }, port: 18789 },

View File

@@ -404,6 +404,20 @@ function toObjectRecord(value: unknown): Record<string, unknown> {
return {};
}
function shouldPassThroughRestoreValue(incoming: unknown): boolean {
return incoming === null || incoming === undefined || typeof incoming !== "object";
}
function toRestoreArrayContext(
incoming: unknown,
prefix: string,
): { incoming: unknown[]; path: string } | null {
if (!Array.isArray(incoming)) {
return null;
}
return { incoming, path: `${prefix}[]` };
}
function restoreArrayItemWithLookup(params: {
item: unknown;
index: number;
@@ -478,26 +492,25 @@ function restoreRedactedValuesWithLookup(
prefix: string,
hints: ConfigUiHints,
): unknown {
if (incoming === null || incoming === undefined) {
if (shouldPassThroughRestoreValue(incoming)) {
return incoming;
}
if (typeof incoming !== "object") {
return incoming;
}
if (Array.isArray(incoming)) {
const arrayContext = toRestoreArrayContext(incoming, prefix);
if (arrayContext) {
// Note: If the user removed an item in the middle of the array,
// we have no way of knowing which one. In this case, the last
// element(s) get(s) chopped off. Not good, so please don't put
// sensitive string array in the config...
const path = `${prefix}[]`;
const { incoming: incomingArray, path } = arrayContext;
if (!lookup.has(path)) {
if (!isExtensionPath(prefix)) {
return incoming;
return incomingArray;
}
return restoreRedactedValuesGuessing(incoming, original, prefix, hints);
return restoreRedactedValuesGuessing(incomingArray, original, prefix, hints);
}
return mapRedactedArray({
incoming,
incoming: incomingArray,
original,
path,
mapItem: (item, index, originalArray) =>
@@ -551,19 +564,18 @@ function restoreRedactedValuesGuessing(
prefix: string,
hints?: ConfigUiHints,
): unknown {
if (incoming === null || incoming === undefined) {
if (shouldPassThroughRestoreValue(incoming)) {
return incoming;
}
if (typeof incoming !== "object") {
return incoming;
}
if (Array.isArray(incoming)) {
const arrayContext = toRestoreArrayContext(incoming, prefix);
if (arrayContext) {
// Note: If the user removed an item in the middle of the array,
// we have no way of knowing which one. In this case, the last
// element(s) get(s) chopped off. Not good, so please don't put
// sensitive string array in the config...
const path = `${prefix}[]`;
return restoreGuessingArray(incoming, original, path, hints);
const { incoming: incomingArray, path } = arrayContext;
return restoreGuessingArray(incomingArray, original, path, hints);
}
const orig = toObjectRecord(original);
const result: Record<string, unknown> = {};