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

> Automate Claude plugin validation with the validate-plugins GitHub Action. Ensure security and policy compliance with zero-config checks and schema validation.

- Repository: [Anthropic/claude-plugins-community](https://github.com/anthropics/claude-plugins-community)
- Tags: how-to-guide
- Published: 2026-08-28

---

**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`](https://github.com/anthropics/claude-plugins-community/blob/main/.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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/.mcp.json), [`.lsp.json`](https://github.com/anthropics/claude-plugins-community/blob/main/.lsp.json), [`hooks/hooks.json`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/action.yml):

- **`marketplace-path`**: Location of assembled marketplace file (default: [`.claude-plugin/marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/.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`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/workflows/validate-plugins.yml) to validate changed plugins on every pull request:

```yaml
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:

```yaml
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:

```bash
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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/.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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/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.