{"record":{"id":"7e29ec41cdb16858","repo":"siyuan-note/siyuan","slug":"invalid-skill-path","errorCode":null,"errorMessage":"invalid skill path","messagePattern":"invalid skill path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill_manage.go","lineNumber":59,"sourceCode":"}\n\ntype SkillFileEntry struct {\n\tPath     string\n\tIsDir    bool\n\tEditable bool\n}\n\ntype SkillFileData struct {\n\tEntries        []SkillFileEntry\n\tContent        *string\n\tRevision       string\n\tReadOnlyReason string\n}\n\n// 管理操作使用真实的相对路径，不使用技能正文中的名称作为文件标识。\nfunc validateManagedSkillPath(p string) error {\n\tif p == \"\" || !fs.ValidPath(p) || strings.ContainsAny(p, \"\\\\:\") {\n\t\treturn errors.New(\"invalid skill path\")\n\t}\n\tfor _, part := range strings.Split(p, \"/\") {\n\t\tif strings.TrimSpace(part) != part || strings.HasSuffix(part, \".\") ||\n\t\t\tstrings.ContainsAny(part, \"<>\\\"|?*~\") || strings.ContainsFunc(part, unicode.IsControl) {\n\t\t\treturn errors.New(\"invalid skill path component\")\n\t\t}\n\t\tdevice := strings.ToUpper(strings.TrimRight(strings.SplitN(part, \".\", 2)[0], \" .\"))\n\t\tif device == \"CON\" || device == \"PRN\" || device == \"AUX\" || device == \"NUL\" || device == \"CONIN$\" || device == \"CONOUT$\" {\n\t\t\treturn errors.New(\"reserved skill file name\")\n\t\t}\n\t\tif strings.HasPrefix(device, \"COM\") || strings.HasPrefix(device, \"LPT\") {\n\t\t\tnumber := strings.TrimPrefix(strings.TrimPrefix(device, \"COM\"), \"LPT\")\n\t\t\tif len([]rune(number)) == 1 && strings.ContainsAny(number, \"0123456789¹²³\") {\n\t\t\t\treturn errors.New(\"reserved skill file name\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill_manage.go#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"Windows-style absolute paths passed by a client (C:\\..., a\\\\b); paths built by string concatenation with '..'; plugin/API clients sending unnormalized paths.","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"],"exampleFix":"// before\npath := \"C:\\\\skills\\\\my-skill\\\\SKILL.md\"\n\n// after\npath := \"my-skill/SKILL.md\"\n","handlingStrategy":"validation","validationCode":"function isValidSkillPath(p) {\n  return p.length > 0 && !p.includes('\\\\') && !p.includes(':') &&\n    !p.split('/').includes('..') && p === p.replace(/\\/{2,}/g, '/').replace(/^\\/|\\/$/g, '');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.manageSkillFiles({action: 'read', path});\n} catch (e) {\n  if (e.message === 'invalid skill path') {\n    // normalize to a slash-separated relative path and retry\n  }\n}","preventionTips":["Always send workspace-relative slash paths","Never build paths by concatenating user input with '..'","Convert Windows backslash paths before calling the API"],"tags":["path-validation","security","path-traversal","go"],"backgroundTag":"path-traversal-blocked","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}