Repository: dotnet/roslyn
File: src/Compilers/Shared/BuildServerConnection.cs (C#)

Code before:
            var commandLineArgs = $@"""-pipename:{pipeName}""";

            if (!File.Exists(processFilePath))
            {
                // Fallback to not use the apphost if it is not present (can happen in compiler toolset scenarios for example).
                commandLineArgs = RuntimeHostInfo.GetDotNetExecCommandLine(Path.ChangeExtension(processFilePath, ".dll"), commandLineArgs);
                processFilePath = RuntimeHostInfo.GetDotNetPathOrDefault();
            }

            return (processFilePath, commandLineArgs);
        }

        /// <summary>
        /// Creates an environment block for Windows CreateProcess API.
        /// </summary>
        /// <param name="environmentVariables">Dictionary of environment variables to include</param>
        /// <returns>Pointer to environment block that must be freed with <see cref="Marshal.FreeHGlobal"/></returns>
        private static IntPtr CreateEnvironmentBlock(Dictionary<string, string> environmentVariables)
        {
            if (environmentVariables.Count == 0)
            {
                return IntPtr.Zero;
            }

            // Build the environment block as a single string
            // Environment variable names are case-insensitive on Windows, so we sort using OrdinalIgnoreCase for consistency
            var envBlock = new StringBuilder();
            foreach (var kvp in environmentVariables.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase))
            {
                envBlock.Append(kvp.Key);
                envBlock.Append('=');
                envBlock.Append(kvp.Value);
                envBlock.Append('\0');
            }
            // Windows environment block format requires an additional null terminator after the last variable to mark the end of the block
            envBlock.Append('\0');

            // Convert to Unicode and allocate unmanaged memory
            return Marshal.StringToHGlobalUni(envBlock.ToString());
        }

        /// <summary>
        /// Gets the environment variables that should be passed to the server process.
        /// </summary>
        /// <param name="currentEnvironment">Current environment variables to use as a base</param>
        /// <param name="logger">Optional logger for logging environment variable setup</param>
        /// <returns>Dictionary of environment variables to set, or null if no custom environment is needed</returns>
        internal static Dictionary<string, string>? GetServerEnvironmentVariables(System.Collections.IDictionary currentEnvironment, ICompilerServerLogger? logger = null)
        {
            if (RuntimeHostInfo.GetToolDotNetRoot() is not { } dotNetRoot)
            {

Review comment (security):
It looks like the win32 api _requires_ the env vars to be sorted, perhaps that's worth mentioning.

From https://learn.microsoft.com/en-us/windows/win32/procthread/changing-environment-variables:
> All strings in the environment block must be sorted alphabetically by name. The sort is case-insensitive, Unicode order, without regard to locale.

Code after:
            var commandLineArgs = $@"""-pipename:{pipeName}""";

            if (!File.Exists(processFilePath))
            {
                // Fallback to not use the apphost if it is not present (can happen in compiler toolset scenarios for example).
                commandLineArgs = RuntimeHostInfo.GetDotNetExecCommandLine(Path.ChangeExtension(processFilePath, ".dll"), commandLineArgs);
                processFilePath = RuntimeHostInfo.GetDotNetPathOrDefault();
            }

            return (processFilePath, commandLineArgs);
        }

        /// <summary>
        /// Creates an environment block for Windows CreateProcess API.
        /// </summary>
        /// <param name="environmentVariables">Dictionary of environment variables to include</param>
        /// <returns>Pointer to environment block that must be freed with <see cref="Marshal.FreeHGlobal"/></returns>
        private static IntPtr CreateEnvironmentBlock(Dictionary<string, string> environmentVariables)
        {
            if (environmentVariables.Count == 0)
            {
                return IntPtr.Zero;
            }

            // Build the environment block as a single string
            // Windows API requires environment variables to be sorted alphabetically by name (case-insensitive, Unicode order)
            var envBlock = new StringBuilder();
            foreach (var kvp in environmentVariables.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase))
            {
                envBlock.Append(kvp.Key);
                envBlock.Append('=');
                envBlock.Append(kvp.Value);
                envBlock.Append('\0');
            }
            // Windows environment block format requires an additional null terminator after the last variable to mark the end of the block
            envBlock.Append('\0');

            // Convert to Unicode and allocate unmanaged memory
            return Marshal.StringToHGlobalUni(envBlock.ToString());
        }

        /// <summary>
        /// Gets the environment variables that should be passed to the server process.
        /// </summary>
        /// <param name="currentEnvironment">Current environment variables to use as a base</param>
        /// <param name="logger">Optional logger for logging environment variable setup</param>
        /// <returns>Dictionary of environment variables to set, or null if no custom environment is needed</returns>
        internal static Dictionary<string, string>? GetServerEnvironmentVariables(System.Collections.IDictionary currentEnvironment, ICompilerServerLogger? logger = null)
        {
            if (RuntimeHostInfo.GetToolDotNetRoot() is not { } dotNetRoot)
            {