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
- Pass a clean relative slash-separated path like 'my-skill/SKILL.md'
- Normalize the path and strip '..' segments and drive letters before calling the API
- 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
- Always send workspace-relative slash paths
- Never build paths by concatenating user input with '..'
- Convert Windows backslash paths before calling the API
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
- boot appearance asset forbidden
- invalid child template path
- invalid history source path
- notebook asset path resolves outside notebook directory
- path contains invalid character
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 nilView on GitHub (pinned to 9f775e8a12)