When writing credentials or generating user secrets, files are written using a write-to-temp-then-rename pattern. However, neither package called Sync (fsync) on the temporary file before closing and renaming. If a crash or power failure occurred shortly after write, this could result in truncated or 0-byte key/credential files on disk.
Add tmp.Sync() calls to writeNewSecretFile and Store.write to guarantee durability.
* fix(securefile): reclaim stale lock files to prevent permanent DOS
When createSecretFile fails to acquire the O_EXCL lock because it already exists, check if the file is stale (older than 10 seconds). If so, atomically reclaim it via a rename-aside check, preventing a permanent DOS after abrupt process crashes. Update the lock file to write and verify process-specific tokens, and add TestCreateSecretFileReclaimsStaleLock to cover the stale reclaim flow.
* style(securefile): gofmt const block alignment
* fix: address bugs found in a multi-agent codebase audit
Five parallel review agents each swept a different area (self-update,
TUI keybindings, agent/session handling, sandbox/shell safety, and
auth/credential storage) and surfaced nine concrete defects, all fixed
here with regression tests:
- sandbox: grant_scope path comparisons were case-sensitive, letting a
persisted deny grant be bypassed by spelling a Windows path with
different case
- securefile/oauth: createSecretFile's Windows ERROR_ACCESS_DENIED
handling was only applied to the oauth token store, not the shared
credstore backend; both also masked the real lock-contention error
behind a misleading "file does not exist" timeout
- cli: firstUsableProvider ignored OAuth logins when picking a
fallback provider, forcing unnecessary re-onboarding for an
already-authenticated user
- sessions: exec prompt summarization truncated on a raw byte offset,
which could split a multi-byte UTF-8 rune and embed invalid UTF-8
into a resumed session's prompt
- tui: configurable keybindings had no collision detection, so a
remapped chord could silently make a hardcoded shortcut (or another
configured binding) permanently unreachable
- tools: the Windows shell-syntax pre-flight check wasn't quote-aware
and could block harmless commands whose quoted arguments merely
contained operator-like text
- update: checksum verification didn't cross-check the archive
filename it verified against the one being extracted; extractZip
didn't reject symlink-mode entries like extractTarGz already did;
replaceBinary's restore-on-failure rename had no retry, risking a
permanently missing binary on a transient Windows file lock
* fix: close remaining PR 481 review findings
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
* fix(tools): drop stale ; test case after rebasing onto main's MSYS check
Rebasing fix/bug-hunt-findings onto main pulled in #476, which replaced
this file's regex-based windows_shell_syntax detection with a
segment/word-anchored check and deliberately stopped treating ; as a
command separator on Windows (cmd.exe doesn't split on ; the way bash
does). detectShellCommandIssue itself came out of the rebase unchanged
from main (our quote-masking version was fully superseded by #476's
more thorough segment-based approach), but the quoted-content test
this PR added still asserted the old bash-style semantics for
`echo "foo" ; ls -la`, which now correctly returns no issue since it's
a single echo invocation with literal arguments under cmd.exe, not a
real ls invocation.
---------
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Encrypted credential storage
- internal/securefile: AES-256-GCM at-rest crypter (fail-closed, atomic, locked).
- internal/credstore: provider API-key store, keyring-first with an encrypted-file
fallback; plaintext only via an explicit ZERO_CRED_STORAGE=file opt-out.
- Provider API keys no longer live in config.json: captured straight into the store
from the wizard/CLI setup, and any existing inline plaintext key is migrated to the
store on launch (fail-soft; stripped only after the store write succeeds).
- buildProvider and every model/provider switch reload the credential (stored key,
env var, or OAuth bearer), so a stored-key provider always authenticates.
- `zero auth logout <provider>` now also removes the stored API key and its marker.
/model and /provider
- /model lists every authenticated provider (filtered to those with a usable
credential), grouped per provider under section headers.
- Each provider's models are live-discovered (its real served models) rather than a
static catalog; local providers show only the models actually pulled.
- Selecting a model from another provider switches provider and model together.
- /provider offers keep / replace / remove when a provider already has a stored key.
Gated throughout: go build, go vet ./..., go test ./... -race, lint.