You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/10/12 01:08:16 UTC
(maka) 02/04: fix(settings): preserve bot credentials through settings updates
This is an automated email from the ASF dual-hosted git repository.
Astro-Han pushed a commit to branch fix/channels-settings-roundtrip
in repository https://gitbox.apache.org/repos/asf/maka.git
commit 15451892c12ada2b76cb60c66c241a79df548c77
Author: AstroHan <le...@outlook.com>
AuthorDate: Sun Oct 11 22:28:43 2026 +1000
fix(settings): preserve bot credentials through settings updates
Generated-by: Codex
---
.../__tests__/client-settings-ipc-main.test.ts | 6 ++-
.../main/__tests__/settings-ipc-helpers.test.ts | 46 ++++++++++++++--------
apps/desktop/src/main/bot-onboarding-main.ts | 2 +
apps/desktop/src/main/settings-ipc-helpers.ts | 27 ++-----------
.../core/src/__tests__/bot-chat-settings.test.ts | 34 ++++++++++++++++
packages/core/src/bot-chat-settings.ts | 24 +++++------
.../core/src/settings/__tests__/sensitive.test.ts | 1 +
packages/core/src/settings/network-settings.ts | 5 ++-
8 files changed, 86 insertions(+), 59 deletions(-)
diff --git a/apps/desktop/src/main/__tests__/client-settings-ipc-main.test.ts b/apps/desktop/src/main/__tests__/client-settings-ipc-main.test.ts
index 0bf6e0bc2d..e0f6d0d21f 100644
--- a/apps/desktop/src/main/__tests__/client-settings-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/client-settings-ipc-main.test.ts
@@ -19,6 +19,7 @@
import assert from "node:assert/strict";
import test from "node:test";
+import { SENSITIVE_PLACEHOLDER } from "@maka/core/settings/network-settings";
import {
createDefaultSettings,
mergeSettings,
@@ -26,7 +27,7 @@ import {
} from "@maka/core/settings";
import { registerClientSettingsIpc } from "../client-settings-ipc-main.js";
-test("client settings updates filter Host policy and return newly submitted secrets", async () => {
+test("client settings updates filter Host policy and mask newly submitted secrets", async () => {
const handlers = new Map<string, (...args: unknown[]) => unknown>();
let settings = createDefaultSettings();
let storedPatch: UpdateAppSettingsInput | undefined;
@@ -63,8 +64,9 @@ test("client settings updates filter Host policy and return newly submitted secr
assert.equal((result as { settings: typeof settings }).settings.appearance.theme, "dark");
assert.equal(
(result as { settings: typeof settings }).settings.botChat.channels.telegram.token,
- "fresh-bot-token",
+ SENSITIVE_PLACEHOLDER,
);
+ assert.equal(settings.botChat.channels.telegram.token, "fresh-bot-token");
assert.equal(settings.chatDefaults.permissionMode, "bypass");
assert.equal(applied, 1);
});
diff --git a/apps/desktop/src/main/__tests__/settings-ipc-helpers.test.ts b/apps/desktop/src/main/__tests__/settings-ipc-helpers.test.ts
index 3cbd2eb556..55bd8620d1 100644
--- a/apps/desktop/src/main/__tests__/settings-ipc-helpers.test.ts
+++ b/apps/desktop/src/main/__tests__/settings-ipc-helpers.test.ts
@@ -19,7 +19,11 @@
import assert from "node:assert/strict";
import { describe, test } from "node:test";
-import { createDefaultSettings, mergeSettings } from "@maka/core/settings";
+import { mkdtemp, rm } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createSettingsStore } from "@maka/storage/settings-store";
+import { BOT_PROVIDERS, createDefaultSettings, mergeSettings } from "@maka/core/settings";
import { SENSITIVE_PLACEHOLDER } from "@maka/core/settings/network-settings";
import {
buildSettingsUpdateResult,
@@ -65,20 +69,28 @@ describe("settings IPC helpers", () => {
}
});
- test("reveals sensitive fields only when the current patch explicitly changes them", () => {
- const settings = createDefaultSettings();
- settings.botChat.channels.telegram.token = "new-bot-token";
- settings.botChat.channels.feishu.appSecret = "stored-feishu-secret";
-
- const masked = maskAppSettings(settings, {
- botChat: { channels: { telegram: { token: "new-bot-token" } } },
- });
-
- assert.equal(masked.botChat.channels.telegram.token, "new-bot-token");
- assert.equal(
- masked.botChat.channels.feishu.appSecret,
- SENSITIVE_PLACEHOLDER,
- );
+ test("masks bot credentials in save responses and preserves them across masked saves and reloads", async () => {
+ const workspaceRoot = await mkdtemp(join(tmpdir(), "maka-bot-settings-"));
+ try {
+ const store = createSettingsStore(workspaceRoot);
+ for (const provider of BOT_PROVIDERS) {
+ const patch = { botChat: { channels: { [provider]: { token: "stored-token", appSecret: "stored-secret" } } } };
+ const configured = await store.update(patch);
+ const response = buildSettingsUpdateResult(configured, patch);
+ assert.equal(response.settings.botChat.channels[provider].token, SENSITIVE_PLACEHOLDER, provider);
+ assert.equal(response.settings.botChat.channels[provider].appSecret, SENSITIVE_PLACEHOLDER, provider);
+ await store.update({ botChat: response.settings.botChat });
+ const saved = await createSettingsStore(workspaceRoot).get();
+ assert.equal(saved.botChat.channels[provider].token, "stored-token", provider);
+ assert.equal(saved.botChat.channels[provider].appSecret, "stored-secret", provider);
+ await store.update({ botChat: { channels: { [provider]: { token: "", appSecret: "" } } } });
+ const cleared = await createSettingsStore(workspaceRoot).get();
+ assert.equal(cleared.botChat.channels[provider].token, "", provider);
+ assert.ok(!cleared.botChat.channels[provider].appSecret, provider);
+ }
+ } finally {
+ await rm(workspaceRoot, { recursive: true, force: true });
+ }
});
test("never reveals Tavily API key back to renderer, even on the save response", () => {
@@ -86,9 +98,9 @@ describe("settings IPC helpers", () => {
webSearch: { providers: { tavily: { apiKey: "tvly-new-secret" } } },
});
- const masked = maskAppSettings(settings, {
+ const masked = buildSettingsUpdateResult(settings, {
webSearch: { providers: { tavily: { apiKey: "tvly-new-secret" } } },
- });
+ }).settings;
assert.equal(
masked.webSearch.providers.tavily.apiKey,
diff --git a/apps/desktop/src/main/bot-onboarding-main.ts b/apps/desktop/src/main/bot-onboarding-main.ts
index 3f3135ca00..9df7ece3a2 100644
--- a/apps/desktop/src/main/bot-onboarding-main.ts
+++ b/apps/desktop/src/main/bot-onboarding-main.ts
@@ -376,6 +376,8 @@ export class BotOnboardingService {
ownedFields: ReadonlyArray<keyof BotChannelSettings>,
): Promise<void> {
const previousValues = pickChannelFields(previousChannel, ownedFields);
+ // An absent pre-onboarding secret needs an explicit clear on rollback.
+ if ('appSecret' in previousValues) previousValues.appSecret ??= '';
const rollbackPatch: UpdateAppSettingsInput = {
botChat: { channels: { [provider]: previousValues } },
};
diff --git a/apps/desktop/src/main/settings-ipc-helpers.ts b/apps/desktop/src/main/settings-ipc-helpers.ts
index c633809d0f..73f81fb94b 100644
--- a/apps/desktop/src/main/settings-ipc-helpers.ts
+++ b/apps/desktop/src/main/settings-ipc-helpers.ts
@@ -29,7 +29,6 @@ import type { BotProvider } from '@maka/core/bot-chat-settings';
import { botDisplayLabel } from '@maka/core/bot-events';
import { generalizedErrorMessage, redactSecrets } from '@maka/core/redaction';
import {
- SENSITIVE_PLACEHOLDER,
maskSensitive,
type TestProxyResult,
} from "@maka/core/settings/network-settings";
@@ -72,15 +71,12 @@ export function proxyTestFailure(result: TestProxyResult): {
export function maskAppSettings(
settings: RuntimeHostAppSettings,
- revealPatch?: UpdateAppSettingsInput,
): RuntimeHostAppSettings;
export function maskAppSettings(
settings: AppSettings,
- revealPatch?: UpdateAppSettingsInput,
): AppSettings;
export function maskAppSettings(
settings: AppSettings,
- revealPatch: UpdateAppSettingsInput = {},
): AppSettings {
return {
...settings,
@@ -92,17 +88,8 @@ export function maskAppSettings(
provider,
{
...channel,
- token: shouldReveal(
- revealPatch.botChat?.channels?.[provider as BotProvider]?.token,
- )
- ? channel.token
- : (maskSensitive(channel.token) ?? ""),
- appSecret: shouldReveal(
- revealPatch.botChat?.channels?.[provider as BotProvider]
- ?.appSecret,
- )
- ? channel.appSecret
- : (maskSensitive(channel.appSecret) ?? ""),
+ token: maskSensitive(channel.token) ?? "",
+ appSecret: maskSensitive(channel.appSecret) ?? "",
},
]),
) as AppSettings["botChat"]["channels"],
@@ -182,19 +169,11 @@ export function buildSettingsUpdateResult(
): UpdateAppSettingsResult {
const personalization = collectPersonalizationWarnings(patch.personalization);
return {
- settings: maskAppSettings(settings, patch),
+ settings: maskAppSettings(settings),
...(personalization.length ? { warnings: { personalization } } : {}),
};
}
-function shouldReveal(value: string | undefined): boolean {
- return (
- typeof value === "string" &&
- value.length > 0 &&
- value !== SENSITIVE_PLACEHOLDER
- );
-}
-
export function toSettingsTestResult(
provider: BotProvider,
result: BotTestResult,
diff --git a/packages/core/src/__tests__/bot-chat-settings.test.ts b/packages/core/src/__tests__/bot-chat-settings.test.ts
index aced836c8c..75adfeb501 100644
--- a/packages/core/src/__tests__/bot-chat-settings.test.ts
+++ b/packages/core/src/__tests__/bot-chat-settings.test.ts
@@ -21,12 +21,46 @@ import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import {
createDefaultBotChatSettings,
+ BOT_PROVIDERS,
mergeBotChatSettings,
normalizeBotChatSettings,
parseAllowedUserIdsFromText,
} from '../bot-chat-settings.js';
+import { SENSITIVE_PLACEHOLDER } from '../settings/network-settings.js';
+import { MASKED_TOKEN_SENTINEL } from '../web-search.js';
describe('bot chat settings owner', () => {
+ test('preserves masked credentials and supports replacement, omission and explicit clearing', () => {
+ for (const provider of BOT_PROVIDERS) {
+ const configured = mergeBotChatSettings(createDefaultBotChatSettings(), {
+ channels: { [provider]: { token: 'stored-token', appSecret: 'stored-secret' } },
+ });
+ for (const mask of [SENSITIVE_PLACEHOLDER, MASKED_TOKEN_SENTINEL]) {
+ const saved = mergeBotChatSettings(configured, {
+ channels: { [provider]: { enabled: true, token: mask, appSecret: mask } },
+ });
+ assert.equal(saved.channels[provider].token, 'stored-token', provider);
+ assert.equal(saved.channels[provider].appSecret, 'stored-secret', provider);
+ assert.equal(saved.channels[provider].enabled, true, provider);
+ }
+ const omitted = mergeBotChatSettings(configured, {
+ channels: { [provider]: { token: undefined, appSecret: undefined } },
+ });
+ assert.equal(omitted.channels[provider].token, 'stored-token', provider);
+ assert.equal(omitted.channels[provider].appSecret, 'stored-secret', provider);
+ const replaced = mergeBotChatSettings(configured, {
+ channels: { [provider]: { token: 'new-token', appSecret: 'new-secret' } },
+ });
+ assert.equal(replaced.channels[provider].token, 'new-token', provider);
+ assert.equal(replaced.channels[provider].appSecret, 'new-secret', provider);
+ const cleared = mergeBotChatSettings(configured, {
+ channels: { [provider]: { token: '', appSecret: '' } },
+ });
+ assert.equal(cleared.channels[provider].token, '', provider);
+ assert.ok(!cleared.channels[provider].appSecret, provider);
+ }
+ });
+
test('normalizes an explicitly patched allowlist without touching it on unrelated patches', () => {
const defaults = createDefaultBotChatSettings();
const withAllowlist = mergeBotChatSettings(defaults, {
diff --git a/packages/core/src/bot-chat-settings.ts b/packages/core/src/bot-chat-settings.ts
index 83376311c4..2e80c8444d 100644
--- a/packages/core/src/bot-chat-settings.ts
+++ b/packages/core/src/bot-chat-settings.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import { applySensitivePatch } from './settings/network-settings.js';
+
export type BotProvider =
| 'telegram'
| 'feishu'
@@ -59,15 +61,9 @@ export interface BotChannelSettings {
lastTestAt?: number;
lastError?: string;
/**
- * PR-BOT-USER-ALLOWLIST-0 (external bot research): platform-native user IDs
- * permitted to message this bot. `undefined` or empty means no
- * restriction (preserves the V0.1 behavior for existing installs).
- * When non-empty, the bot bridge silently drops inbound messages from
- * any other user — no acknowledgement is sent back, so unauthorized
- * scanners cannot use bounce behavior to enumerate the bot's policy.
- *
- * Stored as a string array since Telegram IDs are 64-bit and JS
- * `Number` loses precision past 2^53.
+ * Platform-native sender IDs. An absent or empty list allows everyone.
+ * A configured list is enforced by the shared adapter before routing.
+ * IDs stay strings to preserve Telegram's 64-bit values.
*/
allowedUserIds?: ReadonlyArray<string>;
}
@@ -149,15 +145,13 @@ export function mergeBotChatSettings(
...current.channels,
...Object.fromEntries(
Object.entries(patch?.channels ?? {}).map(([provider, channelPatch]) => {
+ const previous = current.channels[provider as BotProvider];
const merged = {
- ...current.channels[provider as BotProvider],
+ ...previous,
...channelPatch,
+ token: applySensitivePatch(previous?.token, channelPatch?.token) ?? '',
+ appSecret: applySensitivePatch(previous?.appSecret, channelPatch?.appSecret),
};
- // PR-BOT-USER-ALLOWLIST-0: keep the persisted allowlist
- // shape consistent on every save, not only on initial load.
- // The renderer textarea sends an array; the normalize step
- // trims/dedups/caps and downgrades the empty case to
- // `undefined` (the V0.1 "no restriction" sentinel).
if ('allowedUserIds' in (channelPatch ?? {})) {
const normalized = normalizeAllowedUserIds(merged.allowedUserIds);
if (normalized) merged.allowedUserIds = normalized;
diff --git a/packages/core/src/settings/__tests__/sensitive.test.ts b/packages/core/src/settings/__tests__/sensitive.test.ts
index be28cb7f94..222ea57d4f 100644
--- a/packages/core/src/settings/__tests__/sensitive.test.ts
+++ b/packages/core/src/settings/__tests__/sensitive.test.ts
@@ -25,6 +25,7 @@ describe('applySensitivePatch', () => {
test('handles plaintext, placeholder, empty string, and undefined', () => {
assert.strictEqual(applySensitivePatch('old', 'new'), 'new');
assert.strictEqual(applySensitivePatch('old', '••••••••'), 'old');
+ assert.strictEqual(applySensitivePatch('old', '••••••'), 'old');
assert.strictEqual(applySensitivePatch('old', ''), undefined);
assert.strictEqual(applySensitivePatch('old', undefined), 'old');
});
diff --git a/packages/core/src/settings/network-settings.ts b/packages/core/src/settings/network-settings.ts
index 25f8ee4aa8..7fdf8002db 100644
--- a/packages/core/src/settings/network-settings.ts
+++ b/packages/core/src/settings/network-settings.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import { MASKED_TOKEN_SENTINEL } from '../web-search.js';
+
export type ProxyType = 'http' | 'https' | 'socks5';
export const SENSITIVE_PLACEHOLDER = '••••••••' as const;
@@ -26,7 +28,8 @@ export function applySensitivePatch(
prev: string | undefined,
next: Sensitive | undefined,
): string | undefined {
- if (next === undefined || next === SENSITIVE_PLACEHOLDER) return prev;
+ if (next === undefined || next === SENSITIVE_PLACEHOLDER || next === MASKED_TOKEN_SENTINEL)
+ return prev;
if (next === '') return undefined;
return next;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org