zeroclaw-labs/zeroclaw · error · anyhow::Error

Skill security audit failed: {}

Error message

Skill security audit failed: {}

What it means

enforce_skill_security_audit runs audit_skill_directory_with_options over the skill directory and bails when the report has findings. Findings include: no SKILL.md (or deprecated manifest) at the skill root, symlinks anywhere inside, script-like files when allow_scripts is false (the default), markdown/toml files over 512 KiB, and forbidden-content findings in markdown/manifests. The message embeds report.summary(), so each finding names a file and a reason.

Source

Thrown at crates/zeroclaw-runtime/src/skills/mod.rs:2011

        _ => anyhow::bail!(
            "Unable to determine installed skill directory after clone (multiple new directories found)"
        ),
    }
}

fn enforce_skill_security_audit(
    skill_path: &Path,
    allow_scripts: bool,
) -> Result<audit::SkillAuditReport> {
    let report = audit::audit_skill_directory_with_options(
        skill_path,
        audit::SkillAuditOptions { allow_scripts },
    )?;
    if report.is_clean() {
        return Ok(report);
    }

    anyhow::bail!("Skill security audit failed: {}", report.summary());
}

fn remove_git_metadata(skill_path: &Path) -> Result<()> {
    let git_dir = skill_path.join(".git");
    if git_dir.exists() {
        std::fs::remove_dir_all(&git_dir)
            .with_context(|| format!("failed to remove {}", git_dir.display().to_string()))?;
    }
    Ok(())
}

fn copy_dir_recursive_secure(src: &Path, dest: &Path) -> Result<()> {
    let src_meta = std::fs::symlink_metadata(src)
        .with_context(|| format!("failed to read metadata for {}", src.display().to_string()))?;
    if src_meta.file_type().is_symlink() {
        anyhow::bail!(
            "Refusing to copy symlinked skill source path: {}",
            src.display()

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the findings in the message — each names a relative path and the reason it was flagged
  2. If the finding is 'script-like files are blocked by skill security policy' and you trust the skill, set skills.allow_scripts = true in your zeroclaw config and reinstall
  3. Add a SKILL.md at the skill root and remove symlinks (replace with real files)
  4. Shrink oversized markdown/toml below the 512 KiB static-audit limit

Example fix

# before: install of a script-carrying skill fails
zeroclaw skills install ./my-skill   # audit failed: scripts/setup.sh blocked

# after: trust the skill explicitly
# zeroclaw config:
# [skills]
# allow_scripts = true
zeroclaw skills install ./my-skill
Defensive patterns

Strategy: validation

Validate before calling

// Pre-audit with the library's public API before installing:
use zeroclaw_runtime::skills::audit::{audit_skill_directory_with_options, SkillAuditOptions};
let report = audit_skill_directory_with_options(
    std::path::Path::new(source),
    SkillAuditOptions { allow_scripts },
)?;
if !report.is_clean() {
    eprintln!("would be rejected: {}", report.summary());
    // fix findings (remove scripts/symlinks, add SKILL.md) or set allow_scripts
}

Try / catch

match install_local_skill_source(source, &skills_path, allow_scripts) {
    Err(e) if e.to_string().starts_with("Skill security audit failed") => {
        // parse the ';'-joined findings; scripts_blocked => hint allow_scripts=true
    }
    r => r,
}

Prevention

When it happens

Trigger: install_local_skill_source or install_git_skill_source on a skill that ships shell/python helper scripts while config skills.allow_scripts is false; skill root lacking SKILL.md; any symlink inside the skill tree; SKILL.md larger than 512 KiB.

Common situations: Installing tool-style skills that carry .sh/.py helpers; copying a skill folder without its SKILL.md; vendored repos that symlink shared assets; generated skills with huge markdown.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/944186d3f5eea45a. Report an issue: GitHub.