diff --git a/frontend/sync-client/src/persistence/settings.test.ts b/frontend/sync-client/src/persistence/settings.test.ts new file mode 100644 index 00000000..2f4bcee0 --- /dev/null +++ b/frontend/sync-client/src/persistence/settings.test.ts @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { Logger, LogLevel } from "../tracing/logger"; +import { Settings } from "./settings"; + +describe("Settings logging", () => { + it("redacts tokens while loading and updating settings", async () => { + const logger = new Logger(); + const settings = new Settings( + logger, + { token: "loaded-secret" }, + async () => {} + ); + await settings.setSettings({ token: "updated-secret" }); + + const messages = logger + .getMessages(LogLevel.DEBUG) + .map((entry) => entry.message) + .join("\n"); + assert.doesNotMatch(messages, /loaded-secret|updated-secret/u); + assert.match(messages, /\[REDACTED\]/u); + }); +}); diff --git a/frontend/sync-client/src/persistence/settings.ts b/frontend/sync-client/src/persistence/settings.ts index f1f17f26..bac7c69d 100644 --- a/frontend/sync-client/src/persistence/settings.ts +++ b/frontend/sync-client/src/persistence/settings.ts @@ -6,36 +6,35 @@ export interface SyncSettings { remoteUri: string; token: string; vaultName: string; - syncConcurrency: number; isSyncEnabled: boolean; maxFileSizeMB: number; ignorePatterns: string[]; webSocketRetryIntervalMs: number; - diffCacheSizeMB: number; enableTelemetry: boolean; requestTimeoutMs: number; networkRetryIntervalMs: number; syncIntervalMs?: number; - minimumSaveIntervalMs: number; } export const DEFAULT_SETTINGS: SyncSettings = { remoteUri: "", token: "", vaultName: "default", - syncConcurrency: 1, isSyncEnabled: false, maxFileSizeMB: 10, ignorePatterns: [], webSocketRetryIntervalMs: 3500, - diffCacheSizeMB: 4, enableTelemetry: false, requestTimeoutMs: 30_000, networkRetryIntervalMs: 1000, - syncIntervalMs: undefined, - minimumSaveIntervalMs: 1000 + syncIntervalMs: undefined }; +const redactSettings = (value: Partial): object => ({ + ...value, + ...(value.token === undefined ? {} : { token: "[REDACTED]" }) +}); + export class Settings { public readonly onSettingsChanged = new EventListeners< (newSettings: SyncSettings, oldSettings: SyncSettings) => unknown @@ -55,7 +54,7 @@ export class Settings { }; this.logger.debug( - `Loaded settings: ${JSON.stringify(this.settings, null, 2)}` + `Loaded settings: ${JSON.stringify(redactSettings(this.settings), null, 2)}` ); } @@ -78,7 +77,7 @@ export class Settings { public async setSettings(value: Partial): Promise { await this.lock.withLock(async () => { this.logger.debug( - `Updating settings with: ${JSON.stringify(value)}` + `Updating settings with: ${JSON.stringify(redactSettings(value))}` ); const oldSettings = this.settings; const next = { ...this.settings, ...value };