Fixed
- Markup injection in
accountscommand output — provider names, account names, environments, summaryDisplayFields, exception messages, and the--providerflag echoed in the "Unknown provider" error now flow throughMarkup.Escapebefore reachingMarkupLine/Table.AddRow/ Spectre selection prompts. A credential whose name happened to contain[or]previously corrupted theaccounts listtable rendering, and exception messages with markup-looking content (common onJsonException/ IO errors) garbled the error line inCommandErrorReporter. accounts delete <short-id>crash —accounts delete abc(or any non-GUID id shorter than 8 chars) previously threwArgumentOutOfRangeExceptionon theaccountId[..8]slice in the confirm prompt before reaching "not found". A newCommandFormatting.ShortIdhelper safely abbreviates, andDeleteCredentialAsync/SelectCredentialAsync/GetCredentialByIdAsyncnow validate thataccountIdis a GUID before any filesystem call. Non-GUIDs resolve to deterministicfalseon the mutating APIs andArgumentExceptionon the strictGetCredentialByIdAsync. Closes a path-traversal/glob-injection vector where*or..\..\foocould have flowed intoPath.CombineandDirectory.GetFilespatterns.- Concurrent
accounts selectlost updates —selections.jsonwas read-modify-written without serialization, so two CLI invocations modifying different providers could lose one provider's update. Reads/writes now happen under a cross-process advisory lock backed byselections.json.lock(FileShare.None+FileOptions.DeleteOnClose+ exponential backoff up to ~5s). Atomic rename was already preventing torn files; this closes the lost-update window. The Keychain and libsecret backends are unaffected — both OS stores serialise their own writes. - Unbalanced parenthesis in
accounts selectandaccounts deletechoice prompts — drive-by fix while replacing the unsafe[..8]slices.
Changed
PackageOutputPath— was hard-coded asC:\nuget-local\, which on Linux produced surprisingsrc/NextIteration.SpectreConsole.Auth/C:/nuget-local/…repo churn. Now repo-relative ($(MSBuildThisFileDirectory)..\..\artifacts\packages) and already gitignored.GeneratePackageOnBuild— now scoped toReleasebuilds via MSBuild condition.dotnet testand Debug builds no longer pack, materially shortening the local test loop.
Tests
18 new tests; suite now at 145.
CommandFormatting.ShortIdacross full GUIDs, short strings, exact-8 boundary, null/empty.- Non-GUID
accountIdresolves to deterministic not-found on file-backendDelete/Select; throws onGetCredentialByIdAsync. - Concurrent
SelectCredentialAsyncon different providers both persist (regression test for theselections.jsonrace). SelectionsLockuncontended/contended/post-release semantics.
Migration notes
No source changes required on consumer code. Public API surface is unchanged — this is additive hardening on top of 0.6.0.
Install
dotnet add package NextIteration.SpectreConsole.Auth --version 0.6.1See CHANGELOG.md for the full history.