siyuan-note/siyuan · error

invalid skill resource path: %s

Error message

invalid skill resource path: %s

What it means

normalizeSkillResourcePath sanitizes the resource portion of a skill locator before it is joined to the skill directory. It rejects absolute paths, parent traversals ('..'), Windows drive/volume prefixes, and backslash-based escape attempts, returning this error for anything unsafe.

Source

Thrown at kernel/util/skill.go:289

	}
	result.Content = content
	result.ResourcePath = resource
	return result, nil
}

func splitSkillLocator(locator string) (name, resource string) {
	locator = strings.TrimSpace(strings.ReplaceAll(locator, `\`, "/"))
	name, resource, _ = strings.Cut(locator, "/")
	return
}

func normalizeSkillResourcePath(resource string) (string, error) {
	resource = strings.ReplaceAll(resource, `\`, "/")
	cleaned := path.Clean(resource)
	native := filepath.FromSlash(cleaned)
	if cleaned == "." || path.IsAbs(cleaned) || cleaned == ".." || strings.HasPrefix(cleaned, "../") ||
		filepath.IsAbs(native) || filepath.VolumeName(native) != "" || hasWindowsDrivePrefix(cleaned) {
		return "", fmt.Errorf("invalid skill resource path: %s", resource)
	}
	return cleaned, nil
}

func hasWindowsDrivePrefix(resource string) bool {
	if len(resource) < 2 || resource[1] != ':' {
		return false
	}
	drive := resource[0]
	return 'a' <= drive && drive <= 'z' || 'A' <= drive && drive <= 'Z'
}

func listSkillResources(skillDir string) (resources []string, truncated bool) {
	realRoot, err := filepath.EvalSymlinks(skillDir)
	if err != nil {
		return nil, false
	}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Use a relative path within the skill directory (e.g. 'scripts/helper.md')
  2. Strip leading '/' and any '..' segments from the resource path before calling
  3. Convert backslashes to forward slashes and remove drive letters
  4. Reference the file via the skill's declared bundled resources list

Example fix

// before
LoadSkill("my-skill/../../../etc/passwd", enabled)
// after
LoadSkill("my-skill/references/usage.md", enabled)
Defensive patterns

Strategy: validation

Validate before calling

function isSafeResourcePath(p) {
  const c = p.replace(/\\/g, "/");
  return !c.startsWith("/") && !/^[A-Za-z]:/.test(c) &&
    c !== "." && !c.split("/").includes("..");
}
if (!isSafeResourcePath(resource)) sanitizeBeforeCall(resource);

Try / catch

try {
  return await loadSkillResource(locator);
} catch (e) {
  if (String(e).includes("invalid skill resource path")) {
    return resolveInsideSkillOnly(locator); // rewrite the path relative to the skill
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling LoadSkill with a locator whose resource part is absolute ('/etc/passwd', 'C:\x'), contains '..' segments, has a Windows drive prefix, or resolves to '.'/'..' after path.Clean.

Common situations: LLM-generated resource paths containing traversal sequences; copying Windows-style paths with backslashes or drive letters into the locator; attempting to read files outside the skill directory on purpose.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/d6b6a82fb945d08b. Report an issue: GitHub.