fix(jail): block POSIX-absolute cd targets on Windows
filepath.IsAbs("/etc") is false on Windows, so the cd-escape check
folded "/etc" back inside root via filepath.Join and let it through,
failing CI. Treat a leading forward slash that is not OS-absolute as an
escape attempt.- commit
- 4615780369a41d6b6ee6dc661ce00be2dcdb5e44
- author
- patriceckhart · · 2026-06-17 15:24 UTC
- parent
- bfd8c07
- signature
- unsigned
- refs
- tag: v0.2.37
1 file · +8 -0
M packages/agent/tools/sandbox.go +8 -0
| @@ -168,6 +168,14 @@ func (s *Sandbox) checkCDTarget(dir string) error { | |||
| 168 | 168 | return fmt.Errorf("sandbox root: %w", err) | |
| 169 | 169 | } | |
| 170 | 170 | expanded := expandHome(dir) | |
| 171 | + | // A leading forward slash is an absolute POSIX path. The shell uses | |
| 172 | + | // POSIX-style paths regardless of host OS, but on Windows | |
| 173 | + | // filepath.IsAbs("/etc") is false and filepath.Join would fold it | |
| 174 | + | // back inside root, so a `cd /etc` escape would slip through. Treat | |
| 175 | + | // it as an unconditional escape attempt: outside any project root. | |
| 176 | + | if strings.HasPrefix(expanded, "/") && !filepath.IsAbs(expanded) { | |
| 177 | + | return fmt.Errorf("jailed: cd outside sandbox root is not allowed (use /unjail to disable)") | |
| 178 | + | } | |
| 171 | 179 | if !filepath.IsAbs(expanded) { | |
| 172 | 180 | // Relative targets (including `..`) resolve against the sandbox | |
| 173 | 181 | // root, which is the bash tool's working directory when jailed. | |
| @@ -168,6 +168,14 @@ func (s *Sandbox) checkCDTarget(dir string) error { | |||||
| 168 | return fmt.Errorf("sandbox root: %w", err) | 168 | return fmt.Errorf("sandbox root: %w", err) | ||
| 169 | } | 169 | } | ||
| 170 | expanded := expandHome(dir) | 170 | expanded := expandHome(dir) | ||
| 171 | + | // A leading forward slash is an absolute POSIX path. The shell uses | |||
| 172 | + | // POSIX-style paths regardless of host OS, but on Windows | |||
| 173 | + | // filepath.IsAbs("/etc") is false and filepath.Join would fold it | |||
| 174 | + | // back inside root, so a `cd /etc` escape would slip through. Treat | |||
| 175 | + | // it as an unconditional escape attempt: outside any project root. | |||
| 176 | + | if strings.HasPrefix(expanded, "/") && !filepath.IsAbs(expanded) { | |||
| 177 | + | return fmt.Errorf("jailed: cd outside sandbox root is not allowed (use /unjail to disable)") | |||
| 178 | + | } | |||
| 171 | if !filepath.IsAbs(expanded) { | 179 | if !filepath.IsAbs(expanded) { | ||
| 172 | // Relative targets (including `..`) resolve against the sandbox | 180 | // Relative targets (including `..`) resolve against the sandbox | ||
| 173 | // root, which is the bash tool's working directory when jailed. | 181 | // root, which is the bash tool's working directory when jailed. | ||