Fix command wrapper symlink clobber#184
Open
LiranCohen wants to merge 9 commits into
Open
Conversation
Merged
…mpts (Phase 1) (#187) * feat(auth): durable vault-unlock secret to stop repeated password prompts Resolve the vault password through one path (explicit -> GITD_PASSWORD -> public-reader -> durable secret store -> prompt) and persist a freshly entered secret per profile in a durable store: the OS keychain (macOS security, Linux secret-tool) when available, otherwise a machine-keyed AES-256-GCM encrypted file under ~/.enbox/secrets/. After the first unlock, commands restore the session without re-prompting for the vault password. GITD_SECRET_BACKEND=keychain|file|none pins or disables the backend. A cached secret that is later rejected is cleared automatically to avoid lock-out; gitd auth logout / reset drop the stored secret. Part of #186 (Phase 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style: fix import order in vault-password.spec (sort-imports) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…188) * feat(auth): durable vault-unlock secret to stop repeated password prompts Resolve the vault password through one path (explicit -> GITD_PASSWORD -> public-reader -> durable secret store -> prompt) and persist a freshly entered secret per profile in a durable store: the OS keychain (macOS security, Linux secret-tool) when available, otherwise a machine-keyed AES-256-GCM encrypted file under ~/.enbox/secrets/. After the first unlock, commands restore the session without re-prompting for the vault password. GITD_SECRET_BACKEND=keychain|file|none pins or disables the backend. A cached secret that is later rejected is cleared automatically to avoid lock-out; gitd auth logout / reset drop the stored secret. Part of #186 (Phase 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style: fix import order in vault-password.spec (sort-imports) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(auth): make git push/fetch work without a running helper getVaultPassword() now consults the durable secret store (OS keychain / encrypted file) between the public-reader secret and the /dev/tty prompt, so the credential helper's direct-signing fallback and the daemon auto-start are non-interactive after the first unlock. git push / git fetch succeed even when no local helper is running, and a dead or never-started helper no longer blocks pushes. Lockfiles are written atomically (temp + rename) to avoid a corrupt lockfile on a crash mid-write. Part of #186 (Phase 1b). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OS keychain is machine-global (service `gitd`, account `vault:<profile>`) and is NOT scoped to ENBOX_HOME, so a secret cached by one test leaked into later tests that expect "no password" — making resolveGitEndpoint auto-start / resolve a daemon instead of rejecting (CI showed it resolving to port 9418). When ENBOX_HOME is overridden (tests, sandboxes, throwaway installs) and no backend is explicitly pinned, use the encrypted file backend, which lives under ENBOX_HOME and stays isolated. The default home (production) still uses the keychain; GITD_SECRET_BACKEND overrides everything. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The resolveGitEndpoint reject / skip-local-daemon tests read the shared ~/.enbox daemon lockfile and depended on daemon auto-start failing. In CI, concurrent test files (e.g. daemon-lifecycle.spec.ts) race on that lockfile and auto-start behaves differently, so these tests resolved instead of rejecting. - Isolate ENBOX_HOME per test so no leaked lockfile is read. - Add a __setDaemonStarterForTests seam in resolve.ts (defaults to ensureDaemon — no production behavior change) and disable auto-start in the no-daemon tests so they reject deterministically regardless of port/daemon/TTY state. Verified locally and under setsid (no controlling terminal, like CI). Part of #186. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
install.shwrappers, so stale symlinks cannot redirect shell output into package filesgitd setupinstall executable wrappers instead of symlinks to package internalsRoot cause
If
~/.gitd/bin/git-remote-didalready existed as a symlink from a previousgitd setup, the installer's shell redirection followed that symlink and overwrotedist/esm/git-remote/main.jswith wrapper text. Git then invoked the helper and Bun tried to parseexec ...as JavaScript.Verification
bash -n install.shgit diff --checkbun run buildbun run lintHOME=$(mktemp -d)/home ENBOX_HOME=$(mktemp -d)/enbox bun test tests/cli.spec.ts -t setup~/.gitd/bin/git-remote-didas a symlink, ran installer, confirmed symlink target remained unchanged and wrapper replaced the symlinkbun dist/esm/cli/main.js setup --bin-dir <tmp>thensetup --check