{"record":{"id":"dace1ec2cb177e5b","repo":"Tencent/WeKnora","slug":"errinvalidskillname","errorCode":"ErrInvalidSkillName","errorMessage":"%w %q","messagePattern":"%w %q","errorType":"validation","errorClass":"ErrInvalidSkillName","httpStatus":null,"severity":"error","filePath":"internal/sandbox/skill_paths.go","lineNumber":47,"sourceCode":"// separator are rejected so install/exec cannot walk out of the skills root.\nfunc IsValidSkillName(name string) bool {\n\tname = strings.TrimSpace(name)\n\tif name == \"\" || name == \".\" || name == \"..\" {\n\t\treturn false\n\t}\n\tif strings.ContainsAny(name, \"/\\\\\\x00\") {\n\t\treturn false\n\t}\n\treturn path.Base(path.Clean(name)) == name\n}\n\n// SkillDirFor returns the image directory of a skill. The key is the skill\n// name from SKILL.md: that is what the installer writes, and what the agent\n// is told to execute. The database id stays a row key and is not part of\n// the path.\nfunc SkillDirFor(skillName string) (string, error) {\n\tif !IsValidSkillName(skillName) {\n\t\treturn \"\", fmt.Errorf(\"%w %q\", ErrInvalidSkillName, skillName)\n\t}\n\treturn path.Join(SkillsImageRoot, skillName), nil\n}\n\n// SkillRequirementsPath is where the installer agent writes what the skill\n// needs at run time. It lives inside the skill's own directory so it travels\n// with the skill into the snapshot, and it is a file rather than a tool call\n// on purpose: the agent reads a third-party SKILL.md, so nothing it produces\n// may reach the database except as bytes the server parses and validates.\n//\n// An invalid skill name yields an empty path, which every caller treats as\n// \"no declaration\": the name is already validated before an install starts,\n// and a best-effort read is not a place to fail an install.\nfunc SkillRequirementsPath(skillName string) string {\n\tdir, err := SkillDirFor(skillName)\n\tif err != nil {\n\t\treturn \"\"\n\t}","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/skill_paths.go#L29-L65","documentation":"SkillDirFor maps a skill name (from SKILL.md) to its directory under the skills image root, but only if the name passes IsValidSkillName. This sentinel-wrapped error (errors.Is-able via ErrInvalidSkillName) is thrown for names that could escape the skills directory — empty names, path separators, \"..\", slashes, etc.","triggerScenarios":"Calling SkillDirFor, or any of its callers (DiscoverSkills, LoadSkillInstructions, LoadSkillFile, GetSkillBasePath, RemoteScriptPath, relativeSkillFileFromImagePath) with a skillName failing IsValidSkillName — e.g. \"\", \"../escape\", \"a/b\", or names with illegal characters.","commonSituations":"Storing skill names from user input or a database with slashes/whitespace; older records predating the naming rule; hand-edited SKILL.md name fields; constructing skill paths from URL segments.","solutions":["Validate the name with IsValidSkillName before use and reject/repair bad values at ingestion time.","Normalize the stored skill name to the allowed charset (letters/digits/dashes per the validator) and re-save it.","Check errors.Is(err, sandbox.ErrInvalidSkillName) and surface a user-facing 'invalid skill name' message instead of a raw path error."],"exampleFix":"// before\ndir, err := sandbox.SkillDirFor(userInput) // may be \"../x\"\n// after\nif !sandbox.IsValidSkillName(userInput) {\n    return fmt.Errorf(\"invalid skill name %q\", userInput)\n}\ndir, err := sandbox.SkillDirFor(userInput)","handlingStrategy":"validation","validationCode":"if !sandbox.IsValidSkillName(name) {\n    return fmt.Errorf(\"invalid skill name %q\", name)\n}","typeGuard":"func validSkillName(name string) bool { return sandbox.IsValidSkillName(name) }","tryCatchPattern":"dir, err := sandbox.SkillDirFor(name)\nif errors.Is(err, sandbox.ErrInvalidSkillName) {\n    return fmt.Errorf(\"skill %q has an invalid name; fix SKILL.md\", name)\n}","preventionTips":["Validate skill names at ingestion/install time, not at use time.","Never build skill paths from URL segments or raw user input.","Run a migration check over stored skill names after library upgrades."],"tags":["validation","path-traversal","skills"],"backgroundTag":"invalid-identifier","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}