From 16ac7e8895040439707418a200f4227ecb260d72 Mon Sep 17 00:00:00 2001 From: Stuart Meeks Date: Fri, 21 Aug 2026 04:23:44 +0000 Subject: [PATCH] chore: retire TODO.md, migrate remaining backlog to issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All TODO.md items are now resolved or tracked elsewhere: - Flaky secret-store delete test — fixed (#22). - Keystore format versioning + zero-on-dispose — shipped (#23). - 'Decide whether to default the OS-native backends' — decided: keep opt-in while experimental (already reflected in CredentialStoreOptions docs). - Environment/standards-bound items migrated to issues: Keychain ACL hardening (#18), KWallet validation (#19), macOS CI matrix (#20), Linux CI job split (#21), and a repo-wide Path.Combine -> Path.Join sweep (#24). CHANGELOG records the migration and the opt-in decision. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 10 ++++++ TODO.md | 86 ---------------------------------------------------- 2 files changed, 10 insertions(+), 86 deletions(-) delete mode 100644 TODO.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f991e22..1198540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **`TODO.md` retired; its backlog moved to GitHub issues.** Completed items shipped in + this cycle (the flaky secret-store delete test, keystore format versioning, and + zero-on-dispose — see above). Remaining items that need an environment or a + NextIteration.Standards change were migrated to issues: Keychain ACL hardening (#18), + libsecret KWallet validation (#19), macOS CI version matrix (#20), splitting the Linux + keyring CI job (#21), and a repo-wide `Path.Combine` → `Path.Join` sweep (#24). Decision + recorded: the macOS Keychain and Linux libsecret backends stay **opt-in** while marked + experimental (revisit when that tag is dropped), as already documented on + `CredentialStoreOptions.UseKeychain` / `UseKeyring`. + - **`.keystore` files now carry a format header (magic + 1-byte version).** A future KDF/format change can now be detected and rejected with a clear "unsupported keystore format version" error instead of surfacing as an opaque integrity-check failure. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 29b0ed8..0000000 --- a/TODO.md +++ /dev/null @@ -1,86 +0,0 @@ -# TODO - -## Flaky test: KeychainCredentialManagerTests.DeleteCredentialAsync_RemovesCredential - -Observed 2026-08-20 on `macos-latest`, net10.0/arm64: `Assert.True(deleted)` failed -(`DeleteCredentialAsync` returned false) at `KeychainCredentialManagerTests.cs:213`. The -net8.0/arm64 leg of the same job passed, and a re-run of the identical commit passed -cleanly — so it is a race, not a deterministic failure. - -Two things changed shortly before it appeared, both of which alter timing rather than -behaviour: the test project began multi-targeting, so two test processes now run -concurrently against the same login keychain, and coverage instrumentation was enabled. -The app identifier is already a per-instance GUID, so this is not namespace collision -between the two runs — more likely `SecItemDelete` racing `SecItemAdd` visibility under -concurrent keychain access. - -Worth fixing rather than re-running: a flaky security-critical test trains people to ignore -red. Candidate approaches — retry the delete assertion against a short deadline, or have -the manager confirm deletion by re-querying rather than trusting the status code. - -The libsecret tests share the same shape (concurrent runs against one Secret Service) and -have not failed yet, which is not the same as being correct. - -## Credential store backends - -`LocalFileCredentialEncryption` protects credentials with AES-GCM but relies on -filesystem permissions for its real security boundary — the key-encryption key -is derived from non-secret machine/user identifiers. For stronger protection -against a local attacker, the library should offer OS-native secret-store -backends on platforms that have them. - -### ✅ KeychainCredentialManager (macOS) — **experimental, shipped** - -An `ICredentialManager` implementation backed directly by the macOS Keychain. -Each credential is a generic-password keychain item scoped by the consumer's -`KeychainAppIdentifier`. Opt in via `CredentialStoreOptions.UseKeychain = true`. - -Still outstanding: -- Harden with ACL-scoped access (require the running binary to match the - creator, so a neighbouring app can't read another CLI's items even with - the same app identifier). -- Batch-test against multiple macOS versions via CI matrix. -- Decide whether to expose `UseKeychain = true` as the default on macOS - (currently opt-in while experimental). - -### ✅ LibsecretCredentialManager (Linux) — **experimental, shipped** - -An `ICredentialManager` implementation backed directly by libsecret / -the Secret Service API. Each credential becomes a Secret Service item in -the user's default keyring. Opt in via -`CredentialStoreOptions.UseKeyring = true`. - -Still outstanding: -- Validate against KWallet's Secret Service shim (current tests only - exercise GNOME Keyring). -- CI currently starts `gnome-keyring-daemon` inline; consider splitting - the Linux tests into a separate job mirroring the macOS pattern for - cleaner signal. -- Decide whether to expose `UseKeyring = true` as the default on Linux - when a Secret Service is detected (currently opt-in while experimental). - -## LocalFileCredentialEncryption hardening - -### ✅ Optional caller-supplied entropy — **shipped** - -`LocalFileCredentialEncryption` now accepts `byte[]? additionalEntropy` via -its constructor, surfaced on `CredentialStoreOptions.AdditionalEntropy` and -`CredentialEncryptionFactory.Create` / `CreateLocalFile`. When set, the -entropy is concatenated with the machine identity on the password side of -PBKDF2, so the KEK depends on both the machine AND the caller-supplied -secret. An attacker with just the keystore file can no longer decrypt. - -Default behaviour (entropy omitted / null / empty) is bit-identical to -earlier versions — no breaking change for existing consumers. Supplying a -value is a breaking on-disk format change for that keystore specifically; -consumers rotating the entropy need to delete the keystore and re-add -credentials. - -### Future hardening ideas - -- Keystore format versioning (1-byte header magic) so migration across - future KDF changes can produce a clear "unsupported format" error - instead of an integrity-check failure. -- Zero-on-dispose for the in-memory entropy buffer, so a heap dump taken - after the app exits doesn't expose the secret. Currently the GC reclaims - it on object disposal, but without clearing.