XTLS/Xray-core · error

asset path must stay in asset directory

Error message

asset path must stay in asset directory

What it means

Returned by getAssetFileLocation (backing ResolveAsset/StatAsset) when the requested relative path fails filepath.IsLocal or is exactly '.'. This is a path-traversal guard: asset reads must stay inside the platform asset directory, so absolute paths, '..' escapes, Windows drive letters, or empty/'.' inputs are rejected.

Source

Thrown at common/platform/filesystem/file.go:57

	if err != nil {
		return nil, err
	}
	return NewFileReader(path)
}

func StatAsset(file string) (os.FileInfo, error) {
	_, info, err := getAssetFileLocation(file)
	return info, err
}

func ResolveAsset(file string) (string, error) {
	path, _, err := getAssetFileLocation(file)
	return path, err
}

func getAssetFileLocation(file string) (string, os.FileInfo, error) {
	if !filepath.IsLocal(file) || file == "." {
		return "", nil, errors.New("asset path must stay in asset directory")
	}
	local, err := filepath.Localize(file)
	if err != nil {
		return "", nil, err
	}
	path := platform.GetAssetLocation(local)
	info, err := os.Stat(path)
	if err != nil {
		return "", nil, err
	}
	if !info.Mode().IsRegular() {
		return "", nil, errors.New("asset is not a regular file")
	}
	return path, info, nil
}

func ReadCert(file string) ([]byte, error) {
	if filepath.IsAbs(file) {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Pass only a bare filename (e.g. 'geosite.dat'), not an absolute or parent-relative path
  2. Sanitize any user-controlled asset name with filepath.Base() before calling the API
  3. If the asset truly lives elsewhere, read it directly with filesystem.ReadFile instead of the asset resolver

Example fix

// before
path, err := filesystem.ResolveAsset(userProvidedName) // "../secret.dat"

// after
path, err := filesystem.ResolveAsset(filepath.Base(userProvidedName))
Defensive patterns

Strategy: validation

Validate before calling

name := filepath.Base(userInput)
if !filepath.IsLocal(name) || name == "." || name == string(filepath.Separator) {
    return errors.New("asset name must be a plain filename")
}

Type guard

func isSafeAssetName(name string) bool { return filepath.IsLocal(name) && name != "." && filepath.Base(name) == name }

Prevention

When it happens

Trigger: Calling ResolveAsset/StatAsset with a path containing '..' (e.g. '../xray.geoip.dat'), an absolute path ('/etc/passwd'), a UNC/drive path on Windows, or the literal '.'.

Common situations: Geodata/rule asset filenames built from untrusted config input, config values written as absolute paths when the API expects a bare filename, or sanitization bugs that let user-supplied names traverse directories.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/63291c1c25e0753b. Report an issue: GitHub.