From c7cf9526de81b047a2f9202ecbbdbf65d0b3b57b Mon Sep 17 00:00:00 2001 From: Syngnat Date: Mon, 13 Apr 2026 12:40:25 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(security):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20macOS=20=E6=97=A0=E6=B3=95=E6=89=93=E5=BC=80?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8F=8A=E4=B8=89=E5=B9=B3=E5=8F=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E7=B3=BB=E7=BB=9F=E9=92=A5=E5=8C=99=E4=B8=B2=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 密文存储:新增 dailysecret 本地存储引擎,连接/代理/AI 密钥不再依赖系统钥匙串 - 启动迁移:自动将已有钥匙串密文迁移到本地 JSON,用户无感知 - WebKit 迁移:从旧版 Wails WebKit LocalStorage 中恢复连接与代理数据 - DMG 修复:移除 --sandbox-safe 避免扩展属性污染签名,新增 xattr 清理与签名校验 - 安全适配:钥匙串不可用时标记完成而非回滚,消除无钥匙串环境下的阻塞 - 出口脱敏:所有连接/代理 API 返回前统一 sanitize 防止密文泄漏 --- .github/workflows/dev-build.yml | 14 ++ .github/workflows/release.yml | 14 ++ build-release.sh | 141 ++++++++--- .../2026-04-11-issue-backlog-tracking.md | 111 --------- frontend/package.json.md5 | 2 +- frontend/src/App.tsx | 19 +- frontend/src/utils/appVersionDisplay.test.ts | 21 ++ frontend/src/utils/appVersionDisplay.ts | 14 ++ .../src/utils/macWindowDiagnostics.test.ts | 16 +- frontend/src/utils/macWindowDiagnostics.ts | 18 +- frontend/src/vite-env.d.ts | 7 + internal/ai/service/config_store.go | 97 ++++---- internal/ai/service/config_store_test.go | 55 ++-- .../ai/service/daily_secret_store_adapter.go | 19 ++ internal/ai/service/provider_secret.go | 99 ++++---- internal/ai/service/provider_secret_test.go | 122 +++++---- internal/ai/service/service.go | 6 +- internal/app/app.go | 34 +-- internal/app/connection_package_transfer.go | 20 +- .../app/connection_package_transfer_test.go | 66 ++--- internal/app/connection_secret_resolution.go | 2 + .../app/connection_secret_resolution_test.go | 48 +++- internal/app/daily_secret_migration.go | 223 +++++++++++++++++ internal/app/daily_secret_persistence.go | 107 ++++++++ .../app/darwin_daily_secret_migration_test.go | 210 ++++++++++++++++ internal/app/global_proxy_persistence.go | 38 +-- internal/app/global_proxy_secret_test.go | 32 +++ internal/app/legacy_webkit_storage.go | 181 ++++++++++++++ internal/app/legacy_webkit_storage_test.go | 183 ++++++++++++++ internal/app/methods_saved_connections.go | 24 +- .../app/methods_saved_connections_test.go | 121 +++++++++ internal/app/saved_connections.go | 95 +++---- internal/app/saved_connections_test.go | 23 ++ internal/app/security_update_engine_test.go | 73 +++--- internal/dailysecret/store.go | 235 ++++++++++++++++++ internal/dailysecret/store_test.go | 104 ++++++++ 36 files changed, 2097 insertions(+), 497 deletions(-) delete mode 100644 docs/issues/2026-04-11-issue-backlog-tracking.md create mode 100644 frontend/src/utils/appVersionDisplay.test.ts create mode 100644 frontend/src/utils/appVersionDisplay.ts create mode 100644 internal/ai/service/daily_secret_store_adapter.go create mode 100644 internal/app/daily_secret_migration.go create mode 100644 internal/app/daily_secret_persistence.go create mode 100644 internal/app/darwin_daily_secret_migration_test.go create mode 100644 internal/app/legacy_webkit_storage.go create mode 100644 internal/app/legacy_webkit_storage_test.go create mode 100644 internal/dailysecret/store.go create mode 100644 internal/dailysecret/store_test.go diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index b6675b7..f5601e6 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -320,6 +320,9 @@ jobs: echo "ℹ️ macOS 产物不执行 UPX 压缩,保留原始主程序。" echo "🔏 正在进行 Ad-hoc 签名..." + if command -v xattr >/dev/null 2>&1; then + xattr -cr "$APP_NAME" || true + fi codesign --force --deep --sign - "$APP_NAME" DMG_NAME="${{ matrix.build_name }}.dmg" @@ -336,6 +339,17 @@ jobs: --app-drop-link 600 185 \ "$DMG_NAME" \ "$APP_NAME" + + VERIFY_MOUNT_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gonavi-dev-verify.XXXXXX") + hdiutil attach -nobrowse -readonly -mountpoint "$VERIFY_MOUNT_DIR" "$DMG_NAME" >/dev/null + PACKAGED_APP=$(find "$VERIFY_MOUNT_DIR" -maxdepth 1 -name "*.app" | head -n 1) + if [ -z "$PACKAGED_APP" ]; then + echo "❌ DMG 内未找到 .app 应用包!" + hdiutil detach "$VERIFY_MOUNT_DIR" -quiet >/dev/null 2>&1 || true + exit 1 + fi + codesign --verify --deep --strict --verbose=4 "$PACKAGED_APP" + hdiutil detach "$VERIFY_MOUNT_DIR" -quiet >/dev/null 2>&1 || true mv "$DMG_NAME" "../../$FINAL_NAME" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0961be..b146317 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -314,6 +314,9 @@ jobs: echo "🔏 正在进行 Ad-hoc 签名..." # 注意:Ad-hoc + hardened runtime(--options runtime)在未配置 entitlements 时, # 可能导致部分 macOS 机型上应用双击无响应。这里保持 Ad-hoc 深签名但禁用 runtime hardened。 + if command -v xattr >/dev/null 2>&1; then + xattr -cr "$APP_NAME" || true + fi codesign --force --deep --sign - "$APP_NAME" DMG_NAME="${{ matrix.build_name }}.dmg" @@ -330,6 +333,17 @@ jobs: --app-drop-link 600 185 \ "$DMG_NAME" \ "$APP_NAME" + + VERIFY_MOUNT_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gonavi-release-verify.XXXXXX") + hdiutil attach -nobrowse -readonly -mountpoint "$VERIFY_MOUNT_DIR" "$DMG_NAME" >/dev/null + PACKAGED_APP=$(find "$VERIFY_MOUNT_DIR" -maxdepth 1 -name "*.app" | head -n 1) + if [ -z "$PACKAGED_APP" ]; then + echo "❌ DMG 内未找到 .app 应用包!" + hdiutil detach "$VERIFY_MOUNT_DIR" -quiet >/dev/null 2>&1 || true + exit 1 + fi + codesign --verify --deep --strict --verbose=4 "$PACKAGED_APP" + hdiutil detach "$VERIFY_MOUNT_DIR" -quiet >/dev/null 2>&1 || true mv "$DMG_NAME" "../../$FINAL_NAME" diff --git a/build-release.sh b/build-release.sh index d60cad5..d2bc228 100755 --- a/build-release.sh +++ b/build-release.sh @@ -84,6 +84,63 @@ try_compress_binary_with_upx() { fi } +clear_macos_bundle_xattrs() { + local bundle_path="$1" + if [ -z "$bundle_path" ] || [ ! -e "$bundle_path" ]; then + return + fi + if command -v xattr >/dev/null 2>&1; then + xattr -cr "$bundle_path" >/dev/null 2>&1 || true + fi +} + +verify_macos_dmg_bundle_signature() { + local dmg_path="$1" + local mount_dir="" + local app_path="" + + if [ -z "$dmg_path" ] || [ ! -f "$dmg_path" ]; then + echo -e "${RED} ❌ DMG 文件不存在,无法校验签名:$dmg_path${NC}" + return 1 + fi + if ! command -v hdiutil >/dev/null 2>&1 || ! command -v codesign >/dev/null 2>&1; then + echo -e "${YELLOW} ⚠️ 当前环境缺少 hdiutil 或 codesign,跳过 DMG 内应用签名校验。${NC}" + return 0 + fi + + mount_dir=$(mktemp -d "${TMPDIR:-/tmp}/gonavi-dmg-verify.XXXXXX") + if [ -z "$mount_dir" ] || [ ! -d "$mount_dir" ]; then + echo -e "${RED} ❌ 创建 DMG 校验挂载目录失败。${NC}" + return 1 + fi + + if ! hdiutil attach -nobrowse -readonly -mountpoint "$mount_dir" "$dmg_path" >/dev/null 2>&1; then + rmdir "$mount_dir" >/dev/null 2>&1 || true + echo -e "${RED} ❌ 挂载 DMG 失败,无法校验签名。${NC}" + return 1 + fi + + app_path=$(find "$mount_dir" -maxdepth 1 -name "*.app" -print -quit) + if [ -z "$app_path" ] || [ ! -d "$app_path" ]; then + hdiutil detach "$mount_dir" -quiet >/dev/null 2>&1 || true + rmdir "$mount_dir" >/dev/null 2>&1 || true + echo -e "${RED} ❌ DMG 内未找到 .app 应用包。${NC}" + return 1 + fi + + if ! codesign --verify --deep --strict --verbose=4 "$app_path" >/dev/null 2>&1; then + echo -e "${RED} ❌ DMG 内 .app 签名校验失败:$(basename "$app_path")${NC}" + codesign --verify --deep --strict --verbose=4 "$app_path" 2>&1 | sed 's/^/ /' + hdiutil detach "$mount_dir" -quiet >/dev/null 2>&1 || true + rmdir "$mount_dir" >/dev/null 2>&1 || true + return 1 + fi + + hdiutil detach "$mount_dir" -quiet >/dev/null 2>&1 || true + rmdir "$mount_dir" >/dev/null 2>&1 || true + return 0 +} + MAC_VOLICON_PATH="build/darwin/icon.icns" if [ ! -f "$MAC_VOLICON_PATH" ]; then MAC_VOLICON_PATH="" @@ -112,19 +169,20 @@ if [ $? -eq 0 ]; then else echo -e "${RED} ❌ 未找到 macOS arm64 主程序文件。${NC}" exit 1 - fi - - # Ad-hoc 代码签名(无 Apple Developer 账号时防止 Gatekeeper 报已损坏) - echo " 🔏 正在对 .app 进行 ad-hoc 签名 (arm64)..." - codesign --force --deep --sign - "$DIST_DIR/$APP_DEST_NAME" + fi + + # Ad-hoc 代码签名(无 Apple Developer 账号时防止 Gatekeeper 报已损坏) + echo " 🔏 正在对 .app 进行 ad-hoc 签名 (arm64)..." + clear_macos_bundle_xattrs "$DIST_DIR/$APP_DEST_NAME" + codesign --force --deep --sign - "$DIST_DIR/$APP_DEST_NAME" - # 创建 DMG - if command -v create-dmg &> /dev/null; then - echo " 📦 正在打包 DMG (arm64)..." - # 移除已存在的 DMG (以防万一) - rm -f "$DIST_DIR/$DMG_NAME" - # create-dmg 的 source 需要是“包含 .app 的目录”,不能直接传 .app 路径。 - STAGE_DIR=$(mktemp -d "$DIST_DIR/.dmg-stage-${APP_NAME}-${VERSION}-arm64.XXXXXX") + # 创建 DMG + if command -v create-dmg &> /dev/null; then + echo " 📦 正在打包 DMG (arm64)..." + # 移除已存在的 DMG (以防万一) + rm -f "$DIST_DIR/$DMG_NAME" + # create-dmg 的 source 需要是“包含 .app 的目录”,不能直接传 .app 路径。 + STAGE_DIR=$(mktemp -d "$DIST_DIR/.dmg-stage-${APP_NAME}-${VERSION}-arm64.XXXXXX") if [ -z "$STAGE_DIR" ] || [ ! -d "$STAGE_DIR" ]; then echo -e "${RED} ❌ 创建 DMG 临时目录失败,跳过 DMG 打包。${NC}" else @@ -134,8 +192,9 @@ if [ $? -eq 0 ]; then cp -R "$DIST_DIR/$APP_DEST_NAME" "$STAGE_DIR/$APP_DEST_NAME" fi - # --sandbox-safe 会跳过 Finder 的 AppleScript 排版,避免打包过程中弹出/打开挂载窗口(CI/本地静默打包更友好)。 - CREATE_DMG_ARGS=(--volname "${APP_NAME} ${VERSION}" --format UDZO --sandbox-safe) + # 注意:本地验证表明 `--sandbox-safe` 与“目录作为 source”组合会污染 DMG 内 .app 的扩展属性, + # 导致签名校验失败,因此这里显式禁用该参数,优先保证产物可打开。 + CREATE_DMG_ARGS=(--volname "${APP_NAME} ${VERSION}" --format UDZO) if [ -n "$MAC_VOLICON_PATH" ]; then CREATE_DMG_ARGS+=(--volicon "$MAC_VOLICON_PATH") else @@ -179,15 +238,17 @@ if [ $? -eq 0 ]; then fi fi - if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then - hdiutil verify "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 - if [ $? -ne 0 ]; then - echo -e "${RED} ❌ DMG 校验失败,保留 .app 以便排查。${NC}" - else - # 删除中间的 .app 文件,保持目录整洁 - rm -rf "$DIST_DIR/$APP_DEST_NAME" - echo " ✅ 已生成 $DMG_NAME" - fi + if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then + hdiutil verify "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo -e "${RED} ❌ DMG 校验失败,保留 .app 以便排查。${NC}" + elif ! verify_macos_dmg_bundle_signature "$DIST_DIR/$DMG_NAME"; then + echo -e "${RED} ❌ DMG 内应用签名校验失败,保留 .app 与 .dmg 以便排查。${NC}" + else + # 删除中间的 .app 文件,保持目录整洁 + rm -rf "$DIST_DIR/$APP_DEST_NAME" + echo " ✅ 已生成 $DMG_NAME" + fi fi fi @@ -219,11 +280,12 @@ if [ $? -eq 0 ]; then else echo -e "${RED} ❌ 未找到 macOS amd64 主程序文件。${NC}" exit 1 - fi - - # Ad-hoc 代码签名 - echo " 🔏 正在对 .app 进行 ad-hoc 签名 (amd64)..." - codesign --force --deep --sign - "$DIST_DIR/$APP_DEST_NAME" + fi + + # Ad-hoc 代码签名 + echo " 🔏 正在对 .app 进行 ad-hoc 签名 (amd64)..." + clear_macos_bundle_xattrs "$DIST_DIR/$APP_DEST_NAME" + codesign --force --deep --sign - "$DIST_DIR/$APP_DEST_NAME" if command -v create-dmg &> /dev/null; then echo " 📦 正在打包 DMG (amd64)..." @@ -239,8 +301,9 @@ if [ $? -eq 0 ]; then cp -R "$DIST_DIR/$APP_DEST_NAME" "$STAGE_DIR/$APP_DEST_NAME" fi - # --sandbox-safe 会跳过 Finder 的 AppleScript 排版,避免打包过程中弹出/打开挂载窗口(CI/本地静默打包更友好)。 - CREATE_DMG_ARGS=(--volname "${APP_NAME} ${VERSION}" --format UDZO --sandbox-safe) + # 注意:本地验证表明 `--sandbox-safe` 与“目录作为 source”组合会污染 DMG 内 .app 的扩展属性, + # 导致签名校验失败,因此这里显式禁用该参数,优先保证产物可打开。 + CREATE_DMG_ARGS=(--volname "${APP_NAME} ${VERSION}" --format UDZO) if [ -n "$MAC_VOLICON_PATH" ]; then CREATE_DMG_ARGS+=(--volicon "$MAC_VOLICON_PATH") else @@ -282,14 +345,16 @@ if [ $? -eq 0 ]; then fi fi - if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then - hdiutil verify "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 - if [ $? -ne 0 ]; then - echo -e "${RED} ❌ DMG 校验失败,保留 .app 以便排查。${NC}" - else - rm -rf "$DIST_DIR/$APP_DEST_NAME" - echo " ✅ 已生成 $DMG_NAME" - fi + if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then + hdiutil verify "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo -e "${RED} ❌ DMG 校验失败,保留 .app 以便排查。${NC}" + elif ! verify_macos_dmg_bundle_signature "$DIST_DIR/$DMG_NAME"; then + echo -e "${RED} ❌ DMG 内应用签名校验失败,保留 .app 与 .dmg 以便排查。${NC}" + else + rm -rf "$DIST_DIR/$APP_DEST_NAME" + echo " ✅ 已生成 $DMG_NAME" + fi fi fi diff --git a/docs/issues/2026-04-11-issue-backlog-tracking.md b/docs/issues/2026-04-11-issue-backlog-tracking.md deleted file mode 100644 index e540228..0000000 --- a/docs/issues/2026-04-11-issue-backlog-tracking.md +++ /dev/null @@ -1,111 +0,0 @@ -# 2026-04-11 Issue Backlog Tracking - -## Scope - -- 分支:`codex/issue-242-data-root` -- 策略:按 GitHub issue 创建时间从早到晚逐条处理 -- 提交要求:每条 issue 单独本地提交,提交信息使用 `Fixes #` - -## Progress - -| Issue | Title | Status | Commit | -| --- | --- | --- | --- | -| #242 | 希望有自定义数据存储位置功能 | Fixed | `1f617f9` | -| #287 | 建议补充 Sql Server 数据库图标 | Fixed | `60b63d7` | -| #305 | 金仓数据库设计表新增字段保存失败 | Fixed | `f696f52` | -| #306 | 驱动下载 | Fixed | `8297829` | -| #308 | clickhouse 获取数据库列表失败 | Fixed | `5d86ee7` | -| #310 | 选择库后,右侧行显示各个表 | Fixed | `808c773` | -| #311 | WIN 系统的执行 500 多条 insert 语句要几分钟 | Fixed | `83fe3d4` | -| #315 | 窗体内缩放异常 | Fixed | `5038ae5` | -| #316 | 人大金仓数据库驱动版本过低 | Fixed | `aa1bb5b` | -| #317 | 驱动管理增加导入 jar 功能 | Blocked | - | -| #318 | mysql,bit 列,修改成 1 失败 | Fixed | `89d79ff` | -| #319 | 关于运行外部 sql 文件的一些建议 | Deferred | - | -| #320 | 无法连接达梦数据库 | Fixed | `1c2377b` | -| #322 | 【拖选复制】希望添加 查询结果表格可以拖选复制,效果就如操作excel表格的选择复制一样 | Fixed | Pending | -| #325 | 有没有考虑对数据库的驱动版本进行选择或者自定义? | Fixed | `af5e842` | -| #327 | SHOW DATABASES 报错 | Fixed | `fb500ee` | -| #328 | [Bug] 安装更新失败 | Fixed | `426ef3b` | -| #329 | 如果调整了左侧导航栏的宽度后,建议左侧导航栏内增加横向滚动查看 | Fixed | `fcade0f` | -| #330 | 建议在查询结果表格中增加自适应内容列宽的功能 | Fixed | `632e57e` | -| #331 | 重复连接 DB,一分钟重试了 60 多次 | Fixed | `ca76440` | -| #351 | 为什么没有截断和清空表的功能呀? | Fixed | Pending | - -## Notes - -### #317 - -- 当前驱动管理只支持内置 Go 驱动和可选 Go 驱动代理包。 -- 仓库内不存在 JDBC/JAR 装载、Java 运行时探测、classpath 管理或桥接执行链路。 -- 在现有架构下直接增加 “导入 jar” 入口会形成假功能,因此暂记为架构阻塞,不做伪实现。 - -### #318 - -- 根因:MySQL 写入归一化只覆盖时间列,`bit` 列提交时会把前端传来的 `"1"`/`"0"` 原样透传给驱动。 -- 处理:为 MySQL `bit` 列补充写入值归一化,将常见文本/布尔/数值输入转换为驱动可接受的 `[]byte`。 -- 验证:补充 `internal/db/mysql_value_test.go` 回归测试,覆盖 `bit(1)` 的 insert/update 写入路径。 - -### #319 - -- 现有应用已支持“运行外部 SQL 文件”,但 issue 诉求包含目录树、目录加载、双击文件打开等整组工作区能力。 -- 该项已超出单点缺陷修复范围,暂按功能增强项顺延,避免在逐条修 bug 流程中引入大范围 UI/状态管理重构。 - -### #320 - -- 达梦当前走可选 Go 驱动代理安装链路,不支持 JAR 导入属于既有架构边界。 -- 根因:驱动 release 资产缓存把 `GoNavi-DriverAgents.zip` 里的 bundle 条目也混进了“顶层已发布 asset”集合,导致安装链路误以为存在单独的 `dameng-driver-agent-*.exe` 下载地址。 -- 处理:缓存层区分真实 release 顶层 asset 与 bundle index 条目,安装 URL 解析仅在真实顶层 asset 存在时才走直链;bundle-only 驱动改为直接进入总包提取回退,不再先卡在 20% 试无效 URL。 -- 验证:补充 `internal/app/methods_driver_version_test.go` 回归测试,覆盖 bundle-only 达梦驱动跳过伪直链,并回归 Mongo 历史版本与本地导入链路。 - -### #327 - -- 根因:低权限 MySQL 账号执行 `SHOW DATABASES` 会直接报错,当前实现没有回退路径。 -- 处理:为数据库列表查询增加 `SELECT DATABASE()` 回退,仅保留当前连接库时也能正常展示。 -- 验证:补充 `internal/db/mysql_metadata_test.go` 回归测试,覆盖有权限、多库和低权限回退场景。 - -### #328 - -- 根因:Windows 更新脚本在批处理执行、错误码读取和重启命令上不够稳,`cmd /C start`、LF 行尾和块内 `%ERRORLEVEL%` 在实际环境下容易引发安装失败。 -- 处理:更新脚本统一输出为 CRLF,块内错误码改为延迟展开,旧文件回退路径统一为 `TARGET_OLD`,并将脚本启动方式收敛为 `cmd.exe /D /C call