# How the validate-plugins GitHub Action Works: Schema Validation and Security Enforcement

> Discover how the validate-plugins GitHub Action ensures Claude plugin schema compliance and security with its multi-layered validation pipeline. Learn about its validation process and enforcement mechanisms.

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

---

**The `validate-plugins` action is a composite GitHub Action that enforces the canonical Claude plugin schema and security/policy invariants through a multi-layered validation pipeline.**

The `validate-plugins` action in the `anthropics/claude-plugins-community` repository serves as the primary CI gate for Claude plugin marketplace submissions. It validates both the upstream Zod schema from Anthropic and organization-specific security rules to ensure every plugin entry meets strict quality and safety standards before merging.

## Architecture and Validation Layers

The action operates through four 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, from schema compliance to supply-chain security.

### Canonical Schema Validation (Step 20)

The first layer guarantees that [`marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/marketplace.json) conforms to the official Claude plugin specification. Step 20 installs the latest `@anthropic-ai/claude-code` package and executes `claude plugin validate` against the marketplace manifest. This check ensures the JSON structure matches the upstream Zod schema maintained by Anthropic, catching type mismatches and missing required fields before they reach production.

### Security and Policy Invariants (Step 11)

Step 11 applies a static set of hard-coded rules (Invariants I1 through I9) defined in the action's README. These organization-specific checks include:
- **Sorted names** (I1): Enforcing alphabetical ordering of plugin entries
- **SHA pinning** (I5): Requiring 40-character lowercase hexadecimal SHAs for external plugins
- **HTTPS-only URLs** (I3): Blocking non-TLS endpoints
- **No shell metacharacters** (I4): Preventing injection vectors in string fields

These invariants never change upstream and provide a defense-in-depth layer beyond schema validation.

### Per-Plugin Quality Checks (Steps 30, 40, 41)

The third layer validates individual plugin implementations:
- **Step 30**: Clones each external plugin at its pinned SHA and runs `claude plugin validate` on the plugin's own manifest (or synthesizes a minimal manifest for skills-only entries)
- **Step 40**: Validates every in-repo plugin folder modified by the pull request
- **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)) to ensure they are well-formed

### Diff-Gating Logic

Steps 11 and 20 always execute against the full marketplace to catch global invariant violations. Steps 30, 40, and 41 run only on files changed in the PR unless the `validate-all-external` input is set to `"true"`, enabling efficient CI execution while maintaining the option for full audits.

## Core Execution Flow

The action coordinates seven sequential steps orchestrated by shell scripts in `.github/actions/validate-plugins/scripts/`:

1. **Change Detection** ([`00-detect-changes.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/00-detect-changes.sh)): Computes the delta of changed marketplace entries, external plugins, and local folders based on the `base-ref` input.

2. **Invariant Validation** ([`11-validate-invariants.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/11-validate-invariants.sh)): Executes the static I1-I9 rules against the complete marketplace JSON using helper functions from [`lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/lib/common.sh).

3. **Canonical Schema Validation** ([`20-validate-cli-marketplace.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/20-validate-cli-marketplace.sh)): Invokes `claude plugin validate <marketplace.json>` to verify upstream schema compliance.

4. **External Plugin Validation** ([`30-validate-cli-external.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/30-validate-cli-external.sh)): Clones each external repository into a temporary directory, validates the SHA against an allow-list, applies SSRF host filtering, and runs CLI checks on the cloned manifest.

5. **Local Plugin Validation** ([`40-validate-cli-local.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/40-validate-cli-local.sh)): Validates every changed in-repo plugin folder for structural integrity.

6. **Auxiliary File Checks** ([`41-validate-aux-files.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/41-validate-aux-files.sh)): Parses JSON auxiliary files for syntax errors and schema compliance.

7. **Reporting** ([`90-report.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/90-report.sh)): Aggregates results into a markdown report and sets action outputs including `changed-entries`, `changed-external`, `changed-folders`, `result`, and `report-path`.

All scripts source [`.github/actions/validate-plugins/lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/lib/common.sh), which provides safe-path assertions, host allow-list verification, and security guard functions.

## Security Model and SSRF Protection

The action implements a zero-trust security model for external plugin validation:

**SSRF Guard**: All URLs undergo vetting against the `allowed-hosts` configuration before any `git clone` operation executes.

**SHA Pinning Enforcement**: External plugins must specify an immutable 40-character lowercase hexadecimal SHA (Invariant I5). The action validates this format before cloning and verifies the checked-out commit matches the declared SHA.

**No Code Execution**: Only the static `claude plugin validate` binary executes against cloned repositories. The action never runs plugin code or build scripts from external submissions.

**Shell Safety**: All interpolated variables are double-quoted, and git commands use `--` end-of-options markers to prevent argument injection.

## Workflow Implementation Examples

### Standard Pull Request Validation

Add this to [`.github/workflows/validate-plugins.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/workflows/validate-plugins.yml) to gate PRs touching plugin files:

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

```

This configuration automatically runs all eleven checks when plugin-related files change.

### Nightly Drift Detection

Detect external repository rewrites or deletions that would invalidate pinned SHAs:

```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"

```

Setting `validate-all-external: "true"` forces step 30 to validate every external entry regardless of PR changes, catching forced pushes or repository deletions.

### Per-File Repository Structure

For marketplaces where each plugin lives in its own JSON file:

```yaml
- uses: anthropics/claude-plugins-community/.github/actions/validate-plugins@<PINNED-SHA>
  with:
    marketplace-path: .claude-plugin/marketplace.json
    entries-dir: .claude-plugin/plugins

```

This enables Invariants I6 and I7, validating that filenames match the `name` field and preventing direct edits to the aggregated marketplace file.

## Summary

- The `validate-plugins` action combines **canonical schema validation** via the `claude plugin validate` CLI with **organization-specific security invariants** (I1-I9).
- Execution flows through seven distinct steps (00, 11, 20, 30, 40, 41, 90) managed by shell scripts in `.github/actions/validate-plugins/scripts/`.
- **Security controls** include SSRF filtering, mandatory SHA pinning, and shell-escaping to prevent injection attacks.
- **Diff-gating** optimizes CI performance by validating only changed entries unless `validate-all-external` is enabled.
- Helper utilities in [`lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/lib/common.sh) provide shared validation logic across all scripts.

## Frequently Asked Questions

### What input parameters does the validate-plugins action accept?

The action accepts `marketplace-path` (required), `entries-dir` (optional, enables per-file invariants), `validate-all-external` (boolean string to force full external validation), and `skip-local-folders` (boolean string to bypass in-repo checks). These parameters control which validation layers execute and against which file sets.

### How does the action prevent SSRF attacks during external plugin cloning?

The action vets all URLs against an `allowed-hosts` allow-list before executing `git clone` operations in [`scripts/30-validate-cli-external.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/scripts/30-validate-cli-external.sh). Additionally, it enforces HTTPS-only URLs (Invariant I3) and validates that external plugins use immutable 40-character SHA references rather than mutable branch names.

### What is the difference between step 20 and step 30 in the validation pipeline?

Step 20 ([`20-validate-cli-marketplace.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/20-validate-cli-marketplace.sh)) validates the root [`marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/marketplace.json) file against the upstream Zod schema using `claude plugin validate`. Step 30 ([`30-validate-cli-external.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/30-validate-cli-external.sh)) clones individual external plugin repositories and validates each plugin's own manifest file (or a synthesized manifest for skills-only plugins) to ensure external submissions match the schema before inclusion in the marketplace.

### Where are the invariant rules I1-I9 defined and how can I view them?

The invariants are documented in [`.github/actions/validate-plugins/README.md`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/README.md) under the "Invariants Reference" section. The implementation logic resides in [`scripts/11-validate-invariants.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/scripts/11-validate-invariants.sh) and [`lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/lib/common.sh), where functions like safe-path assertions and sorting validators enforce these static rules against the marketplace JSON.