The function was using defer to destroy password buffers, which caused
the buffers to be freed before the function returned. This led to a
SIGBUS error when trying to access the destroyed buffer's memory.
Changed to manual memory management to ensure buffers are only destroyed
when no longer needed, and the first buffer is returned directly to the
caller who is responsible for destroying it.
Changed mnemonic input to use secure non-echoing input like passphrases:
- Use secret.ReadPassphrase() instead of readLineFromStdin()
- Add newline after hidden input for better UX
- Remove unused stdin reading functions from cli.go
This prevents sensitive mnemonic phrases from being displayed on screen
during input, matching the security behavior of passphrase input.
- Add build tags to keychain implementation files (Darwin-only)
- Create stub implementations for non-Darwin platforms that panic
- Conditionally show keychain support in help text based on platform
- Platform check in UnlockersAdd prevents keychain usage on non-Darwin
- Verified GPG operations already protected against command injection
via validateGPGKeyID() and proper exec.Command argument passing
- Keychain operations use go-keychain library, no shell commands
The application now builds and runs on Linux/non-Darwin platforms with
keychain functionality properly isolated to macOS only.
- Support 'secret unlockers add pgp [keyid]' positional argument syntax
- Automatically detect and use default GPG key when no key is specified
- Change PGP unlocker ID format from <keyid>-pgp to pgp-<keyid>
- Check if PGP key is already added before creating duplicate unlocker
- Add getDefaultGPGKey() that checks gpgconf first, then falls back to
first secret key
- Export ResolveGPGKeyFingerprint() for use in CLI
- Add checkUnlockerExists() helper to verify unlocker IDs
The new behavior:
- 'secret unlockers add pgp' uses default GPG key
- 'secret unlockers add pgp KEYID' uses specified key
- 'secret unlockers add pgp --keyid=KEYID' also works
- Errors if key is already added or no default key exists
- Replaced exec.Command calls to /usr/bin/security with native keybase/go-keychain API
- Added comprehensive test suite for keychain operations
- Fixed binary data storage in tests using hex encoding
- Updated macse tests to skip with explanation about ADE requirements
- All tests passing with CGO_ENABLED=1
- Fixed gpgDecryptDefault to return *memguard.LockedBuffer instead of []byte
- Updated GPGDecryptFunc signature and all implementations
- Confirmed getSecretValue already returns LockedBuffer (was fixed earlier)
- Improved passphrase string handling by removing intermediate variables
- Note: String conversion for passphrases is unavoidable due to age library API
- All GPG decrypted data is now immediately protected in memory
- Changed DecryptWithIdentity to return *memguard.LockedBuffer instead of []byte
- Updated all callers throughout the codebase to handle LockedBuffer
- This ensures decrypted data is protected in memory immediately after decryption
- Fixed all usages in vault, secret, version, and unlocker implementations
- Removed duplicate buffer creation and unnecessary memory clearing
- Changed Secret.GetValue and Version.GetValue to return *memguard.LockedBuffer
- Updated all internal callers to handle LockedBuffer properly
- For backward compatibility, vault.GetSecret still returns []byte but makes a copy
- This ensures secret values are protected in memory during decryption
- Updated tests to handle LockedBuffer returns
- Fixed CLI getSecretValue to use LockedBuffer throughout
- Changed GPGEncryptFunc signature to accept *memguard.LockedBuffer instead of []byte
- Updated gpgEncryptDefault implementation to use LockedBuffer
- Updated all callers including tests to pass LockedBuffer
- This ensures GPG encryption data is protected in memory
- Fixed linter issue with line length
- Changed storeInKeychain to accept *memguard.LockedBuffer instead of []byte
- Updated caller in CreateKeychainUnlocker to create LockedBuffer before storing
- This ensures keychain data is protected in memory before being stored
- Added proper buffer cleanup with defer Destroy()
- Changed EncryptWithPassphrase to accept *memguard.LockedBuffer instead of []byte
- Updated all callers to pass LockedBuffer:
- CreatePassphraseUnlocker in vault/unlockers.go
- Keychain unlocker in keychainunlocker.go
- Tests in passphrase_test.go
- Removed intermediate dataBuffer creation since data is now already protected
- This ensures sensitive data is protected in memory throughout encryption
- Removed unused deprecated Save(value []byte, force bool) function
- This function accepted unprotected secret data which was a security issue
- All code now uses vault.AddSecret directly with LockedBuffer
- Updated TODO.md to reflect completion of this security fix
- Update all JSON field references in tests from snake_case to camelCase
- Update vault list JSON output to use currentVault instead of current_vault
- Make integration tests quiet by default unless run with -v flag
- Fix tests that were using exec.Command to use in-process execution helpers
- Tests now only show debug output when explicitly requested or on failure
- Fix staticcheck QF1011: Remove explicit type declaration for io.Writer variables
- Fix tagliatelle: Change all JSON tags from snake_case to camelCase
- created_at → createdAt
- keychain_item_name → keychainItemName
- age_public_key → agePublicKey
- age_priv_key_passphrase → agePrivKeyPassphrase
- encrypted_longterm_key → encryptedLongtermKey
- derivation_index → derivationIndex
- public_key_hash → publicKeyHash
- mnemonic_family_hash → mnemonicFamilyHash
- gpg_key_id → gpgKeyId
- Fix lll: Break long function signature line to stay under 120 character limit
All linter issues have been resolved. The codebase now passes all linter checks.
Add blank lines before return statements in all files to satisfy
the nlreturn linter. This improves code readability by providing
visual separation before return statements.
Changes made across 24 files:
- internal/cli/*.go
- internal/secret/*.go
- internal/vault/*.go
- pkg/agehd/agehd.go
- pkg/bip85/bip85.go
All 143 nlreturn issues have been resolved.
- Extract getLongTermPrivateKey helper function to reduce nesting in keychainunlocker.go and pgpunlocker.go
- Add getPassphrase helper method to reduce nesting in passphraseunlocker.go
- Refactor version serial extraction to use early returns in version.go
- Extract resolveRelativeSymlink and tryResolveOsSymlink helpers in management.go
- Add processMnemonicForVault helper to reduce nesting in vault creation
- Extract resolveUnlockerDirectory and readUnlockerPathFromFile helpers in unlockers.go
- Add findUnlockerByID helper to reduce duplicate code in RemoveUnlocker and SelectUnlocker
All tests pass after refactoring.
- Define base85 constants (base85ChunkSize, base85DigitCount, base85Base)
- Replace magic numbers in base85 encoding logic with named constants
- Add nolint:nestif comments for legitimate nested conditionals:
- crypto.go: Clear separation between secret generation vs retrieval
- secrets.go: Separate JSON and table output formatting logic
- vault.go: Separate JSON and text output formatting logic
- Add nolint comments for BIP85 standard constants (MainNetPrivateKey, TestNetPrivateKey)
- Handle error return from shake.Write() in NewBIP85DRNG
- Fix line length issue by moving nolint comment to separate line
- Add nolint comment for cobra.ExactArgs(2) magic number
- Replace magic number 32 with named constant x25519KeySize in agehd package
Add nolint:gochecknoglobals comments for legitimate global variables:
- debugEnabled and debugLogger: Package-wide debug state management
- GPGEncryptFunc and GPGDecryptFunc: Required for test mocking
- getCurrentVaultFunc: Required to break import cycle between packages
Fixed the first five errcheck linter errors:
- Added error handling for os.Unsetenv in cli_test.go
- Added error handling for file.Close() in crypto.go (4 instances)
- Rename VaultMetadata to Metadata in internal/vault package to avoid stuttering
- Rename BIP85DRNG to DRNG in pkg/bip85 package to avoid stuttering
- Update all references in code and tests
- Fix G115 integer overflow by converting uint32 to int comparison
- Remove unnecessary int() conversions for syscall constants
- syscall.Stdin/Stderr/Stdout are already int type
- Add blank lines before return statements
- Use require.Error instead of assert.Error for error assertions
- Keep exact float64 comparisons as-is (they are integers from JSON)
Replace hardcoded 0o600 with secret.FilePerms constant for consistency.
Add explanatory comments for cryptographic constants (32-byte keys,
bech32 encoding parameters) rather than extracting them as they are
well-known cryptographic standard values.
Replace all os.Setenv calls with t.Setenv in test functions to ensure
proper test environment cleanup and better test isolation. This leaves
only legitimate application code and helper functions using os.Setenv.
Break long lines in function signatures and strings to comply with
77 character preference by using multi-line formatting and extracting
variables where appropriate.
Convert traditional for loops to use the new Go 1.22+ integer range syntax:
- for i := 0; i < n; i++ → for i := range n (when index is used)
- for i := 0; i < n; i++ → for range n (when index is not used)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove redundant string() conversions on output variables that are
already strings in test assertions and logging.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Change WriteFile permissions from 0o644 to 0o600 to address security
linting issue about file permissions being too permissive.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>