Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions pkg/util/shellutil/tokenswap.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -123,11 +124,12 @@ func encodeEnvVarsForFish(env map[string]string) (string, error) {
func encodeEnvVarsForPowerShell(env map[string]string) (string, error) {
var encoded string
for k, v := range env {
// validate key
if !IsValidEnvVarName(k) {
// PowerShell's braced environment-variable syntax supports Windows names
// such as ProgramFiles(x86), but a closing brace cannot be represented.
if k == "" || strings.Contains(k, "}") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject or escape backticks in PowerShell variable names

When the environment contains an otherwise legal OS variable name with a backtick, such as FOO\``, this validation accepts it and interpolates the name directly into ${env:...}`. The PowerShell variable-name rules identify the backtick as an escape character excluded from braced variable names; in particular, a trailing backtick escapes the generated closing brace, making the token script unparsable and preventing shell initialization. Reject backticks here or escape them correctly before interpolation.

Useful? React with 👍 / 👎.

return "", fmt.Errorf("invalid env var name: %q", k)
}
encoded += fmt.Sprintf("$env:%s = %s\n", k, HardQuotePowerShell(v))
encoded += fmt.Sprintf("${env:%s} = %s\n", k, HardQuotePowerShell(v))
}
return encoded, nil
}
Expand Down
56 changes: 56 additions & 0 deletions pkg/util/shellutil/tokenswap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package shellutil

import "testing"

func TestEncodeEnvVarsForPowerShell(t *testing.T) {
tests := []struct {
name string
envName string
envValue string
want string
}{
{
name: "ProgramFiles(x86)",
envName: "ProgramFiles(x86)",
envValue: `C:\Program Files (x86)`,
want: "${env:ProgramFiles(x86)} = \"C:\\Program Files (x86)\"\n",
},
{
name: "CommonProgramFiles(x86)",
envName: "CommonProgramFiles(x86)",
envValue: `C:\Program Files\Common Files (x86)`,
want: "${env:CommonProgramFiles(x86)} = \"C:\\Program Files\\Common Files (x86)\"\n",
},
{
name: "PATH",
envName: "PATH",
envValue: `C:\Windows\System32`,
want: "${env:PATH} = \"C:\\Windows\\System32\"\n",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := EncodeEnvVarsForShell(ShellType_pwsh, map[string]string{tt.envName: tt.envValue})
if err != nil {
t.Fatalf("EncodeEnvVarsForShell() returned error: %v", err)
}
if got != tt.want {
t.Errorf("EncodeEnvVarsForShell() = %q, want %q", got, tt.want)
}
})
}
}

func TestEncodeEnvVarsForPowerShellRejectsUnrepresentableNames(t *testing.T) {
for _, name := range []string{"", "invalid}name"} {
t.Run(name, func(t *testing.T) {
if _, err := EncodeEnvVarsForShell(ShellType_pwsh, map[string]string{name: "value"}); err == nil {
t.Errorf("EncodeEnvVarsForShell() accepted unrepresentable name %q", name)
}
})
}
}