siyuan-note/siyuan · error

template directory contains a symbolic link

Error message

template directory contains a symbolic link

What it means

While hashing a template directory (computing a manifest over its entries), a directory entry was found to be a symbolic link. Template directories must contain only regular entries; symlinks are refused so the manifest/hash reflects only real contained data and cannot be used to reach outside the directory.

Source

Thrown at kernel/model/template_manage.go:134

}

func templateFileRevision(root *os.Root, p string) (string, error) {
	info, err := root.Stat(p)
	if err != nil {
		return "", err
	}
	h := sha256.New()
	if info.IsDir() {
		err = fs.WalkDir(root.FS(), p, func(name string, entry fs.DirEntry, walkErr error) error {
			if walkErr != nil {
				return walkErr
			}
			stat, statErr := entry.Info()
			if statErr != nil {
				return statErr
			}
			if stat.Mode()&os.ModeSymlink != 0 {
				return errors.New("template directory contains a symbolic link")
			}
			fmt.Fprintf(h, "%s\x00%d\x00%d\x00%d\n", name, stat.Size(), stat.ModTime().UnixNano(), stat.Mode())
			return nil
		})
	} else {
		if info.Size() > maxTemplateSourceSize {
			return "", errors.New("template source is too large")
		}
		var content []byte
		content, err = root.ReadFile(p)
		h.Write(content)
	}
	return fmt.Sprintf("%x", h.Sum(nil)), err
}

func readTemplateSource(root *os.Root, p string) (string, error) {
	info, err := root.Stat(p)
	if err != nil {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Remove or replace the symlink inside the template directory with a real file/directory
  2. Rescan after cleaning; the hash/manifest will then succeed
  3. Keep symlinks out of directories managed as template packages

Example fix

// before
$ ln -s ../shared/theme.css templates/pkg/theme.css
// after
$ rm templates/pkg/theme.css
$ cp ../shared/theme.css templates/pkg/theme.css
Defensive patterns

Strategy: validation

Validate before calling

func dirHasSymlinks(dir string) (bool, error) {
    found := false
    err := filepath.WalkDir(dir, func(_ string, d fs.DirEntry, err error) error {
        if err != nil { return err }
        if d.Type()&fs.ModeSymlink != 0 { found = true }
        return nil
    })
    return found, err
}
// refuse to use the directory if dirHasSymlinks returns true

Try / catch

if err := hashTemplateDir(root, p, h); err != nil {
    return fmt.Errorf("template dir must be symlink-free: %w", err)
}

Prevention

When it happens

Trigger: The template-directory hash/manifest walk calls entry.Info() on each child and stat.Mode()&os.ModeSymlink != 0, e.g. a symlinked file or subdirectory placed inside a managed template package directory.

Common situations: Symlinks inside shared/synced template folders (e.g. created on macOS/Linux then surfaced elsewhere); maliciously planted symlinks; build tooling leaving link artifacts in the template directory.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/a94aabe9a0663313. Report an issue: GitHub.