{"record":{"id":"a3ffb7eac5770f68","repo":"Hmbown/CodeWhale","slug":"audited-skill-path-does-not-match-owned-package","errorCode":null,"errorMessage":"audited skill path {} does not match owned package {}","messagePattern":"audited skill path (.+?) does not match owned package (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/mutation.rs","lineNumber":339,"sourceCode":"            skills_dir.display()\n        );\n    }\n    Ok(true)\n}\n\nfn validate_owned_skill_path(\n    ctx: &MutationContext<'_>,\n    skill: &AuditedSkill,\n    path: &Path,\n) -> Result<PathBuf> {\n    if skill.id.root_id != skill.root.id {\n        bail!(\"audited skill root identity changed; refusing mutation\");\n    }\n    let skills_dir = validate_owned_root_descriptor(ctx, &skill.root)?;\n    let package_name = on_disk_package_name(&skill.id)?;\n    let expected = skills_dir.join(package_name);\n    if path != expected {\n        bail!(\n            \"audited skill path {} does not match owned package {}\",\n            path.display(),\n            expected.display()\n        );\n    }\n    validate_owned_child(&skills_dir, path, true)?;\n    Ok(skills_dir)\n}\n\n/// Resolve the on-disk CodeWhale-owned skills directory for a target scope.\npub fn resolve_owned_target(\n    workspace: &Path,\n    home: Option<&Path>,\n    target: SkillTargetScope,\n) -> Result<PathBuf> {\n    let catalog = SkillRootCatalog::build(workspace, home, None);\n    let kind = match target {\n        SkillTargetScope::Project => SkillRootKind::CodeWhaleProject,","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/skills/mutation.rs#L321-L357","documentation":"After validating that the skill's audited root is intact, the library computes the exact on-disk path the owned skill package should occupy (skills_dir + package name) and requires the mutation target path to equal it exactly. A mismatch means the caller is trying to mutate a path that is not the audited skill's package — possibly a different skill, a symlink, or a crafted path.","triggerScenarios":"Passing a path argument to update_skill/remove_skill/trust_skill that differs from skills_dir.join(on_disk_package_name(skill.id)) — e.g. a path with a different package name, a symlinked alias to the package, a path under a different root, or a path constructed manually instead of derived from the skill ID.","commonSituations":"Tooling builds the path from the skill's display name rather than its canonical package name (normalization mismatch); a symlink shortcut points at the package; the caller operates on a copy of the skill in a backup directory.","solutions":["Derive the path from the AuditedSkill via the same catalog helpers the library uses (skills_dir joined with on_disk_package_name) instead of constructing it manually","Check for symlinks in the passed path and resolve/reject them; use the real path of the package","Verify the skill's on-disk package name matches the skill ID's canonical name; re-import if the directory was renamed","List owned skills through the catalog and use the returned path directly"],"exampleFix":"// before\nlet path = skills_dir.join(\"My Skill\"); // manual, wrong casing\nmutation::update_skill(&audited, &path, &ctx)?;\n// after\nlet path = validate_owned_skill_path(&ctx, &audited, /* use derived */ &skills_dir.join(on_disk_package_name(&audited.id))?)?;\nmutation::update_skill(&audited, &path, &ctx)?;","handlingStrategy":"validation","validationCode":"fn path_matches_owned(skills_dir: &Path, skill: &AuditedSkill, path: &Path) -> Result<bool> {\n    Ok(path == skills_dir.join(on_disk_package_name(&skill.id)?))\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = mutation::update_skill(&audited, &path, &ctx) {\n    if e.to_string().contains(\"does not match owned package\") {\n        eprintln!(\"target path is not the audited package; use the catalog-derived path\");\n    }\n    return Err(e.into());\n}","preventionTips":["Never hand-construct skill package paths; derive them from the skill ID via the catalog helpers","Reject or resolve symlinks before passing a path to a mutation API","Use canonical names, not display names, when building package paths"],"tags":["skills","path-validation","security","symlink"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}