siyuan-note/siyuan · error

invalid skill path

Error message

invalid skill path

What it means

validateManagedSkillPath rejects any path that is empty, not a valid slash-separated fs path (fs.ValidPath), or contains backslash or colon characters. This blocks path traversal and Windows drive/separator injection before any file operation touches disk.

Solutions

  1. Pass a clean relative slash-separated path like 'my-skill/SKILL.md'
  2. Normalize the path and strip '..' segments and drive letters before calling the API
  3. Validate client-side with fs.ValidPath (or an equivalent) before sending

Example fix

// before
path := "C:\\skills\\my-skill\\SKILL.md"

// after
path := "my-skill/SKILL.md"
Defensive patterns

Strategy: validation

Validate before calling

function isValidSkillPath(p) {
  return p.length > 0 && !p.includes('\\') && !p.includes(':') &&
    !p.split('/').includes('..') && p === p.replace(/\/{2,}/g, '/').replace(/^\/|\/$/g, '');
}

Try / catch

try {
  await api.manageSkillFiles({action: 'read', path});
} catch (e) {
  if (e.message === 'invalid skill path') {
    // normalize to a slash-separated relative path and retry
  }
}

Prevention

When it happens

Trigger: Calling ManageSkillFiles with Path empty, containing '\\', ':', leading/trailing slashes, '..' segments, or other sequences rejected by fs.ValidPath in list/read/write/create/mkdir/move/remove actions.

Common situations: Windows-style absolute paths passed by a client (C:\..., a\\b); paths built by string concatenation with '..'; plugin/API clients sending unnormalized paths.

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@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/7e29ec41cdb16858. Report an issue: GitHub.

Appendix: source

Thrown at kernel/util/skill_manage.go:59

}

type SkillFileEntry struct {
	Path     string
	IsDir    bool
	Editable bool
}

type SkillFileData struct {
	Entries        []SkillFileEntry
	Content        *string
	Revision       string
	ReadOnlyReason string
}

// 管理操作使用真实的相对路径,不使用技能正文中的名称作为文件标识。
func validateManagedSkillPath(p string) error {
	if p == "" || !fs.ValidPath(p) || strings.ContainsAny(p, "\\:") {
		return errors.New("invalid skill path")
	}
	for _, part := range strings.Split(p, "/") {
		if strings.TrimSpace(part) != part || strings.HasSuffix(part, ".") ||
			strings.ContainsAny(part, "<>\"|?*~") || strings.ContainsFunc(part, unicode.IsControl) {
			return errors.New("invalid skill path component")
		}
		device := strings.ToUpper(strings.TrimRight(strings.SplitN(part, ".", 2)[0], " ."))
		if device == "CON" || device == "PRN" || device == "AUX" || device == "NUL" || device == "CONIN$" || device == "CONOUT$" {
			return errors.New("reserved skill file name")
		}
		if strings.HasPrefix(device, "COM") || strings.HasPrefix(device, "LPT") {
			number := strings.TrimPrefix(strings.TrimPrefix(device, "COM"), "LPT")
			if len([]rune(number)) == 1 && strings.ContainsAny(number, "0123456789¹²³") {
				return errors.New("reserved skill file name")
			}
		}
	}
	return nil

View on GitHub (pinned to 9f775e8a12)