golang/go · error
file %#q is outside working directory
Error message
file %#q is outside working directory
What it means
Thrown by State.ExtractFiles (state.go:148-177) as a path-traversal (zip-slip) guard. Each archive file name is resolved under s.workdir; if the resolved path does not have the workdir as a prefix, extraction is refused. This prevents a maliciously or accidentally crafted archive from writing outside the test's temporary working directory.
Source
Thrown at src/cmd/internal/script/state.go:165
// originally created.
func (s *State) ExtractFiles(ar *txtar.Archive) error {
wd := s.workdir
// Add trailing separator to terminate wd.
// This prevents extracting to outside paths which prefix wd,
// e.g. extracting to /home/foobar when wd is /home/foo
if wd == "" {
panic("s.workdir is unexpectedly empty")
}
if !os.IsPathSeparator(wd[len(wd)-1]) {
wd += string(filepath.Separator)
}
for _, f := range ar.Files {
name := s.Path(s.ExpandEnv(f.Name, false))
if !strings.HasPrefix(name, wd) {
return fmt.Errorf("file %#q is outside working directory", f.Name)
}
if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil {
return err
}
if err := os.WriteFile(name, f.Data, 0666); err != nil {
return err
}
}
return nil
}
// Getwd returns the directory in which to run the next script command.
func (s *State) Getwd() string { return s.pwd }
// Logf writes output to the script's log without updating its stdout or stderr
// buffers. (The output log functions as a kind of meta-stderr.)View on GitHub (pinned to b6b368adc5)
Solutions
- Make all archive file names relative and contained within the workdir (no leading '../' or absolute paths).
- Audit any env-var placeholders in file names passed through ExpandEnv to ensure they resolve to relative paths.
- Sanitize names with filepath.Clean and reject those that escape before adding to the archive.
- If a file legitimately needs to live outside workdir, write it directly with os.WriteFile instead of ExtractFiles.
Example fix
// before — txtar entry escapes workdir -- ../outside.txt-- contents // after -- subdir/outside.txt-- contents
Defensive patterns
Strategy: validation
Validate before calling
import ("os"; "path/filepath"; "strings")
func archiveNameSafe(wd, name string) bool {
full := filepath.Join(wd, filepath.Clean("/"+name)) // force relative
wdAbs, _ := filepath.Abs(wd)
fullAbs, _ := filepath.Abs(full)
return strings.HasPrefix(fullAbs+string(filepath.Separator), wdAbs+string(filepath.Separator))
}
// Reject before calling ExtractFiles:
for _, f := range ar.Files {
if !archiveNameSafe(s.workdir, f.Name) {
return fmt.Errorf("unsafe archive entry %q", f.Name)
}
} Prevention
- Never put absolute paths or '../' in txtar archive file names.
- Audit env-var placeholders in names passed to ExpandEnv — they can resolve outside workdir.
- Sanitize external archive inputs with filepath.Clean and a prefix check before extraction.
When it happens
Trigger: An archive entry whose name contains '../' (e.g. '../escape.txt'), is absolute ('/etc/foo'), or contains env vars (expanded via ExpandEnv) that resolve to a path outside workdir. The HasPrefix check at state.go:164 fails.
Common situations: Hand-written txtar with a leading '../'; a $envvar in the file name expanding to an absolute path; symlink in workdir causing resolution outside the prefix; archive copied from another test with absolute paths.
Related errors
- ErrInsecurePath
- leading hyphen
- cipher: message authentication failed
- crypto/des: use of DES is not allowed in FIPS 140-only mode
- crypto/des: use of TripleDES is not allowed in FIPS 140-only
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/fe678277191e17ee.
Report an issue: GitHub.