{"record":{"id":"b75921b1d7a31185","repo":"larksuite/cli","slug":"q-has-invalid-relative-path-q","errorCode":null,"errorMessage":"%q has invalid relative path %q","messagePattern":"%q has invalid relative path %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/skillref/ref.go","lineNumber":32,"sourceCode":"\n// Ref is one exact canonical or runtime skill reference. Path is relative to\n// the named skill; an empty Path denotes the skill's SKILL.md.\ntype Ref struct {\n\tSkill string\n\tPath  string\n}\n\n// Parse parses the \"name[/relative/path]\" form accepted by `skills read`.\nfunc Parse(raw string) (Ref, error) {\n\tif raw == \"\" {\n\t\treturn Ref{}, fmt.Errorf(\"skill reference is empty\")\n\t}\n\tskill, path, _ := strings.Cut(raw, \"/\")\n\tif !ValidSkillName(skill) {\n\t\treturn Ref{}, fmt.Errorf(\"%q has invalid skill name %q\", raw, skill)\n\t}\n\tif path != \"\" && (!fs.ValidPath(path) || path == \".\" || strings.Contains(path, `\\`)) {\n\t\treturn Ref{}, fmt.Errorf(\"%q has invalid relative path %q\", raw, path)\n\t}\n\tif path == \"\" && strings.HasSuffix(raw, \"/\") {\n\t\treturn Ref{}, fmt.Errorf(\"%q has an empty relative path\", raw)\n\t}\n\treturn Ref{Skill: skill, Path: path}, nil\n}\n\n// ValidSkillName reports whether name can identify a top-level skill\n// directory. Keep this rule aligned with the skill-tree manifest validator.\nfunc ValidSkillName(name string) bool {\n\treturn name != \"\" && name != \".\" && name != \"..\" && !strings.ContainsAny(name, `/\\`)\n}\n\n// String returns the canonical \"name[/relative/path]\" form.\nfunc (r Ref) String() string {\n\tif r.Path == \"\" {\n\t\treturn r.Skill\n\t}","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/skillref/ref.go#L14-L50","documentation":"If the path portion after the skill name is non-empty, it must be a valid io/fs path: fs.ValidPath, not \".\", and not containing backslashes. This prevents traversal-prone or host-path-shaped references from entering the resolver. The error echoes the full raw reference and the bad path.","triggerScenarios":"Parse(\"auth/./TOKENS.md\"), Parse(\"auth/../secret\"), Parse(\"auth/C:\\\\x\"), Parse(\"auth/a\\\\b.md\"), any path with leading \"/\" or empty segments like \"auth//x\".","commonSituations":"Copying a Windows file path into the reference; joining paths with filepath.Join on Windows before parsing; injecting \"../\" from user input; passing an absolute host path expecting it to resolve into the embedded tree.","solutions":["Rewrite the path as a clean slash-separated relative path, e.g. \"references/tokens.md\".","Use path.Clean on the relative portion and reject/strip \"..\" and \".\" segments before Parse.","Convert backslashes with strings.ReplaceAll(p, \"\\\\\", \"/\") on Windows-origin input.","Verify against the actual files shipped inside the skill directory (embedded FS layout), not host disk layout."],"exampleFix":"// before\nref, err := skillref.Parse(\"auth/..\\\\references\\\\tokens.md\")\n// after\nref, err := skillref.Parse(\"auth/references/tokens.md\")","handlingStrategy":"validation","validationCode":"if path != \"\" && (!fs.ValidPath(path) || path == \".\" || strings.Contains(path, `\\\\`)) {\n    return fmt.Errorf(\"invalid relative path %q; use a clean slash-separated relative path\", path)\n}","typeGuard":"func validRelPath(p string) bool {\n    return p == \"\" || (fs.ValidPath(p) && p != \".\" && !strings.Contains(p, `\\\\`))\n}","tryCatchPattern":"if _, err := skillref.Parse(raw); err != nil {\n    var pe *fs.PathError\n    _ = pe\n    return fmt.Errorf(\"clean the path with path.CutPrefix/path.Clean and retry: %w\", err)\n}","preventionTips":["Apply path.Clean and reject any \"..\" segment before parsing user-supplied paths","Convert backslashes on Windows-origin input before concatenation","Verify the path matches files actually shipped inside the embedded skill directory"],"tags":["go","skillref","path-validation","path-traversal"],"backgroundTag":"invalid-relative-path","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}