🐛 fix(security): 修复 macOS 无法打开应用及三平台依赖系统钥匙串的问题

- 密文存储:新增 dailysecret 本地存储引擎,连接/代理/AI 密钥不再依赖系统钥匙串
- 启动迁移:自动将已有钥匙串密文迁移到本地 JSON,用户无感知
- WebKit 迁移:从旧版 Wails WebKit LocalStorage 中恢复连接与代理数据
- DMG 修复:移除 --sandbox-safe 避免扩展属性污染签名,新增 xattr 清理与签名校验
- 安全适配:钥匙串不可用时标记完成而非回滚,消除无钥匙串环境下的阻塞
- 出口脱敏:所有连接/代理 API 返回前统一 sanitize 防止密文泄漏
This commit is contained in:
Syngnat
2026-04-13 12:40:25 +08:00
parent 604aaad69d
commit c7cf9526de
36 changed files with 2097 additions and 497 deletions

View File

@@ -22,6 +22,7 @@ import { blurToFilter, normalizeBlurForPlatform, normalizeOpacityForPlatform, is
import { DATA_GRID_COLUMN_WIDTH_MODE_OPTIONS, sanitizeDataTableColumnWidthMode } from './utils/dataGridDisplay';
import { getMacNativeTitlebarPaddingLeft, getMacNativeTitlebarPaddingRight, shouldHandleMacNativeFullscreenShortcut, shouldSuppressMacNativeEscapeExit } from './utils/macWindow';
import { shouldEnableMacWindowDiagnostics } from './utils/macWindowDiagnostics';
import { resolveAboutDisplayVersion } from './utils/appVersionDisplay';
import { buildOverlayWorkbenchTheme } from './utils/overlayWorkbenchTheme';
import { getConnectionWorkbenchState } from './utils/startupReadiness';
import { toSaveGlobalProxyInput } from './utils/globalProxyDraft';
@@ -181,6 +182,7 @@ function App() {
const effectiveBlur = normalizeBlurForPlatform(resolvedAppearance.blur);
const blurFilter = blurToFilter(effectiveBlur);
const [runtimePlatform, setRuntimePlatform] = useState('');
const [runtimeBuildType, setRuntimeBuildType] = useState('');
const [isLinuxRuntime, setIsLinuxRuntime] = useState(false);
const [isStoreHydrated, setIsStoreHydrated] = useState(() => useStore.persist.hasHydrated());
const [hasLoadedSecureConfig, setHasLoadedSecureConfig] = useState(false);
@@ -219,13 +221,16 @@ function App() {
const windowCornerRadius = 14;
useEffect(()=>{
if (typeof document === 'undefined' || !document.body) {
return;
}
switch(windowState){
case 'fullscreen':
case 'maximized':
document.body.setAttribute('--gonavi-border-radius', '0px');
document.body.style.setProperty('--gonavi-border-radius', '0px');
break;
default:
document.body.setAttribute('--gonavi-border-radius', `${windowCornerRadius}px`);
document.body.style.setProperty('--gonavi-border-radius', `${windowCornerRadius}px`);
break;
}
}, [windowState]);
@@ -246,6 +251,7 @@ function App() {
if (cancelled) return;
const platform = String(env?.platform || '').toLowerCase();
setRuntimePlatform(platform);
setRuntimeBuildType(String(env?.buildType || '').toLowerCase());
setIsLinuxRuntime(platform === 'linux');
})
.catch(() => {
@@ -1089,6 +1095,7 @@ function App() {
const isAboutOpenRef = React.useRef(false);
const [aboutLoading, setAboutLoading] = useState(false);
const [aboutInfo, setAboutInfo] = useState<{ version: string; author: string; buildTime?: string; repoUrl?: string; issueUrl?: string; releaseUrl?: string; communityUrl?: string } | null>(null);
const aboutDisplayVersion = resolveAboutDisplayVersion(runtimeBuildType, aboutInfo?.version);
const [aboutUpdateStatus, setAboutUpdateStatus] = useState<string>('');
const [lastUpdateInfo, setLastUpdateInfo] = useState<UpdateInfo | null>(null);
const [updateDownloadProgress, setUpdateDownloadProgress] = useState<{
@@ -1145,7 +1152,11 @@ function App() {
const isWindowsRuntime = runtimePlatform === 'windows'
|| (runtimePlatform === '' && isWindowsPlatform());
const useNativeMacWindowControls = isMacRuntime && appearance.useNativeMacWindowControls === true;
const macWindowDiagnosticsEnabled = shouldEnableMacWindowDiagnostics(isMacRuntime, import.meta.env.DEV);
const macWindowDiagnosticsEnabled = shouldEnableMacWindowDiagnostics(
isMacRuntime,
import.meta.env.DEV,
import.meta.env.VITE_GONAVI_ENABLE_MAC_WINDOW_DIAGNOSTICS,
);
const emitWindowDiagnostic = useCallback(async (stage: string, extra: Record<string, unknown> = {}) => {
if (!macWindowDiagnosticsEnabled) {
@@ -3040,7 +3051,7 @@ function App() {
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 12 }}>
<div>
<div style={{ marginBottom: 6, fontWeight: 600 }}></div>
<div style={utilityMutedTextStyle}>{aboutInfo?.version || '未知'}</div>
<div style={utilityMutedTextStyle}>{aboutDisplayVersion}</div>
</div>
<div>
<div style={{ marginBottom: 6, fontWeight: 600 }}></div>

View File

@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest';
import { resolveAboutDisplayVersion } from './appVersionDisplay';
describe('resolveAboutDisplayVersion', () => {
it('shows fixed dev version for development build', () => {
expect(resolveAboutDisplayVersion('development', '0.6.5')).toBe('0.0.1-dev');
});
it('shows fixed dev version for wails dev build type', () => {
expect(resolveAboutDisplayVersion('dev', '0.6.5')).toBe('0.0.1-dev');
});
it('keeps real version for non-development builds', () => {
expect(resolveAboutDisplayVersion('production', '0.6.5')).toBe('0.6.5');
});
it('falls back to unknown when version is empty outside development', () => {
expect(resolveAboutDisplayVersion('production', '')).toBe('未知');
});
});

View File

@@ -0,0 +1,14 @@
const DEV_ABOUT_VERSION = '0.0.1-dev';
export const resolveAboutDisplayVersion = (
buildType: string,
version: string | undefined,
): string => {
const normalizedBuildType = String(buildType || '').trim().toLowerCase();
if (normalizedBuildType === 'development' || normalizedBuildType === 'dev') {
return DEV_ABOUT_VERSION;
}
const normalizedVersion = String(version || '').trim();
return normalizedVersion || '未知';
};

View File

@@ -4,14 +4,22 @@ import { shouldEnableMacWindowDiagnostics } from './macWindowDiagnostics';
describe('macWindowDiagnostics', () => {
it('stays disabled outside macOS runtime', () => {
expect(shouldEnableMacWindowDiagnostics(false, true)).toBe(false);
expect(shouldEnableMacWindowDiagnostics(false, true, 'true')).toBe(false);
});
it('stays disabled for production builds on macOS', () => {
expect(shouldEnableMacWindowDiagnostics(true, false)).toBe(false);
expect(shouldEnableMacWindowDiagnostics(true, false, 'true')).toBe(false);
});
it('enables diagnostics only for macOS development builds', () => {
expect(shouldEnableMacWindowDiagnostics(true, true)).toBe(true);
it('stays disabled by default for macOS development builds', () => {
expect(shouldEnableMacWindowDiagnostics(true, true)).toBe(false);
expect(shouldEnableMacWindowDiagnostics(true, true, '')).toBe(false);
expect(shouldEnableMacWindowDiagnostics(true, true, '0')).toBe(false);
});
it('enables diagnostics only when explicitly opted in on macOS development builds', () => {
expect(shouldEnableMacWindowDiagnostics(true, true, '1')).toBe(true);
expect(shouldEnableMacWindowDiagnostics(true, true, 'true')).toBe(true);
expect(shouldEnableMacWindowDiagnostics(true, true, 'yes')).toBe(true);
});
});

View File

@@ -1,6 +1,22 @@
const isTruthyFlag = (value: string | undefined): boolean => {
switch (String(value || '').trim().toLowerCase()) {
case '1':
case 'true':
case 'yes':
case 'on':
return true;
default:
return false;
}
};
export const shouldEnableMacWindowDiagnostics = (
isMacRuntime: boolean,
isDevBuild: boolean,
envValue?: string,
): boolean => {
return isMacRuntime && isDevBuild;
if (!isMacRuntime || !isDevBuild) {
return false;
}
return isTruthyFlag(envValue);
};

View File

@@ -1,2 +1,9 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_GONAVI_ENABLE_MAC_WINDOW_DIAGNOSTICS?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}