iOfficeAI/OfficeCLI · error · ArgumentException

Invalid skill file path: {relativePath}

Error message

Invalid skill file path: {relativePath}

What it means

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.

Source

Thrown at src/officecli/Core/SkillInstaller.cs:293

    /// <summary>
    /// Return the text content of one bundled reference file inside a skill
    /// (e.g. "reference/decision-rules.md"). Shared by the CLI
    /// `load_skill &lt;name&gt; --path &lt;rel&gt;` command and the MCP
    /// `load_skill` tool's path= argument. Throws on unknown skill, path
    /// traversal, a binary asset (cannot ride the text channel), or a missing
    /// file.
    /// </summary>
    public static string LoadSkillFile(string skillName, string relativePath)
    {
        if (!SkillMap.TryGetValue(skillName, out var folder))
            throw new ArgumentException($"Unknown skill: {skillName}. Available: {KnownSkillsList()}");
        var rel = (relativePath ?? "").Replace('\\', '/').TrimStart('/');
        if (rel.Length == 0)
            throw new ArgumentException("path is empty — pass a relative skill file, e.g. reference/decision-rules.md");
        // Contain to the skill folder: reject traversal and current-dir segments.
        if (rel.Split('/').Any(seg => seg is ".." or "."))
            throw new ArgumentException($"Invalid skill file path: {relativePath}");
        if (BinarySkillExtensions.Contains(Path.GetExtension(rel)))
            throw new ArgumentException(
                $"'{rel}' is a binary asset and cannot be served over the text channel. " +
                $"Install the skill to get it on disk: officecli skills install {skillName}");
        var content = LoadEmbeddedResource($"skills/{folder}/{rel}");
        if (content == null)
            throw new ArgumentException(
                $"Skill file not found: {rel}. List available files via the manifest at the end of: " +
                $"officecli load_skill {skillName}");
        return content;
    }

    /// <summary>
    /// Build the "Reference files" manifest appended to a SKILL.md. Deep trees
    /// (≥ 3 path segments, e.g. a 52-directory style library) collapse to one
    /// line per second-level directory so the manifest stays compact; shallow
    /// files (reference/foo.md) are listed individually. Empty string when the
    /// skill bundles nothing beyond SKILL.md.

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Use a direct relative path inside the skill folder (e.g. 'reference/foo.md').
  2. To read another skill's files, call LoadSkillFile with that skill's name instead of traversing.
  3. Strip leading './' before calling; '..' is never valid here.

Example fix

// before
load_skill morph-ppt --path ../xlsx-toolkit/reference/rules.md
// after
load_skill xlsx-toolkit --path reference/rules.md
Defensive patterns

Strategy: validation

Validate before calling

var rel = relativePath.Replace('\\', '/').TrimStart('/');
if (rel.Split('/').Any(seg => seg is ".." or "."))
    throw new ArgumentException($"Invalid skill file path: {relativePath}");
var text = SkillInstaller.LoadSkillFile(skillName, rel);

Try / catch

try { var text = SkillInstaller.LoadSkillFile(skillName, relativePath); }
catch (ArgumentException ex) when (ex.Message.Contains("Invalid skill file path"))
{ /* reject traversal; use a direct in-skill path */ }

Prevention

When it happens

Trigger: Passing a path containing '..' (e.g. '../other/SKILL.md', 'reference/../../etc') or '.' segments. The skill-name check and empty-path check already passed.

Common situations: A caller tries to read another skill's files via relative traversal, or a path built by concatenation accidentally includes a '.' or '..' segment.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/c7514fb6167b95f0. Report an issue: GitHub.