cilium/cilium · error

refusing to write %q outside of the sysdump directory

Error message

refusing to write %q outside of the sysdump directory

What it means

WithFileSink is the collector's single choke point for opening files inside the sysdump directory. Because filenames can be derived from remote data (e.g. CNI config file names read from `ls` output inside a target pod), the collector rejects any filename whose cleaned absolute path does not resolve under c.sysdumpDir before opening it. This is a path-traversal guard, not a filesystem error — nothing was written.

Source

Thrown at cilium-cli/sysdump/sysdump.go:426

// replaceTimestamp can be used to replace the special timestamp placeholder in file and directory names.
func (c *Collector) replaceTimestamp(f string) string {
	return strings.ReplaceAll(f, timestampPlaceholderFileName, c.startTime.Format(timeFormat))
}

// AbsoluteTempPath returns the absolute path where to store the specified filename temporarily.
func (c *Collector) AbsoluteTempPath(f string) string {
	return path.Join(c.sysdumpDir, c.replaceTimestamp(f))
}

func (c *Collector) WithFileSink(filename string, fn func(io.Writer) error) error {
	path := c.AbsoluteTempPath(filename)
	// filename can be derived from data collected inside a target pod (for
	// example the CNI config file names that SubmitCniConflistSubtask reads
	// from `ls` output), so reject anything that resolves outside the sysdump
	// directory before opening it.
	if !strings.HasPrefix(filepath.Clean(path), filepath.Clean(c.sysdumpDir)+string(os.PathSeparator)) {
		return fmt.Errorf("refusing to write %q outside of the sysdump directory", filename)
	}
	file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode)
	if err != nil {
		return err
	}

	return errors.Join(
		fn(file),
		file.Close(),
	)
}

// WriteYAML writes a kubernetes object to a file as YAML.
func (c *Collector) WriteYAML(filename string, o runtime.Object) error {
	return c.WithFileSink(filename, func(w io.Writer) error {
		return writeYAML(o, w)
	})
}

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Sanitize the filename before passing it: strip path separators and '..' segments, keeping only filepath.Base(filename).
  2. Never pass absolute paths or data straight from pod output; join remote-derived names onto a fixed prefix like 'pods/<ns>/<pod>/'.
  3. Check what filename was logged in the error and confirm whether it legitimately should live outside the sysdump dir — if so, that write does not belong in this API.
  4. If the guard false-positives because sysdumpDir itself is unclean (symlink, trailing slash, '..'), ensure the collector is created with an absolute, filepath.EvalSymlinks-resolved directory.

Example fix

// before
c.WriteYAML(cniConfigName, conf) // cniConfigName = "../../etc/cni/10-flannel.conf" from pod ls output

// after
safe := filepath.Base(cniConfigName) // "10-flannel.conf"
c.WriteYAML(filepath.Join("cni", safe), conf)
Defensive patterns

Strategy: validation

Validate before calling

func safeRelName(name string) string {
	base := filepath.Base(strings.ReplaceAll(name, "\\", "/"))
	if base == "." || base == ".." || base == string(filepath.Separator) {
		return ""
	}
	return base
}
// before calling: filename := filepath.Join("cni", safeRelName(podDerivedName)); if filename == "" { skip }

Type guard

func isInsideSysdump(path, sysdumpDir string) bool {
	rel, err := filepath.Rel(filepath.Clean(sysdumpDir), filepath.Clean(path))
	return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator))
}

Try / catch

if err := collector.WriteYAML(filename, obj); err != nil {
	var pathErr *os.PathError
	if strings.Contains(err.Error(), "refusing to write") {
		log.Printf("skipping unsafe filename %q", filename)
	} else if errors.As(err, &pathErr) {
		log.Printf("filesystem error writing sysdump file: %v", pathErr)
	}
}

Prevention

When it happens

Trigger: Calling WithFileSink (directly or via WriteYAML/WriteString/WriteTable/WriteEventTable, or the anonymous log sink, or submitKVStoreTasks) with a filename that, after timestamp replacement and path.Join/filepath.Clean, escapes the sysdump directory — e.g. '../foo.yaml', an absolute path like '/etc/cni/…', or a pod-supplied name containing '..'.

Common situations: Subtasks that write files named from pod exec output (CNI conflist collection, command output used as filename), misconfigured custom sysdump tasks using absolute paths, or a sysdumpDir itself containing '..' or a trailing-symlink edge that makes Clean resolve differently.

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/7c1fc67c0403ef46. Report an issue: GitHub.