Skip to content

v11.1.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 17:22
· 25 commits to master since this release
2bd0e32

Release Notes (APM v11.1.0)

This release of APM focused on encryption bug fixes and making the overall user experience much better with less bugs.

Zero nonce for recovery AES-GCM (Critical)

File: src/vault.go — SetRecoveryKey(), CheckRecoveryKey()
Problem: AES-GCM nonce was initialized as a zero-filled byte slice, enabling nonce reuse attacks that could expose encrypted DEK slots.
Fix: Generate random nonce via io.ReadFull(rand.Reader, nonce), prepend to ciphertext. Legacy zero-nonce format preserved for backward compatibility.

Silent error discards and nil panic in SetRecoveryKey (Critical)

File: src/vault.go — SetRecoveryKey()
Problem: aes.NewCipher and cipher.NewGCM errors were silently discarded. An invalid key would cause a nil pointer dereference (panic).
Fix: All errors from aes.NewCipher, cipher.NewGCM, and io.ReadFull are now checked and returned. SetRecoveryKey now returns an error instead of void.

Silent KDF fallback on AES-GCM error (Critical)

File: src/vault.go — DecryptData()
Problem: All AES-GCM errors were silently dropped by nested if err == nil blocks. Corrupted data could produce garbage instead of an error.
Fix: Extracted a tryDecryptAESGCM() helper that properly propagates errors. Tries the standard Argon2id KDF first, then the legacy KDF. Returns a clear error if both fail.

Recovery key uses single SHA-256 instead of KDF (Medium)

File: src/vault.go — DeriveRecoveryKey()
Problem: Single SHA-256 provides negligible brute-force resistance for user-written recovery keys.
Fix: Now uses Argon2id (time=2, mem=64MB, threads=2). Old SHA-256 preserved as deriveRecoveryKeyLegacy() for backward compatibility.

History HMAC signatures use vault salt (public) as signing key (Critical)

File: src/vault.go — logHistory(), VerifyHistoryEntrySignature()
Problem: Vault salt is stored in plaintext in the vault header. Using it as the HMAC key means history signatures provide zero forgery protection.
Fix: Added an AuthKey field to the Vault struct (never serialized, json:"-"). Derived from Argon2id during decryption with make+copy to avoid defer Wipe() aliasing. logHistory() signs with AuthKey, falling back to Salt for legacy vaults. VerifyHistoryEntrySignature() tries AuthKey first, then Salt for backward compatibility.

MCP get_entry silently drops errors (Medium)

File: src/mcp.go — get_entry handler
Problem: rand.Read, encryptEpisodic, and writeToTemp errors were silently discarded. If rand.Read failed, the ephemeral key would be all zeros.
Fix: All three errors are now checked and returned as CallToolResult errors.

DecryptVaultWithDEK missing profile params (Medium)

File: src/vault.go — DecryptVaultWithDEK()
Problem: Vaults opened via the recovery key path lost crypto profile context, potentially causing re-encryption to use the wrong parameters.
Fix: Added vault.CurrentProfileParams = &profile after unmarshalling.

ImportFromJSON ignores wrong password (Info)

File: src/portability.go — ImportFromJSON()
Problem: A wrong password was silently swallowed. The function then tried to parse the encrypted blob as plaintext JSON, producing a confusing error.
Fix: Added a json.Valid(bytes) check. If the data isn't valid JSON and decryption fails, the decryption error is returned immediately.

MCP temp files never cleaned up (Info)

File: src/mcp.go — writeToTemp(), decrypt_entry
Problem: Encrypted vault entry data was written to temp files that only get deleted when decrypt_entry is called. If never called, files linger on disk.
Fix: Added a goroutine that removes the temp file after 5 minutes as a safety net.

zeroString() does not actually zero memory (Critical)

File: src/autofill/daemon.go
Problem: zeroString() allocated a new backing array, copied the string bytes, and zeroed the copy. The original Go string data remained in heap memory.
Fix: Uses unsafe.StringData and unsafe.Slice to get a mutable pointer to the string's actual backing array and zero it in place.

wipeVaultSecrets() misses most secret types (Critical)

File: src/autofill/daemon.go
Problem: Only cleared 4 of ~20 secret-carrying entry types. Left uncleared: SSH keys, banking CVV/details, documents, certificates, WiFi passwords, medical records, travel docs, cloud credentials, CI/CD secrets, software licenses, recovery codes, contacts, K8s secrets, docker registries, SSH configs, and audio/video/photo content.
Fix: Now clears all secret-containing fields across every entry type.

MCP args parse error silently swallowed (Medium)

File: src/mcp.go — ensureMCPMutationAuthorized()
Problem: json.Unmarshal error was silently discarded with an underscore assignment. Malformed arguments were silently treated as a new transaction.
Fix: The error from json.Unmarshal is now returned to the caller.

os.MkdirAll errors silently ignored (Medium)

Files: src/mcp.go, src/mcp_transactions.go, src/ephemeral_session.go
Problem: Config directory creation errors were silently discarded. If the directory couldn't be created, subsequent file writes would fail with confusing errors.
Fix: Errors are now captured and documented instead of silently discarded.

cleanupCmd leaks orphan shell processes (Medium)

Files: src/session.go, team/session.go
Problem: Background shell commands (sh -c "sleep N && rm -f ...") were never managed or killed. If APM crashes, orphaned sleep processes survive for the full session duration.
Fix: Removed the shell-based cleanup entirely. The existing Go goroutine in CreateSession already handles cleanup natively.

Corrupted session key causes silent rekey (Info)

File: src/session.go — getSessionKey()
Problem: If the stored session key file was corrupted (invalid base64 or wrong length), a new key was silently generated, making all existing sessions undecryptable.
Fix: Now returns an error ("session key file <path> contains invalid key data") instead of silently regenerating.

Mail feature removed (Cleanup)

Files: src/autofill/mail.go (deleted), src/autofill/types.go, src/autofill/daemon.go, src/autofill/paths.go, src/autofill/system_intelligent.go, src/autofill/system_engine.go, src/autofill/system_engine_windows.go, src/autofill/system_engine_stub.go, main.go, src/autofillcmd/commands.go, src/autofillcmd/autostart_stub.go, src/autofillcmd/autostart_windows.go

The Gmail OTP autocomplete feature (mail.go) has been fully removed:

  • Deleted mail.go (SetupGmail, MailConfigured, DisconnectGmail, mailCache, etc.)
  • Removed MailOnly and MailHotkey from RequestContext in types.go
  • Removed mailHotkey from Daemon.RunOptions and the Daemon struct
  • Removed mailConfigFilePath from paths.go
  • Updated SystemEngine.Start() to a single hotkey signature (removed the mail Hotkey parameter)
  • Removed the fieldIntentMailOTP constant, replaced all references with fieldIntentTOTP
  • Removed TriggerMailOTPFillForActiveWindow from commands.go
  • Removed --mail-hotkey from autostart systemd units, launchd plists, and Windows Registry commands
  • Removed defaultMailHotkey() and the mailHotkey parameter from EnableAutofillAutostart
  • Removed buildAutocompleteMailCommand() from main.go

Autocomplete command tree removed (Cleanup)

File: main.go

The entire apm autocomplete command tree has been removed to eliminate confusion between autofill and autocomplete:

  • Removed autocompleteCmd (enable/disable/start/stop/status/window/link-totp)
  • Removed setAutocompletePopupDisabled()
  • Removed buildAutocompleteFillCommand() wrapper function
  • rootCmd.AddCommand now only registers the autofill command

Autofill system removed entirely (Cleanup)

The entire autofill system has been removed from the project:

  • Deleted src/autofill/ and src/autofillcmd/ directories (all daemon, popup, system engine, matching, profiles, client, commands, and autostart code)
  • Removed autofillcmd import and all call sites from main.go (UnlockDaemonWithPassword, LockDaemonIfRunning, NewAutofillAndVaultCommands)
  • Removed rootCmd.AddCommand(autofillCmd) registration
  • Removed docs/autofill_windows.md
  • Updated README, docs/index.md, docs/features.md, docs/reference/cli.md to remove autofill references

Encryption Bug Fixes (Part II)

Fix #1: Zero nonce for master slot AES-GCM (Critical)

File: src/vault.go — EncryptVault(), decryptNewVault()
Problem: The master slot (DEK encrypted under the master-derived key) used a zero nonce via mNonce := make([]byte, masterSlotAEAD.NonceSize()) with no random fill. Every vault save with the same master password produced an identical encrypted DEK slot, breaking AES-GCM's security guarantees.
Fix: Derived the master slot nonce from SHA-256(salt). Since GenerateSalt creates a new random salt on every save, each save gets a unique nonce. No format change required. On decrypt, the search loop tries the salt-derived nonce first, then falls back to the zero nonce for vaults created before the fix.

Fix #2: Wipe() may be compiler-optimized away (Medium)

File: src/utils.go — Wipe()
Problem: Go's dead-code elimination can legally elide the zeroing loop since the slice is only written to and never read afterwards.
Fix: Added runtime.KeepAlive(b) after the loop, which forces the compiler to treat b as still live at that point, preventing optimization.

Fix #3: EncryptData/DecryptData missing Encrypt-then-MAC (Medium)

File: src/vault.go — EncryptData(), DecryptData()
Problem: Standalone encrypt/decrypt functions lacked the Encrypt-then-MAC defense-in-depth layer that the main vault format has.
Fix: EncryptData now appends CalculateHMAC(payload, AuthKey). DecryptData tries the HMAC-verified format first, falling back to the legacy no-HMAC format for backward compatibility.

Fix #4: Recovery key hash uses raw SHA-256 (Low)

File: src/vault.go — SetRecoveryKey(), tryDeriveRecoveryKey()
Problem: Recovery key hash was a raw SHA-256 of the derived key, susceptible to length-extension and rainbow-table attacks.
Fix: SetRecoveryKey now uses hmac.New(sha256.New, salt). tryDeriveRecoveryKey tries HMAC-SHA256 first, falling back to raw SHA-256 for vaults created before the fix.