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
- Sanitize the filename before passing it: strip path separators and '..' segments, keeping only filepath.Base(filename).
- Never pass absolute paths or data straight from pod output; join remote-derived names onto a fixed prefix like 'pods/<ns>/<pod>/'.
- 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.
- 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
- Never use pod-exec or API output verbatim as a filename; always reduce it with filepath.Base and drop '..' segments.
- Keep all sysdump filenames as fixed constants or fixed prefixes plus sanitized basenames.
- Create the collector with an absolute, symlink-resolved (filepath.EvalSymlinks) sysdump directory.
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
- failed to dump Kubernetes version: %w
- failed to write event table: %w
- tar entry %q: path escapes extraction directory
- unable to list Cilium pods: %w
- failed to fetch bgp state from %s: %w
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/7c1fc67c0403ef46.
Report an issue: GitHub.