{"record":{"id":"672178ba6cba1d7c","repo":"Tencent/WeKnora","slug":"path-q-is-outside-this-install-s-skill-directory","errorCode":null,"errorMessage":"path %q is outside this install's skill directory (%s); an install may only write its own skill","messagePattern":"path %q is outside this install's skill directory \\((.+?)\\); an install may only write its own skill","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/skill_file.go","lineNumber":406,"sourceCode":"func resolveSkillFilePath(skillDir, requested string) (string, error) {\n\tdir := path.Clean(strings.TrimSpace(skillDir))\n\tif dir == \"\" || dir == \".\" || dir == \"/\" {\n\t\treturn \"\", fmt.Errorf(\"this tool is not bound to a skill directory\")\n\t}\n\ttrimmed := strings.TrimSpace(requested)\n\tif trimmed == \"\" {\n\t\treturn \"\", fmt.Errorf(\"path is required; write a file inside %s\", dir)\n\t}\n\tif strings.ContainsRune(trimmed, 0) {\n\t\treturn \"\", fmt.Errorf(\"path %q is not a valid file path\", requested)\n\t}\n\tcandidate := trimmed\n\tif !path.IsAbs(candidate) {\n\t\tcandidate = path.Join(dir, candidate)\n\t}\n\tclean := path.Clean(candidate)\n\tif clean == dir || !strings.HasPrefix(clean, dir+\"/\") {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"path %q is outside this install's skill directory (%s); \"+\n\t\t\t\t\"an install may only write its own skill\",\n\t\t\trequested, dir,\n\t\t)\n\t}\n\treturn clean, nil\n}\n","sourceCodeStart":388,"sourceCodeEnd":414,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/agent/tools/skill_file.go#L388-L414","documentation":"resolveSkillFilePath performs a containment check: after joining and Clean-ing the requested path, it must live strictly inside the bound skill directory (not equal to it, not outside its prefix). Otherwise the write could escape the skill installation — including via \"..\", absolute paths, or symlink-like spellings — so the tool refuses with this error, protecting the principle that an install may only write its own skill.","triggerScenarios":"Calling Execute with a path that resolves outside skillDir: an absolute path into another skill, \"../\" traversal above the skill root, or the skill directory itself (clean == dir).","commonSituations":"Model attempts to write ../../other-skill/file.txt; caller passes an absolute path from a different install; traversal probing in multi-tenant skill setups.","solutions":["Pass a path relative to the skill directory that stays inside it (e.g. \"docs/readme.md\").","Normalize user/model input (filepath.Clean) and verify the prefix yourself before the call.","If writing to another skill is genuinely needed, use the tool bound to that skill's directory instead."],"exampleFix":"// before\nin := SkillFileInput{Path: \"../other-skill/file.txt\", Content: data}\n// after\np := path.Clean(\"../other-skill/file.txt\")\nif !strings.HasPrefix(p, skillDir+\"/\") {\n    return fmt.Errorf(\"refusing to write outside %s\", skillDir)\n}\nin := SkillFileInput{Path: p, Content: data}","handlingStrategy":"validation","validationCode":"clean := path.Clean(path.Join(skillDir, requested))\nif clean == skillDir || !strings.HasPrefix(clean, skillDir+\"/\") {\n    return fmt.Errorf(\"path %q escapes skill directory %s\", requested, skillDir)\n}","typeGuard":"func insideSkillDir(skillDir, requested string) bool {\n    clean := path.Clean(path.Join(skillDir, requested))\n    return clean != skillDir && strings.HasPrefix(clean, skillDir+\"/\")\n}","tryCatchPattern":"if _, err := tool.Execute(ctx, input); err != nil && strings.Contains(err.Error(), \"outside this install's skill directory\") {\n    // clamp the path inside skillDir or reject the operation; do not retry as-is\n}","preventionTips":["Always pass paths relative to the skill directory, never absolute or ../","Pre-clean and prefix-check untrusted paths before invoking the tool","Use one bound tool per skill installation instead of crossing directories"],"tags":["path-traversal","security","sandbox"],"backgroundTag":"path-traversal-blocked","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}