{"record":{"id":"c7514fb6167b95f0","repo":"iOfficeAI/OfficeCLI","slug":"invalid-skill-file-path-relativepath","errorCode":null,"errorMessage":"Invalid skill file path: {relativePath}","messagePattern":"Invalid skill file path: (.+?)","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/SkillInstaller.cs","lineNumber":293,"sourceCode":"\n    /// <summary>\n    /// Return the text content of one bundled reference file inside a skill\n    /// (e.g. \"reference/decision-rules.md\"). Shared by the CLI\n    /// `load_skill &lt;name&gt; --path &lt;rel&gt;` command and the MCP\n    /// `load_skill` tool's path= argument. Throws on unknown skill, path\n    /// traversal, a binary asset (cannot ride the text channel), or a missing\n    /// file.\n    /// </summary>\n    public static string LoadSkillFile(string skillName, string relativePath)\n    {\n        if (!SkillMap.TryGetValue(skillName, out var folder))\n            throw new ArgumentException($\"Unknown skill: {skillName}. Available: {KnownSkillsList()}\");\n        var rel = (relativePath ?? \"\").Replace('\\\\', '/').TrimStart('/');\n        if (rel.Length == 0)\n            throw new ArgumentException(\"path is empty — pass a relative skill file, e.g. reference/decision-rules.md\");\n        // Contain to the skill folder: reject traversal and current-dir segments.\n        if (rel.Split('/').Any(seg => seg is \"..\" or \".\"))\n            throw new ArgumentException($\"Invalid skill file path: {relativePath}\");\n        if (BinarySkillExtensions.Contains(Path.GetExtension(rel)))\n            throw new ArgumentException(\n                $\"'{rel}' is a binary asset and cannot be served over the text channel. \" +\n                $\"Install the skill to get it on disk: officecli skills install {skillName}\");\n        var content = LoadEmbeddedResource($\"skills/{folder}/{rel}\");\n        if (content == null)\n            throw new ArgumentException(\n                $\"Skill file not found: {rel}. List available files via the manifest at the end of: \" +\n                $\"officecli load_skill {skillName}\");\n        return content;\n    }\n\n    /// <summary>\n    /// Build the \"Reference files\" manifest appended to a SKILL.md. Deep trees\n    /// (≥ 3 path segments, e.g. a 52-directory style library) collapse to one\n    /// line per second-level directory so the manifest stays compact; shallow\n    /// files (reference/foo.md) are listed individually. Empty string when the\n    /// skill bundles nothing beyond SKILL.md.","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/SkillInstaller.cs#L275-L311","documentation":"Thrown by LoadSkillFile when any segment of the split relative path is '..' or '.', rejecting path traversal and current-directory segments to contain reads inside the skill folder. The guard prevents escaping the skill's embedded-resource prefix. ArgumentException echoing the original (pre-normalized) relativePath.","triggerScenarios":"Passing a path containing '..' (e.g. '../other/SKILL.md', 'reference/../../etc') or '.' segments. The skill-name check and empty-path check already passed.","commonSituations":"A caller tries to read another skill's files via relative traversal, or a path built by concatenation accidentally includes a '.' or '..' segment.","solutions":["Use a direct relative path inside the skill folder (e.g. 'reference/foo.md').","To read another skill's files, call LoadSkillFile with that skill's name instead of traversing.","Strip leading './' before calling; '..' is never valid here."],"exampleFix":"// before\nload_skill morph-ppt --path ../xlsx-toolkit/reference/rules.md\n// after\nload_skill xlsx-toolkit --path reference/rules.md","handlingStrategy":"validation","validationCode":"var rel = relativePath.Replace('\\\\', '/').TrimStart('/');\nif (rel.Split('/').Any(seg => seg is \"..\" or \".\"))\n    throw new ArgumentException($\"Invalid skill file path: {relativePath}\");\nvar text = SkillInstaller.LoadSkillFile(skillName, rel);","typeGuard":null,"tryCatchPattern":"try { var text = SkillInstaller.LoadSkillFile(skillName, relativePath); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"Invalid skill file path\"))\n{ /* reject traversal; use a direct in-skill path */ }","preventionTips":["Never use '..' or '.' segments — target files directly within the skill folder.","To read another skill's files, switch the skillName rather than traversing.","Sanitize user-supplied paths before passing them in."],"tags":["skills","security","path-traversal","validation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}