audit-plugin — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited audit-plugin (Agent Skill) and scored it 87/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 3 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 3 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill requires Python 3.8+ and standard library only. No external packages needed.
To install this skill's dependencies:
pip-compile ./requirements.in
pip install -r ./requirements.txtSee ../../requirements.txt for the dependency lockfile (currently empty — standard library only).
Performs comprehensive validation of a Claude Code plugin against structure standards, naming conventions, component requirements, and security best practices. Uses the plugin-validator agent for deep validation, supported by component-specific scripts.
Establish the plugin root:
../../../../.claude-plugin/plugin.json -- this is the definitive markerNote: Theplugin-validatoragent is defined inagent-scaffolders. If not installed, skip this step and rely on the component scripts in Step 3 and manual checks in Step 4.
Trigger the plugin-validator agent for comprehensive validation:
"Validate the plugin at <path>"The agent checks all 10 categories automatically:
../../../../.claude-plugin/plugin.json) -- JSON syntax, required name field, kebab-case.claude-plugin/commands/**/*.md) -- frontmatter, description, argument-hint, allowed-toolsagents/**/*.md) -- name, description with <example> blocks, model, colorskills/*/SKILL.md) -- frontmatter, name, description (<= 1024 chars), supporting directorieshooks/hooks.json) -- JSON syntax, valid event names, matcher + hooks array.mcp.json) -- server type, required fields, HTTPS enforcementOutput format from plugin-validator:
## Plugin Validation Report
### Plugin: [name] | Location: [path]
### Summary: [PASS/FAIL with stats]
### Critical Issues ([count]) -- file path + issue + fix
### Warnings ([count]) -- file path + recommendation
### Component Summary -- counts of each type
### Positive Findings
### Overall Assessment: [PASS/FAIL + reasoning]Before deep validation, run the load-error fixer to catch issues that prevent Claude Code from loading the plugin at all. These are silent failures — the plugin simply doesn't load with no useful error until you run /doctor.
python ${CLAUDE_PLUGIN_ROOT}/scripts/fix_plugin_load_errors.py <plugin_root>What it fixes automatically:
| Issue | Symptom in /doctor | Root cause |
|---|---|---|
plugin.json has skills/agents/hooks/commands field (any value — array, boolean true, object) | Invalid input | Validator rejects these entirely — auto-discovery handles them; "hooks": true is a common mistake |
hooks.json is {} (empty object) | expected record, received undefined | Must be {"hooks": {}} |
hooks.json is [] (array) | expected object received array | Must be an object |
hooks.json flat format {"EventName":{...}} | expected record, received undefined | Must be nested under "hooks" key |
hooks.json/lsp.json/.mcp.json has literal \n chars | Unrecognized token '\' | Python json.dump wrote escaped newlines; file must have real newlines |
SKILL.md has comment lines before --- | skill fails to load | Frontmatter parser requires --- as the very first line |
Correct `hooks.json` format:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/script.py || python ${CLAUDE_PLUGIN_ROOT}/hooks/script.py" }]
}
]
}
}Note: use python3 ... || python ... — not bare python — so hooks work on both macOS/Linux and Windows.
Empty hooks (no hooks needed):
{ "hooks": {} }IMPORTANT — Cache coverage: Claude Code scans ALL cached versions under ~/.claude/plugins/cache/, not just the active installPath. Fixing source files and reinstalling is the only reliable fix. Use uvx plugin-add to reinstall.
Git checks out symlinks as plain-text "stand-in" files when core.symlinks=false (common on Windows without Developer Mode, or when cloned without the setting). Stand-ins look like real files but contain only a relative path (e.g. ../../../scripts/execute.py). They are functionally broken — the bridge installer will copy the path string, not the actual script.
Run the bulk scanner from the link-checker plugin:
python plugins/dev-utils/scripts/bulk_symlink_fixer.py plugins/<plugin-name>The scanner detects both:
< 512 bytes) whose content looks like a relative pathImportant — the fixer has a silent failure mode. symlink_manager.py always exits 0, so bulk_symlink_fixer.py prints "✓ Fixed" even when the source doesn't exist. Always verify manually:
# Confirm symlinks resolved (count should match expectations)
find plugins/<plugin-name> -type l | wc -l
# Confirm no text-file stand-ins remain for scripts paths (critical)
find plugins/<plugin-name>/skills -path "*/scripts/*" -type f ! -type lTwo categories of stand-ins:
silently, convert manually:
# Read the path out of the stand-in, unlink the file, recreate as symlink
content = Path(standin).read_text().strip()
Path(standin).unlink()
Path(standin).symlink_to(content)(e.g. references/architecture/architecture.md when the file is at references/architecture.md). Correct the relative path before creating the symlink. Check what actually exists at the plugin references/ root and recalculate the depth.
Correct symlink pattern (must match ADR manager / all standard skills):
skills/<skill>/scripts/execute.py → ../../../scripts/<canonical_name>.py
skills/<skill>/references/architecture.md → ../../../references/architecture.mdThe symlink filename and the target filename may differ (e.g. execute.py → exploration_optimizer_execute.py) — that is intentional and valid.
6 known missing-source stand-ins in exploration-cycle-plugin (leave as-is until source files are created at plugins/exploration-cycle-plugin/references/): agent-loop-patterns.md, exploration-output-standards.md
After plugin-validator, run targeted scripts for detailed checks:
Validate each agent file:
python ${CLAUDE_PLUGIN_ROOT}/scripts/validate_agent.py agents/my-agent.mdChecks: frontmatter structure, required fields (name/description/model/color), name format (3-50 chars, lowercase + hyphens), description has <example> blocks, system prompt length (minimum 20 chars, recommended 500-3,000).
Validate hooks.json schema:
python ${CLAUDE_PLUGIN_ROOT}/scripts/validate_hook_schema.py hooks/hooks.jsonChecks: JSON syntax, valid event names, each hook has matcher + hooks array, hook type is command or prompt, command hooks reference existing scripts with ${CLAUDE_PLUGIN_ROOT}.
Test a hook script directly:
python ${CLAUDE_PLUGIN_ROOT}/scripts/test_hook.py \
--hook hooks/scripts/validate.py \
--event PreToolUse \
--input '{"tool_name": "Write", "tool_input": {"file_path": "src/app.py"}}'Lint hook scripts for common issues:
python ${CLAUDE_PLUGIN_ROOT}/scripts/hook_linter.py hooks/For issues the scripts may not catch:
Plugin structure check:
# Manifest must be here (not in root)
ls .claude-plugin/plugin.json
# Components must be at root (not in .claude-plugin/)
ls commands/ agents/ skills/ hooks/
# Validate JSON
jq . .claude-plugin/plugin.jsonSecurity scan:
# Check for hardcoded credentials
grep -rn "password\|api_key\|secret\|token" --include="*.md" --include="*.json" --include="*.sh" .${CLAUDE_PLUGIN_ROOT} portability:
# Ensure no hardcoded paths in hook commands or MCP config
grep -rn "/Users/\|/home/" --include="*.json" --include="*.sh" .Hook script quality (for any `.py` files wired as hooks):
python3 ... || python ... for cross-platform compatibility?main() have an early-exit project-type guard (e.g. if not (project_root / "context").exists(): return) so it skips silently in projects that haven't initialized the plugin?Naming conventions:
my-plugin, not MyPlugin or my_plugin).md.md describing role.sh, .py, .js)Skill quality (run skill-reviewer for each skill):
"Review my skill at skills/skill-name/SKILL.md"If the repo contains a .claude-plugin/marketplace.json, run this check to ensure every plugin entry points to a directory that actually exists. Missing directories silently fail during /plugin install with no helpful error message.
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/audit_marketplace_sources.py <repo_root>
# or from the repo root:
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/audit_marketplace_sources.py .Common causes of missing paths:
marketplace.json not updated (e.g. obsidian-integration → obsidian-wiki-engine)Fix: Either update the source path to match the real directory name, or remove the entry.
Severity levels:
<example> blocks in agents)Fix critical issues first, then re-validate:
# Re-run validation after fixes
"Validate my plugin at <path>"Keep running until: 0 critical issues, warnings addressed or documented.
references/*.md. Always consult them (especially ADR 001-006) to verify if the plugin follows our standards for shared scripts, cross-plugin dependencies, symlinking patterns, and loose coupling. A plugin that violates these ADRs (e.g. duplicates shared scripts instead of symlinking) is considered structurally non-compliant..claude-plugin/plugin.json minimal valid:
{ "name": "plugin-name" }.claude-plugin/plugin.json recommended:
{
"name": "plugin-name",
"version": "0.1.0",
"description": "What the plugin does",
"author": { "name": "Author Name", "email": "email" }
}Agent description pattern (must have `<example>` blocks):
description: |
Use this agent when user asks to "do X", "run Y", or mentions Z.
<example>
Context: user just finished creating a plugin
user: "I've set up my plugin"
assistant: "Let me validate the structure."
</example>Skill description pattern (third-person, anti-undertrigger):
description: >
This skill should be used when the user asks to "X", "Y", or "Z".
Use this skill even when the user doesn't explicitly say "Z" --
mentions of [related concept] should also trigger this.create-skill, create-command, or create-hook to add missing componentsskill-reviewer on each skill for trigger optimizationaudit-plugin-l5 for advanced red-team structural auditplugin_add.py richfrem/agent-plugins-skills~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.