docker/cli · error

path is outside of root config directory

Error message

path %q is outside of root config directory %q

What it means

Security guard in config.Path (config.go:100-104) that rejects any resolved path which escapes the Docker config directory. Path joins the config dir with the caller-supplied segments, then verifies the result still has the config dir as a prefix (with separator). If not, the segments contained `..` traversal or an absolute component that redirected outside the dir.

Solutions

  1. Sanitize path segments before passing them to config.Path: reject or strip leading slashes and `..` components.
  2. Use filepath.Clean on each segment and verify it does not start with `..` before calling Path.
  3. Construct the full path yourself and confirm it is under Dir() if you cannot control the input.

Example fix

// before
p, err := config.Path(userInput)
// after — sanitize first
cleaned := filepath.Clean("/" + userInput) // force relative
if strings.HasPrefix(cleaned, "..") {
    return fmt.Errorf("invalid path")
}
p, err := config.Path(cleaned)
Defensive patterns

Strategy: validation

Validate before calling

// Reject traversal before calling config.Path.
func safeSegment(s string) error {
    s = filepath.Clean(s)
    if strings.HasPrefix(s, "..") || filepath.IsAbs(s) {
        return fmt.Errorf("unsafe path segment: %q", s)
    }
    return nil
}
for _, seg := range segments {
    if err := safeSegment(seg); err != nil {
        return err
    }
}

Try / catch

p, err := config.Path(segments...)
if err != nil {
    return fmt.Errorf("rejected unsafe path: %w", err)
}

Prevention

When it happens

Trigger: Calling config.Path(p...) where any element in p contains `..` that climbs above the config dir, or an absolute path component (e.g. "/etc/passwd") that, after filepath.Join+Clean, no longer starts with `Dir()+separator`.

Common situations: A tool or context-meta path is built from user/remote input and an attacker (or a misconfigured context) supplies `../../../etc/something`. Also seen when a path segment is inadvertently absolute, overriding the join base. This is a path-traversal protection, not normal usage.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/923a2d330817fe99. Report an issue: GitHub.

Appendix: source

Thrown at cli/config/config.go:103

}

// ContextStoreDir returns the directory the docker contexts are stored in
func ContextStoreDir() string {
	return filepath.Join(Dir(), contextsDir)
}

// SetDir sets the directory the configuration file is stored in
func SetDir(dir string) {
	// trigger the sync.Once to synchronise with Dir()
	initConfigDir.Do(func() {})
	configDir = filepath.Clean(dir)
}

// Path returns the path to a file relative to the config dir
func Path(p ...string) (string, error) {
	path := filepath.Join(append([]string{Dir()}, p...)...)
	if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) {
		return "", fmt.Errorf("path %q is outside of root config directory %q", path, Dir())
	}
	return path, nil
}

// LoadFromReader is a convenience function that creates a ConfigFile object from
// a reader. It returns an error if configData is malformed.
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
	configFile := configfile.ConfigFile{
		AuthConfigs: make(map[string]types.AuthConfig),
	}
	err := configFile.LoadFromReader(configData)
	return &configFile, err
}

// Load reads the configuration file ([ConfigFileName]) from the given directory.
// If no directory is given, it uses the default [Dir]. A [*configfile.ConfigFile]
// is returned containing the contents of the configuration file, or a default
// struct if no configfile exists in the given location.

View on GitHub (pinned to 4f84911bfe)