gohugoio/hugo · error

alias " " traverses outside the website root directory

Error message

alias "%s" traverses outside the website root directory

What it means

Returned by targetPathAlias when the first path component of the cleaned alias is "..", i.e. the alias attempts directory traversal outside the publish root. This is a security guard: aliases are written to the publish directory and a ".." prefix would escape it, allowing arbitrary file overwrite. Always fatal regardless of allowRoot.

Solutions

  1. Remove the leading ".." from the alias; aliases must be site-relative paths under the publish root.
  2. Sanitize any user/external input destined for aliases: reject entries whose cleaned form starts with "..".
  3. Run a security review if aliases are populated from untrusted input (this is a path-traversal vector).

Example fix

---
# before
aliases:
  - "../admin/index"
# after
aliases:
  - "old-admin/index"
---
Defensive patterns

Strategy: validation

Validate before calling

func safeAlias(a string) error {
    c := path.Clean(filepath.ToSlash(a))
    if strings.Split(c, "/")[0] == ".." { return fmt.Errorf("traversal") }
    return nil
}

Prevention

When it happens

Trigger: A front-matter aliases entry beginning with "../" or resolving so that components[0] == ".." after path.Clean — e.g. aliases: ["../secret"], aliases: ["..%2f.." ] after decoding, or a malicious/admin-supplied alias string reaching publishDestAlias.

Common situations: Copy-pasted alias from an external source containing a relative-up path; user-supplied content (CMS, comments rendered into front matter) attempting path escape; misconfigured redirect generator concatenating user input without sanitization.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/ff70b4cd36a962a1. Report an issue: GitHub.

Appendix: 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 52c9bd7908)