Skip to content
Merged
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
2 changes: 1 addition & 1 deletion SHELL_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Blocked features are rejected before execution with exit code 2.
- ✅ Line continuation: `\` at end of line
- ✅ Comments: `# text`
- ❌ Extended globbing: `@(pat)`, `*(pat)`, etc.
- ❌ Tilde expansion: `~`, `~/path`
- ❌ Tilde expansion: `~`, `~/path`, `~user`
- ❌ Process substitution: `<(cmd)`, `>(cmd)`

## Execution
Expand Down
23 changes: 20 additions & 3 deletions interp/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package interp

import (
"fmt"
"strings"

"mvdan.cc/sh/v3/syntax"
)
Expand All @@ -14,6 +15,7 @@ import (
// so that disallowed features are caught early with a clear error message.
func validateNode(node syntax.Node) error {
var err error
hdocWords := make(map[*syntax.Word]bool)
syntax.Walk(node, func(n syntax.Node) bool {
if err != nil {
return false
Expand Down Expand Up @@ -110,6 +112,21 @@ func validateNode(node syntax.Node) error {
if err != nil {
return false
}
if n.Hdoc != nil {
hdocWords[n.Hdoc] = true
}

// Blocked tilde expansion (prevents host user info disclosure via os/user.Lookup).
// Heredoc bodies are excluded since they don't undergo tilde expansion.
case *syntax.Word:
if !hdocWords[n] && len(n.Parts) > 0 {
if lit, ok := n.Parts[0].(*syntax.Lit); ok {
if strings.HasPrefix(lit.Value, "~") {
err = fmt.Errorf("tilde expansion is not supported")
return false
}
}
}
}
return true
})
Expand All @@ -119,9 +136,9 @@ func validateNode(node syntax.Node) error {
// blockedSpecialParams are single-character parameter names that are not
// supported in the safe-shell interpreter (positional params, $#, $0, $@, $*).
var blockedSpecialParams = map[string]bool{
"#": true, // $# - number of positional parameters
"!": true, // $! - PID of the last background command
"0": true, // $0 - name of the shell or script
"#": true, // $# - number of positional parameters
"!": true, // $! - PID of the last background command
"0": true, // $0 - name of the shell or script
"1": true, "2": true, "3": true, "4": true, // $1-$9 - positional parameters
"5": true, "6": true, "7": true, "8": true, "9": true,
"@": true, // $@ - all positional parameters as separate words
Expand Down
14 changes: 14 additions & 0 deletions tests/scenarios/shell/environment/tilde_in_heredoc_allowed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
test_against_local_shell: false
description: Tilde inside heredoc body is allowed (heredocs do not undergo tilde expansion).
input:
script: |+
cat <<EOF
~root
~/path
EOF
expect:
stdout: |+
~root
~/path
stderr: ""
exit_code: 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
test_against_local_shell: false
description: Tilde in variable assignment is blocked.
input:
script: |+
DIR=~/path
expect:
stdout: ""
stderr_contains:
- "tilde expansion is not supported"
exit_code: 2
10 changes: 10 additions & 0 deletions tests/scenarios/shell/environment/tilde_mid_word_allowed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
test_against_local_shell: false
description: Tilde in the middle of a word is not tilde expansion and should be allowed.
input:
script: |+
echo foo~bar
expect:
stdout: |+
foo~bar
stderr: ""
exit_code: 0
10 changes: 5 additions & 5 deletions tests/scenarios/shell/environment/tilde_not_expanded.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
test_against_local_shell: false
description: Tilde expansion is disabled; ~ is passed through as a literal character.
description: Tilde expansion is blocked at validation with exit code 2.
input:
script: |+
echo ~
expect:
stdout: |+
~
stderr: ""
exit_code: 0
stdout: ""
stderr_contains:
- "tilde expansion is not supported"
exit_code: 2
10 changes: 5 additions & 5 deletions tests/scenarios/shell/environment/tilde_path_not_expanded.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
test_against_local_shell: false
description: Tilde with path ~/dir is not expanded.
description: Tilde with path ~/dir is blocked at validation.
input:
script: |+
echo ~/mydir
expect:
stdout: |+
~/mydir
stderr: ""
exit_code: 0
stdout: ""
stderr_contains:
- "tilde expansion is not supported"
exit_code: 2
12 changes: 12 additions & 0 deletions tests/scenarios/shell/environment/tilde_quoted_allowed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
test_against_local_shell: false
description: Tilde inside quotes is not tilde expansion and should be allowed.
input:
script: |+
echo "~root"
echo '~/path'
expect:
stdout: |+
~root
~/path
stderr: ""
exit_code: 0
10 changes: 10 additions & 0 deletions tests/scenarios/shell/environment/tilde_username_blocked.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
test_against_local_shell: false
description: Tilde with username (~root) is blocked to prevent host user info disclosure.
input:
script: |+
echo ~root
expect:
stdout: ""
stderr_contains:
- "tilde expansion is not supported"
exit_code: 2
Loading