From 9089f313f849584b4ec777b6095c0a3da60eef1d Mon Sep 17 00:00:00 2001 From: "Adrian Alfred C. Bonpin" Date: Thu, 24 Sep 2026 03:43:46 +0800 Subject: [PATCH] fix(keychain) + release: macOS keychain ACL self-repair, bump 0.8.1 (#37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(keychain): repair stale macOS keychain ACLs before reads macOS records the trusted application of a legacy keychain item as the executable *path* for an ad-hoc (dev/linker-signed) build, and as a code requirement + cdhash partition list for a signed bundle. When the path changes — the monorepo move (`src-tauri/` -> `desktop/src-tauri/`), a reinstalled app, running the installed app after a dev build — the ACL no longer admits the running binary, so macOS demands the keychain password on *every* access: once per secret, every time, with no "Always Allow". The pin only ran after writes, and writing to an inaccessible item needs the same authorization, so the ACL was never repaired. - keychain_acl::is_item_trusted() answers "am I already in this item's ACL?" with a metadata-only, prompt-free lookup (SecKeychainItemCopyAccess + SecAccessCopyACLList + comparison against this process's own SecTrustedApplication representation; a signed bundle's `cdhash:` partition lists count as trusted and are left alone). - ensure_item_acl() now no-ops when already trusted (no duplicate ACL entries, no redundant authorization) and otherwise pins exactly as before. - New before_read() hook, called from every keychain read path (DB password, SSH password, SSH passphrase — commands and internal helpers), so a stale item costs one authorization and is silent from then on. - ItemSearchOptions reports "nothing matched" as errSecItemNotFound, which is now handled as "no item" instead of a lookup error — previously every connection without a stored password logged a spurious failure. Verified: cargo test --lib (408 passed) plus the ignored live test (`cargo test --lib -- --ignored keychain_acl`), which asserts a fresh item is already trusted, repairs are idempotent and the secret still reads back. That test now finishes in 0.25s instead of ~8s because the redundant SecKeychainItemSetAccess is gone. * chore(release): bump 0.8.1 and document GitHub-only releases Version 0.8.0 -> 0.8.1 across every synced location: desktop/package.json, desktop/src-tauri/Cargo.toml (+ Cargo.lock gridline entry), tauri.conf.json, www/src/pages/index.astro, www/public/landing.js, both README download tables (+ the MAINTENANCE note), and the version/docs-coverage/bundle-config tests. Adds the 0.8.1 changelog entry (site + README Recent Changes). AGENTS.md: releases are GitHub-only — tag `prod` and `git push github vN.M.N` (origin/Gitea is a just-in-case mirror; never push branches, PRs or tags there), and the keychain row documents the self-healing ACL repair. --- AGENTS.md | 10 +- README.md | 32 +- desktop/package.json | 2 +- desktop/src-tauri/Cargo.lock | 2 +- desktop/src-tauri/Cargo.toml | 2 +- desktop/src-tauri/src/commands/keychain.rs | 7 + desktop/src-tauri/src/keychain_acl.rs | 418 +++++++++++++++------ desktop/src-tauri/tauri.conf.json | 2 +- desktop/src/lib/bundle-config.test.ts | 6 +- desktop/src/lib/docs-coverage.test.ts | 18 +- desktop/src/lib/version.test.ts | 4 +- www/public/landing.js | 2 +- www/src/data/changelog.ts | 9 + www/src/pages/index.astro | 2 +- 14 files changed, 367 insertions(+), 149 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0601c3a..ad3728b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,17 +167,19 @@ cargo test # Rust tests (from desktop/sr ### Branch & PR workflow +**GitHub is the only remote that matters.** The canonical repo is `github` → [AdrianBonpin/gridline](https://github.com/AdrianBonpin/gridline) (default branch `prod`, issues, PRs, Actions, releases). The `origin` remote (`git.ranio.xyz/adrianbonpin/gridline`) is a **just-in-case mirror** — do not push branches, open PRs, or publish releases/tags there. If you must mirror something manually, push to `github` first. + **Never commit or push directly to `prod`** — `prod` is protected (branch rules require a pull request). Always: 1. Branch out first — `git checkout -b feat/` (or `fix/…`, `docs/…`) from `prod` -2. Commit and push the feature branch -3. Open a PR into `prod` and merge it there +2. Commit and push the feature branch to `github` (`git push -u github `) +3. Open a PR into `prod` on GitHub (`gh pr create --repo AdrianBonpin/gridline --base prod`) and merge it there Direct pushes to `prod` will be bypassed only in emergencies; prefer the PR path so changes land through the PR gate. ### Releases -Cut a release by tagging the **`prod`** branch once the PR is merged — `git tag vN.M.N && git push origin vN.M.N`. GitHub Actions (`release.yml`) builds installers for macOS (Apple Silicon + Intel), Windows, and Linux and opens a **draft** release (review + publish on GitHub). +**Releases are GitHub-only.** Tag `prod` and push the tag to the `github` remote — `git tag vN.M.N && git push github vN.M.N`. GitHub Actions (`release.yml`) builds installers for macOS (Apple Silicon + Intel), Windows, and Linux and opens a **draft** release (review + publish on GitHub). Never push release tags to `origin`/Gitea; it exists only as a fallback copy of the code. **Before tagging**, keep everything in sync: - Version number across `desktop/package.json`, `desktop/src-tauri/Cargo.toml`, `desktop/src-tauri/tauri.conf.json`, and `www/src/pages/index.astro` (the landing page version string) @@ -245,7 +247,7 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr | DB Viewer: SQLite browse + query | ✅ | Full support via rusqlite | | DB Viewer: MySQL browse + query + edit | ✅ | Full viewer: connect (SSL + SSH tunnel), databases/tables/columns/FKs, query + pagination, inline cell editing + changes queue, DDL copy (`SHOW CREATE TABLE`), CSV/JSON import — added in v0.7.0. PK-only editing (no ctid equivalent); VARBINARY `information_schema` columns decoded correctly | | DB Viewer: Redis browse | ❌ | Connection + test only; browsing gated off with a clean "not supported" state (v0.7.0) | -| Password storage in OS keychain | ✅ | macOS Keychain, Linux Secret Service, Windows Credential Manager; macOS items get an explicit `SecAccess` ACL pinning them to the app's code-signature requirement on every save (`keychain_acl.rs`) — items survive re-signs/upgrades without keychain permission prompts | +| Password storage in OS keychain | ✅ | macOS Keychain, Linux Secret Service, Windows Credential Manager. macOS items get an explicit `SecAccess` ACL pinning them to the app's code-signature requirement on every save (`keychain_acl.rs`) — items survive re-signs/upgrades without keychain permission prompts. **Self-healing ACLs**: the ACL is also (silently) verified *before* reads and repaired when this app isn't in it, so items pinned to an older binary path (pre-monorepo `src-tauri/`, a reinstalled app, an ad-hoc dev build) cost one authorization instead of asking for the keychain password on every access, per secret | | Enable keychain toggle | ✅ | Default ON (opt-out); OFF = don't persist the DB password (session-only, re-prompt on connect) + purge existing keychain entry; SSH secrets stay keychain-only (v0.7.6) | | SSH tunnel config UI | ✅ | Host, port, user, auth method, key path, passphrase fields | | SSH tunnel runtime | ✅ | Real ssh2 tunnel (password + key auth), binds 127.0.0.1 only, secrets in OS keychain (`ssh_password:` / `ssh_passphrase:`), closed on pool eviction / app exit; TLS downgraded to `require` through the tunnel | diff --git a/README.md b/README.md index 8082485..d2fe4f2 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,16 @@ ## Download -Grab the installer for your OS from the [latest release](https://github.com/AdrianBonpin/gridline/releases/latest) — the links below point at the current release (**v0.8.0**): +Grab the installer for your OS from the [latest release](https://github.com/AdrianBonpin/gridline/releases/latest) — the links below point at the current release (**v0.8.1**): | OS | Architecture | Download | | :--------------------------- | :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | -| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.8.0_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.0/Gridline_0.8.0_aarch64.dmg) | -| **macOS** | Intel | [Gridline_0.8.0_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.0/Gridline_0.8.0_x64.dmg) | -| **Windows** | x64 | [Gridline_0.8.0_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.0/Gridline_0.8.0_x64-setup.exe) | -| **Debian / Ubuntu** | amd64 | [Gridline_0.8.0_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.0/Gridline_0.8.0_amd64.deb) | -| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.8.0-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.0/Gridline-0.8.0-1.x86_64.rpm) | -| **Other Linux** | amd64 | [Gridline_0.8.0_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.0/Gridline_0.8.0_amd64.AppImage) | +| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.8.1_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.1/Gridline_0.8.1_aarch64.dmg) | +| **macOS** | Intel | [Gridline_0.8.1_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.1/Gridline_0.8.1_x64.dmg) | +| **Windows** | x64 | [Gridline_0.8.1_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.1/Gridline_0.8.1_x64-setup.exe) | +| **Debian / Ubuntu** | amd64 | [Gridline_0.8.1_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.1/Gridline_0.8.1_amd64.deb) | +| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.8.1-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.1/Gridline-0.8.1-1.x86_64.rpm) | +| **Other Linux** | amd64 | [Gridline_0.8.1_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.8.1/Gridline_0.8.1_amd64.AppImage) | **macOS via Homebrew** (recommended for Mac users): @@ -57,7 +57,7 @@ The cask installs the app directly. The DMGs are Developer-ID signed and notariz > **macOS first launch:** Gridline is Developer-ID signed and notarized, so macOS opens it without a Gatekeeper prompt. Not sure if your Mac is Intel or Apple Silicon? See [Which file should I download?](#which-file-should-i-download) below.