Conversation
Automates extracting the per-site self-signed cert and key from the Lima VM and trusting the cert on the host (macOS login keychain + Firefox NSS on macOS and Linux). Replaces the manual 6-step ritual previously documented for Lima users. Adds `vm trust`, `vm untrust`, and `vm trust paths` subcommands. Trust state is recorded in ~/.local/share/trellis/state/trusted_certs.json with a process-level flock so concurrent runs serialize. Verify checks the live trust setting (macOS verify-cert, Linux system bundle, NSS fingerprint) so drift triggers a re-trust instead of silently passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compute macOS specific one within macOS specific code and only expose a single `Fingerprint` field.
Lift the read-cert / fingerprint / decide-skip-or-retrust / untrust-old / trust-new / record-state sequence out of cmd/vm_trust.go into trust.ApplySite, plus the symmetric trust.RevokeSite for vm_untrust.go. Path/label helpers (ProjectID, Label, ExportDir) move alongside since the package owns the on-disk layout. The cmd files now hold only CLI concerns: flag parsing, site selection, VM cert fetching, and UI formatting. vm_trust.go Run drops from ~230 to ~100 lines; vm_untrust.go from ~135 to ~75. Trust logic, state mutation, and path conventions are now testable without spawning security/certutil from a cmd test. Drive-by: the private key is now written via a temp-file + rename helper, closing a brief window where an existing key file at 0o644 would hold the new private bytes before Chmod ran. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The lock guarded a vanishingly rare race: two concurrent `trellis vm trust` runs against the same state file. State.Save already does temp-write + atomic rename, so concurrent saves can't corrupt the file — the realistic hazard is a lost update across a load/modify/save window, which requires the user to run the command twice in parallel by hand. The Linux user CA bundle rebuild has the same shape. If lost updates ever show up in practice the right fix is a short-lived flock inside State.Save, not a multi-second lock around the whole trust loop (which on Linux can include blocking sudo prompts). Removing it deletes lock_unix.go, the windows no-op stub, and the AcquireLock calls in both cmd files. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers the deterministic part of the package: - paths_test: ProjectID stable + distinct, Label format, ExportDir separates forks of the same instance name - format_test: FormatLocation across macOS / Linux / NSS / unknown - store_test: VerifyResult.AllAccounted (Missing breaks, Unknown counts, lengths must match) - linux_test: safeFilename (dot/dash/underscore preserved, slashes and control chars and unicode replaced) - apply_test: ApplySite via a recordingStore Store fake — fresh trust, fingerprint-match skip, drift re-trust, fingerprint-changed re-trust, untrust-error state-preservation, trust-error partial state, empty-cert early exit, key file mode 0o600, key file removal when KeyPEM is empty, CertPath refresh on skip; RevokeSite success + error; writeFileAtomic mode invariant on existing 0o644 file Coverage for pkg/trust goes from ~7% to ~27%, but the relevant number is that ApplySite is now at 87% and RevokeSite at 100%. Remaining 0% is in macos/linux/nss.go where exec-binding makes them Tier 4 work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers the platform store layer's pure-FS and pure-string parts. Uses t.Setenv(\"XDG_DATA_HOME\") and t.Setenv(\"HOME\") to point app_paths and Firefox profile lookup at tempdirs — no production refactor needed since both already respect those env vars. - state_test: Load on missing/empty/corrupt JSON, Save+Load round-trip preserves all fields including AddedAt, Save leaves no temp files behind from the atomic-rename path - linux_test: rebuildBundle concatenates user CAs in dir order, injects trailing newlines so adjacent PEM blocks parse, removes the bundle when the dir is empty; pemFileContainsFingerprint scans all PEM blocks; fileFingerprintMatches handles match/mismatch/missing-file - nss_test: isNSSNicknameNotFound covers the certutil error variants; isNSSProfile detects cert9.db (modern) and cert8.db (legacy); firefoxProfileDirs walks the platform-appropriate parent - macos_test: isKeychainNotFound covers the security CLI error variants; sha1HexFromCertFile matches crypto/sha1 over DER and returns empty on missing/invalid input Coverage for pkg/trust now 47.6% (was ~27% after Tier 2, ~7% baseline). The remaining 0% surface is exec-bound: linuxStore.Trust/Untrust/ Verify, macOSStore.Trust/Untrust/Verify, nssTrust/Untrust/Verify, keychainTrustsCertForSSL, keychainHasFingerprint. Those are Tier 4 work that needs an exec runner shim. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a runner interface (Run, RunStdin, Lookup) and a default execRunner wrapping os/exec. Each platform store and a new nssHelper take a runner so tests can inject a recording fake instead of spawning real binaries. The fake (runner_test.go) registers single-shot canned responses keyed by program name + an args prefix and records every call so tests can assert on argument shape and call sequence. Tests cover the previously-zero-coverage branches: - macOS: Trust calls add-trusted-cert with -p ssl / -r trustRoot; Untrust deletes when find-certificate confirms presence; skips delete when fingerprint is already gone; treats "could not be found" as success; surfaces other delete errors; Verify maps verify-cert outcomes to Present / Missing (not-trusted) / Unknown - Linux: Trust writes user CA + bundle without sudo; --trust-system invokes sudo tee + sudo update-ca-certificates; system location is recorded even when update-ca-certificates fails so untrust can clean up; Untrust removes user CA file; system Untrust runs sudo rm -f and update-ca-certificates --fresh; Verify requires both file and bundle - NSS: disabled is a no-op; reports CertutilMissing without erroring; reports FirefoxFound=false when no profiles exist; happy path runs -D then -A in order; -A failure surfaces; Untrust treats nickname- not-found as success; certutil-missing-but-records-exist returns error; Verify classifies present / missing / unknown Drive-by fix: rebuildBundle would error when called with the user CA dir absent and no bundle present (os.Remove on missing file). Now guarded with os.IsNotExist so untrust paths that bypass the user CA location don't panic on a fresh install. Coverage for pkg/trust now 81.2%, up from 47.6%. Remaining 0% is execRunner itself (the prod-only os/exec wrapper) and Default() (GOOS dispatcher). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
swalkinshaw
approved these changes
May 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the manual 6-step ritual for trusting the Lima VM's self-signed cert with a single
trellis vm trustcommand. Pulls the cert and key out of the VM, exports them to~/.local/share/trellis/ssl/<vm>-<hash>/, and trusts the cert in the macOS login keychain and every Firefox profile so browsers and host-side tooling stop showing warnings.Commands
trellis vm trust— trust eachssl.enabled: true && provider: self-signedsite. Flags:--site,--no-export-key,--trust-system(Linux only).trellis vm untrust— reverse trust entries this project added. Flag:--site.trellis vm trust paths— print the host paths of the exported cert + key per site.Behavior
already trusted (fingerprint match, skipped)and does nothing. If something has drifted (manual deletion, regenerated cert, etc.), it re-applies trust and tells you why.vm trustadded, leaving manually trusted certs in your keychain alone. Failures preserve state so a retry is safe.certutilis on PATH (frombrew install nss/apt install libnss3-tools); if it's missing the command prints a one-time install hint instead of silently skipping.example.com) don't collide — entries are scoped per project.Example output
Follow up
Update the docs with the new commands after this PR lands in a new release
🤖 Generated with Claude Code