gohugoio/hugo · error

alias "%s" traverses outside the website root directory

Error message

alias "%s" traverses outside the website root directory

What it means

After cleaning the alias path, Hugo splits it into components and rejects it if the first component is "..", meaning the alias would place a redirect file outside the publish (website root) directory. This is a directory-traversal guard: alias values come from content front matter and must never cause writes above publishDir.

Source

Thrown at hugolib/alias.go:139

}

func (a aliasHandler) targetPathAlias(src string, of output.Format) (string, error) {
	originalAlias := src
	if len(src) <= 0 {
		return "", fmt.Errorf("alias \"\" is an empty string")
	}

	alias := path.Clean(filepath.ToSlash(src))

	if !a.allowRoot && alias == "/" {
		return "", fmt.Errorf("alias \"%s\" resolves to website root directory", originalAlias)
	}

	components := strings.Split(alias, "/")

	// Validate against directory traversal
	if components[0] == ".." {
		return "", fmt.Errorf("alias \"%s\" traverses outside the website root directory", originalAlias)
	}

	// Handle Windows file and directory naming restrictions
	// See "Naming Files, Paths, and Namespaces" on MSDN
	// https://msdn.microsoft.com/en-us/library/aa365247%28v=VS.85%29.aspx?f=255&MSPPError=-2147217396
	msgs := []string{}
	reservedNames := []string{"CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}

	if strings.ContainsAny(alias, ":*?\"<>|") {
		msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains invalid characters on Windows: : * ? \" < > |", originalAlias))
	}
	for _, ch := range alias {
		if ch < ' ' {
			msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains ASCII control code (0x00 to 0x1F), invalid on Windows: : * ? \" < > |", originalAlias))
			continue
		}
	}
	for _, comp := range components {

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Rewrite the alias as a root-relative path from the site root, e.g. `aliases: ["/old-section/old-page/"]` instead of `../old-page`.
  2. Remove any `..` segments — aliases are always resolved against publishDir, so parent-directory references are never needed.
  3. If migrating redirects in bulk, script a normalization pass that converts relative redirect sources to absolute site paths before writing front matter.

Example fix

# before
---
aliases: ["../old-page"]
---

# after
---
aliases: ["/section/old-page/"]
---
Defensive patterns

Strategy: validation

Validate before calling

func aliasInsideRoot(a string) bool {
    clean := path.Clean(filepath.ToSlash(a))
    return !strings.HasPrefix(clean, "../") && clean != ".." && !strings.Contains(clean, "/../")
}

Try / catch

if err != nil && strings.Contains(err.Error(), "traverses outside the website root") {
    // surface the alias value and its source page; do not retry
}

Prevention

When it happens

Trigger: Front matter like `aliases: ["../old-page"]` or a relative alias with enough `..` segments that path.Clean leaves a leading "..", e.g. `aliases: ["foo/../../bar"]`. Fires in aliasHandler.targetPathAlias during publishing of alias files.

Common situations: Authors writing aliases relative to the current page's directory and using `..` to go up a level (aliases are resolved against the site root, not the page); content imported from other systems where redirects were expressed as relative paths; untrusted or generated front matter containing traversal sequences.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/ff70b4cd36a962a1.json. Report an issue: GitHub ↗.