Harden accounts CLI: markup escaping, accountId validation, selections lock#1
Merged
StuartMeeks merged 1 commit intomainfrom May 3, 2026
Merged
Harden accounts CLI: markup escaping, accountId validation, selections lock#1StuartMeeks merged 1 commit intomainfrom
StuartMeeks merged 1 commit intomainfrom
Conversation
…s lock - Escape user/provider-controlled strings (provider names, account names, environments, summary DisplayFields, exception messages) before they reach Spectre MarkupLine, Table.AddRow, or selection-prompt choices. Closes a markup-injection vector where credential names or exception messages containing '[' / ']' garbled command output. - Validate accountId is a GUID before any filesystem call in FileCredentialManager.Delete/Select/GetCredentialById and the matching Keychain/libsecret APIs. Non-GUIDs now resolve to deterministic 'false' on the mutating paths and ArgumentException on GetCredentialByIdAsync, closing a path-traversal/glob-injection surface where '*' or '..\\..\\foo' could have flowed into Path.Combine and Directory.GetFiles. - Add CommandFormatting.ShortId to abbreviate ids safely, so 'accounts delete abc' (or any short user-supplied id) no longer crashes on the [..8] slice in the confirm prompt before reaching 'not found'. - Serialise selections.json read-modify-write across processes via a new SelectionsLock helper (FileShare.None + FileOptions.DeleteOnClose with ~5s exponential-backoff retry). Atomic rename was already preventing torn files; this closes the lost-update window where two CLI invocations modifying different providers could lose one provider's selection. - csproj: PackageOutputPath was hard-coded to C:\\nuget-local\\ which on Linux produced surprising 'src/.../C:/nuget-local/' repo churn. Now repo-relative under artifacts/packages (already gitignored). GeneratePackageOnBuild now scoped to Release builds so 'dotnet test' and Debug builds no longer pack. Drive-by: fixed unbalanced parenthesis in 'accounts select' / 'delete' choice prompts that surfaced while replacing the unsafe [..8] slices. 18 new tests; suite at 145. Bumps version to 0.6.1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Six fixes from a recent code review of 0.6.0, bundled into 0.6.1.
accountscommand (provider names, account names, environments, summaryDisplayFields, exception messages, the--providerflag echo). Closes a markup-injection vector where a credential name orJsonException/IO message containing[or]garbled the table or error line.accountIdis a GUID before any filesystem call onDelete/Select/GetCredentialByIdAsync(file backend + Keychain + libsecret). Non-GUIDs resolve to deterministicfalseon the mutating paths andArgumentExceptionon the strict read API. Closes a path-traversal / glob-injection surface where*or..\..\foocould have flowed intoPath.Combine/Directory.GetFiles.CommandFormatting.ShortIdsafely abbreviates ids soaccounts delete abc(or any short user-supplied id) no longer crashes on the[..8]slice in the confirm prompt before reaching "not found".selections.jsonRMW via a newSelectionsLockhelper (FileShare.None+FileOptions.DeleteOnClose+ ~5s exponential-backoff retry). Atomic rename was already preventing torn files; this closes the lost-update window where two CLI invocations modifying different providers could lose one provider's selection. Keychain/libsecret backends are unaffected — both OS stores serialise their own writes.PackageOutputPathwas hard-coded asC:\nuget-local\, which on Linux produced surprisingsrc/.../C:/nuget-local/…repo churn. Now repo-relative underartifacts/packages(already gitignored).GeneratePackageOnBuildnow scoped toReleasebuilds —dotnet testand Debug builds no longer pack.Drive-by fix: the unbalanced parenthesis in the
accounts select/deletechoice prompts ({c.AccountId[..8]}...was missing its closing)) — surfaced while replacing the unsafe[..8]slices.Test plan
dotnet buildclean (0 warnings,TreatWarningsAsErrorson)dotnet test— full suite green (145 tests, +18 new)CommandFormatting.ShortIdacross full GUIDs, short strings, exact-8 boundary, null/emptyaccountIdresolves to deterministic not-found on file-backendDelete/Select; throws onGetCredentialByIdAsyncSelectCredentialAsyncon different providers both persist (regression test for theselections.jsonrace)SelectionsLockuncontended / contended / post-release semanticsubuntu-latest(libsecret) andmacos-latest(Keychain)Notes
[0.6.1] — 2026-05-03.