-
Notifications
You must be signed in to change notification settings - Fork 30
fix: CLI --password-stdin + honor handler exit codes #253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| namespace PlanViewer.Cli; | ||
|
|
||
| /// <summary> | ||
| /// Resolves a SQL Server password from the available CLI inputs: | ||
| /// 1. --password-stdin (reads one line from redirected stdin) | ||
| /// 2. --password (inline CLI arg; emits a stderr warning because it's | ||
| /// visible in process listings, shell history, and audit logs) | ||
| /// 3. PLANVIEW_PASSWORD environment variable (from the process environment or | ||
| /// a .env file, already looked up by the caller) | ||
| /// </summary> | ||
| internal static class PasswordResolver | ||
| { | ||
| /// <summary> | ||
| /// Returns true with a resolved password (which may be null if no source provided | ||
| /// one). Returns false on user error (mutual-exclusion violation or stdin not | ||
| /// redirected when --password-stdin was requested). The caller is responsible | ||
| /// for setting Environment.ExitCode on failure. | ||
| /// </summary> | ||
| public static bool TryResolve( | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New file with five distinct decision branches (mutual-exclusion, stdin-claimed, stdin-not-redirected, stdin-success, inline-with-warning, env-fallback) and no unit tests. The table in the PR description was exercised end-to-end against the built Generated by Claude Code |
||
| string? inlinePassword, | ||
| bool passwordFromStdin, | ||
| bool stdinAlreadyClaimed, | ||
| string? envPassword, | ||
| out string? password) | ||
| { | ||
| password = null; | ||
|
|
||
| if (passwordFromStdin && !string.IsNullOrEmpty(inlinePassword)) | ||
| { | ||
| Console.Error.WriteLine("--password and --password-stdin are mutually exclusive."); | ||
| return false; | ||
| } | ||
|
|
||
| if (passwordFromStdin && stdinAlreadyClaimed) | ||
| { | ||
| Console.Error.WriteLine("--password-stdin can't be combined with --stdin (both read from stdin)."); | ||
| return false; | ||
| } | ||
|
|
||
| if (passwordFromStdin) | ||
| { | ||
| if (!Console.IsInputRedirected) | ||
| { | ||
| Console.Error.WriteLine("--password-stdin requires stdin to be redirected (pipe the password into the command)."); | ||
| return false; | ||
| } | ||
| password = Console.In.ReadLine()?.TrimEnd('\r', '\n') ?? ""; | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Repro: Consider failing explicitly when the read yields null-or-empty, since Generated by Claude Code |
||
| return true; | ||
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(inlinePassword)) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Subtle behavior change from Pre-change: Almost certainly nobody depends on passing an empty literal, but worth being aware it's a behavior delta, not just a refactor. Generated by Claude Code |
||
| { | ||
| Console.Error.WriteLine( | ||
| "Warning: --password is visible in process listings and shell history. " + | ||
| "Prefer --password-stdin, the PLANVIEW_PASSWORD env var, or the credential store."); | ||
| password = inlinePassword; | ||
| return true; | ||
| } | ||
|
|
||
| password = envPassword; | ||
| return true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,4 +23,8 @@ | |
| if (credentialService != null) | ||
| root.Add(CredentialCommand.Create(credentialService)); | ||
|
|
||
| return await root.InvokeAsync(args); | ||
| // System.CommandLine's InvokeAsync returns 0 for successful dispatch even when a | ||
| // handler set Environment.ExitCode = 1 to signal a validation error. Honor either | ||
| // signal so scripts can tell success from failure. | ||
| var code = await root.InvokeAsync(args); | ||
| return code != 0 ? code : Environment.ExitCode; | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The expression Generated by Claude Code |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
credential adddoesn't expose an explicit--password-stdinflag (it just has the implicit "read from stdin if redirected" path below). For symmetry withanalyze/query-store, consider routing this throughPasswordResolveror adding the matching flag — otherwise the three commands end up with three slightly different password-input models (explicit flag, implicit redirect, or env). Not a blocker; just inconsistency surface area.Generated by Claude Code