Fix credential_process failing when executable path contains spaces on Windows.#4413
Conversation
fececa6 to
c9324b2
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates ProcessAWSCredentials to avoid routing credential_process execution through cmd.exe on Windows, addressing failures with quoted executable paths containing spaces and hangs when cmd.exe is disabled by Group Policy.
Changes:
- Replace
cmd.exe /cinvocation on Windows with direct parsing ofcredential_processintoProcessStartInfo.FileName+Arguments. - Add Windows-focused unit tests for quoted executable paths (including paths with spaces) and for malformed quoted input.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| sdk/src/Core/Amazon.Runtime/Credentials/ProcessAWSCredentials.cs | Parses and directly executes credential_process on Windows instead of using cmd.exe. |
| sdk/test/UnitTests/Custom/Runtime/Credentials/ProcessAWSCredentialsTest.cs | Adds test coverage for quoted executable paths with spaces/quoted args and unmatched quote behavior. |
Comments suppressed due to low confidence (3)
sdk/src/Core/Amazon.Runtime/Credentials/ProcessAWSCredentials.cs:104
- On Windows this now parses and launches
credential_processdirectly viaProcessStartInfo.FileNamewithUseShellExecute = false. That breaks profiles that point at script files like.bat/.cmd(including this repo’s ownwindows-credentials-script.batused by unit tests), becauseProcess.StartwithUseShellExecute=falseuses CreateProcess and cannot execute batch files. Consider either (a) detecting script extensions on Windows and invokingcmd.exe /cwith correct quoting only for those cases, or (b) switching tests/docs to use an actual executable/interpreter (e.g.,powershell.exe -File ...,python.exe script.py) and explicitly documenting thatcredential_processmust start with an executable on Windows.
#if NETSTANDARD
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
fileName = "sh";
var escapedArgs = processCredentialInfo.Replace("\\", "\\\\").Replace("\"", "\\\"");
arguments = $"-c \"{escapedArgs}\"";
}
else
{
(fileName, arguments) = ParseCredentialProcessCommand(processCredentialInfo);
}
#else
(fileName, arguments) = ParseCredentialProcessCommand(processCredentialInfo);
#endif
_processStartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true
};
sdk/test/UnitTests/Custom/Runtime/Credentials/ProcessAWSCredentialsTest.cs:206
- This test repeats the fixed temp directory name pattern and can collide with other runs/parallel tests. Use a unique temp directory name and ensure cleanup handles pre-existing directories.
// Create a directory with a space in the name and copy the script there
var dirWithSpace = Path.Combine(Path.GetTempPath(), "aws sdk test dir");
Directory.CreateDirectory(dirWithSpace);
var scriptCopy = Path.Combine(dirWithSpace, "windows-credentials-script.bat");
File.Copy(Executable, scriptCopy, overwrite: true);
sdk/test/UnitTests/Custom/Runtime/Credentials/ProcessAWSCredentialsTest.cs:221
- Same cleanup brittleness as the earlier test: deleting the copied script and then
Directory.Delete(..., recursive:false)can intermittently fail on Windows (locks/AV) and leave temp artifacts. Prefer recursive directory deletion and resilient cleanup so the test doesn’t flake.
finally
{
File.Delete(scriptCopy);
Directory.Delete(dirWithSpace, recursive: false);
}
| { | ||
| // Fail fast on malformed input, consistent with botocore's _windows_shell_split(). | ||
| throw new ProcessAWSCredentialException( | ||
| $"No closing quotation mark in credential_process command: {command}"); |
| // Create a directory with a space in the name and copy the script there | ||
| var dirWithSpace = Path.Combine(Path.GetTempPath(), "aws sdk test dir"); | ||
| Directory.CreateDirectory(dirWithSpace); | ||
| var scriptCopy = Path.Combine(dirWithSpace, "windows-credentials-script.bat"); | ||
| File.Copy(Executable, scriptCopy, overwrite: true); |
| finally | ||
| { | ||
| File.Delete(scriptCopy); | ||
| Directory.Delete(dirWithSpace, recursive: false); | ||
| } |
aa1321d to
4e208a2
Compare
4e208a2 to
91a75b9
Compare
91a75b9 to
6da0c02
Compare
| /// This avoids using cmd.exe, which has issues with quoted paths | ||
| /// and hangs when cmd.exe is disabled via Group Policy. | ||
| /// </summary> | ||
| private static (string FileName, string Arguments) ParseCredentialProcessCommand(string command) |
There was a problem hiding this comment.
Talked offline, but this suggestion (from Claude) achieves the same result and it was much easier for me to parse (no pun intended):
private static void ParseCommand(string command, out string fileName, out string arguments)
{
if (command[0] == '"')
{
var rest = command.Substring(1);
var closingQuote = rest.IndexOf('"');
if (closingQuote < 0)
throw new ProcessAWSCredentialException("Unmatched quote in credential_process value.");
fileName = rest.Substring(0, closingQuote);
arguments = rest.Substring(closingQuote + 1).TrimStart();
}
else
{
var space = command.IndexOf(' ');
fileName = space < 0 ? command : command.Substring(0, space);
arguments = space < 0 ? "" : command.Substring(space + 1).TrimStart();
}
}The other topic was even though this isn't documented anywhere, there are some edge cases that could be relying on cmd behavior. For example, credential_process = set-env.cmd && get-creds.exe and credential_process = get-creds.exe 2>nul
Description
ProcessAWSCredentialspreviously delegated tocmd.exe /cto run thecredential_processcommand. This caused two issues:Quoted paths with spaces (e.g., "C:\Program Files\foo.exe") were broken because cmd.exe strips quotes when arguments follow, causing it to interpret "C:\Program" as the executable (GitHub #393).
On systems where cmd.exe is disabled via Group Policy, the SDK hung indefinitely waiting for cmd.exe to exit (GitHub #1845).
The fix removes the
cmd.exeintermediary on Windows and instead parses thecredential_processvalue directly — extracting the double-quoted executable path and arguments per the AWS SDK specification — then launches the process viaProcessStartInfo.FileName. This is consistent with howbotocorehandlescredential_processon Windows.Refer https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html for some guidance around using double-quotes.
Motivation and Context
Issues:
Testing
Dry-runs
Breaking Changes Assessment
Screenshots (if appropriate)
Types of changes
Checklist
License