validate-plugins GitHub Action: End-to-End Claude Plugin Validation & Security

The validate-plugins GitHub Action is a composite workflow that provides automated, zero-config validation for Claude plugin marketplace repositories, combining canonical schema checks via the claude CLI with nine security and policy invariants.

The validate-plugins action lives in the anthropics/claude-plugins-community repository and is designed to be dropped into any *-plugins repository without modification. It ensures every plugin submission adheres to Anthropic's strict metadata schema while enforcing security policies that prevent supply-chain attacks, malformed deployments, and runtime crashes.

Architecture and Validation Layers

The action orchestrates six distinct validation layers defined in .github/actions/validate-plugins/action.yml. Each layer targets specific failure modes in the Claude plugin ecosystem, from schema drift to malicious URL injection.

Canonical Schema Validation (Step 20)

In action.yml:20-21, the action executes claude plugin validate <marketplace.json> against the assembled marketplace file. This uses the Zod schema bundled in @anthropic-ai/claude-code, guaranteeing that the marketplace structure matches the latest upstream definition from Anthropic.

Policy Invariant Enforcement (Step 11)

Step 11 runs validate-plugins/scripts/11-validate-invariants.sh to enforce nine static rules (I1-I9) on the full marketplace:

  • I1: Entries must be sorted alphabetically with no duplicates
  • I5: External plugins must pin exact SHA commits
  • I6/I7: Filename-to-name matching (activated when entries-dir is set)
  • I8: Protection against direct edits to vendored paths
  • Additional checks for description length, URL format, and shell-character safety

External Plugin Validation (Step 30)

For each changed external entry—or all entries when validate-all-external=true—the action clones the repository at the exact pinned SHA specified in source.sha. It then runs claude plugin validate on the remote plugin.json. For entries marked strict:false that lack a manifest, the action synthesizes a minimal manifest before validation.

In-Repo Plugin Validation (Step 40)

Step 40 (action.yml:31-36) validates changed plugin folders inside the repository using the same CLI validation logic as external plugins.

Auxiliary File Safety (Step 41)

Step 41 parses auxiliary JSON files (.mcp.json, .lsp.json, hooks/hooks.json) for each changed folder. Malformed auxiliary files cause immediate CI failure to prevent runtime crashes in the Claude Code environment.

Reporting (Step 90)

The final step collates results from all layers into a markdown report and exposes the result output (pass/fail) plus the report file path.

Security Model and SSRF Protection

The action implements defense-in-depth mechanisms defined in the README's Security Model section and lib/common.sh.

SSRF Guard: All external URLs are validated against the allowed-hosts input (default: github.com gitlab.com bitbucket.org). Bare IP addresses are explicitly rejected.

Safe Command Construction: The validate-plugins/lib/common.sh helper re-validates all contributor-controlled values before shell interpolation. Every Git invocation uses double-quoting and -- end-of-options markers to prevent argument injection.

Isolation: External repositories clone into temporary directories at the pinned SHA, and only the static claude plugin validate binary executes. No code from external repositories runs during validation.

Key Configuration Inputs

The action accepts granular inputs defined in action.yml:

  • marketplace-path: Location of assembled marketplace file (default: .claude-plugin/marketplace.json)
  • entries-dir: Enables per-file mode, activating filename-name matching invariants (I6/I7)
  • warn-invariants: Space-separated list of invariant codes treated as warnings instead of errors (default: I1 I3 I5 I8)
  • sha-exempt: Plugin names allowed to omit source.sha (exempt from I5)
  • scope-errors-to-changed: Downgrades invariant errors on unchanged entries to warnings
  • validate-all-external: Forces validation of all external entries for nightly drift detection
  • external-timeout-secs: Timeout per external plugin clone (default: 120 seconds)
  • allowed-hosts: SSRF allowlist for external URLs (default: github.com gitlab.com bitbucket.org)

Usage Examples

Standard PR Validation Workflow

Drop this into .github/workflows/validate-plugins.yml to validate changed plugins on every pull request:

name: Validate Plugins
on:
  pull_request:
    paths:
      - '.claude-plugin/**'
      - 'plugins/**'
      - '*/.claude-plugin/**'
      - '*/agents/**'
      - '*/skills/**'
      - '*/commands/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: anthropics/claude-plugins-community/.github/actions/validate-plugins@<PINNED-SHA>
        with:
          marketplace-path: .claude-plugin/marketplace.json

Nightly Drift Detection

Validate all external plugins on a schedule to detect upstream changes or deleted repositories:

name: Validate Plugins (nightly drift check)
on:
  schedule:
    - cron: '23 7 * * *'
  workflow_dispatch:

jobs:
  drift:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: anthropics/claude-plugins-community/.github/actions/validate-plugins@<PINNED-SHA>
        with:
          validate-all-external: "true"
          skip-local-folders: "true"

Local Debugging

Execute individual validation scripts locally by setting the required environment variables:

export ACTION_PATH=.github/actions/validate-plugins
export VALIDATE_TMP=/tmp/validate-plugins
export BASE_REF=origin/main
export MARKETPLACE_PATH=.claude-plugin/marketplace.json
export ALLOWED_HOSTS="github.com gitlab.com bitbucket.org"

mkdir -p "$VALIDATE_TMP"

bash $ACTION_PATH/scripts/00-detect-changes.sh
bash $ACTION_PATH/scripts/11-validate-invariants.sh
bash $ACTION_PATH/scripts/20-validate-cli-marketplace.sh
bash $ACTION_PATH/scripts/30-validate-cli-external.sh
bash $ACTION_PATH/scripts/40-validate-cli-local.sh
bash $ACTION_PATH/scripts/41-validate-aux-files.sh
bash $ACTION_PATH/scripts/90-report.sh

Summary

  • The validate-plugins action is a composite GitHub Action located in anthropics/claude-plugins-community that validates Claude plugin marketplace repositories without requiring custom configuration.
  • It combines canonical schema validation (via claude plugin validate) with nine policy invariants (I1-I9) that enforce sorting, SHA pinning, and path safety.
  • External plugins are cloned at pinned SHAs and validated in isolation, with SSRF protection via strict host allowlisting in lib/common.sh.
  • The action supports both PR validation (changed files only) and full marketplace scans for drift detection via the validate-all-external flag.
  • All contributor inputs are sanitized before shell execution using double-quoting and -- end-of-options markers to prevent injection attacks.

Frequently Asked Questions

What causes the validate-plugins action to fail?

The action fails when the claude plugin validate CLI detects schema violations, when policy invariants (I1-I9) detect violations not explicitly listed in warn-invariants, or when auxiliary JSON files (.mcp.json, etc.) are malformed. Setting fail-on-warnings: "true" treats all warnings as fatal errors.

How does the action prevent supply-chain attacks on external plugins?

The action validates all external URLs against the allowed-hosts list (rejecting bare IPs), clones repositories at the exact source.sha commit hash specified in the marketplace entry, and executes only the static claude plugin validate binary rather than any code contained in the external repository.

Can I run validation locally without GitHub Actions?

Yes. Export the required environment variables (ACTION_PATH, VALIDATE_TMP, BASE_REF, MARKETPLACE_PATH, ALLOWED_HOSTS) and execute the individual bash scripts located in .github/actions/validate-plugins/scripts/ sequentially, starting with 00-detect-changes.sh to populate the changed-files list.

What is the difference between strict:false and standard external plugins?

Standard external plugins must provide a plugin.json manifest at the repository root. For entries marked strict:false that lack a manifest, the action automatically synthesizes a minimal manifest from repository metadata before running claude plugin validate, allowing legacy or simple plugins to pass validation without requiring a full manifest file.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →