# scan-plugins Composite GitHub Action: Automated Safety Scanning for Claude Plugins

> Discover the scan-plugins GitHub Action, an automated tool that inspects Claude plugins for safety and policy compliance before merging. Enhance your plugin security now.

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

---

**The `scan-plugins` composite GitHub Action is a reusable workflow component that automatically inspects Claude-compatible plugins for safety and policy compliance before they are merged, combining deterministic static analysis with optional AI-powered policy verification.**

Maintained in the `anthropics/claude-plugins-community` repository, this action serves as a critical CI gatekeeper for plugin submissions. It validates that external plugin entries meet security standards by checking for floating runtime dependencies and enforcing custom policy rules through Anthropic’s Claude CLI.

## Two-Stage Security Model

The action operates through two distinct stages that run sequentially, allowing secure validation even in environments without API credentials.

### Static Pin Check (Auth-Free)

The first stage performs deterministic analysis on every changed external plugin entry defined in [`.github/actions/scan-plugins/action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/scan-plugins/action.yml) (lines 48-65). For each plugin, the action clones the exact commit at the `pinned SHA` and examines the [`.mcp.json`](https://github.com/anthropics/claude-plugins-community/blob/main/.mcp.json) declaration for **floating launchers** such as `npx`, `uvx`, `bunx`, or `pipx`.

These launchers resolve code from package registries at runtime, introducing supply-chain risks if not properly pinned. If any non-waived floating launcher is detected, the action can hard-fail the job when `fail-on-unpinned-autoexec` is set to `"true"`. This check executes **always**, regardless of Anthropic authentication availability, and emits results via the `pin-scanned` and `pin-failed` JSON outputs.

### Claude Policy Scan (Requires Anthropic Auth)

When an `anthropic-api-key` or Workload Identity Federation (WIF) credentials are supplied, the action proceeds to the second stage defined at lines 65-82 of [`action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/action.yml). This step installs the `@anthropic-ai/claude-code` CLI and executes [`scripts/scan.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/scripts/scan.sh) to evaluate each plugin against a customizable `policy-prompt`.

The CLI returns a structured verdict (`passes: true/false`) for each plugin, with findings reported as inline GitHub annotations and step summaries. The final aggregate result—either `pass` or `fail`—is exposed through the `result` output, while individual scan data flows through `scanned` and `failed` outputs.

## Key Configuration Inputs

The action accepts several parameters that control authentication behavior and failure modes:

- **`anthropic-api-key`** or **`anthropic-federation-rule-id`** – Provide either a static API key or WIF configuration (plus organization and service-account details) to enable the Claude policy scan. If neither is provided, the scan stage skips gracefully while static checks continue (see lines 16-30 of [`action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/action.yml)).
- **`fail-on-findings`** – When set to `"true"`, any policy violation causes the job to fail. By default, the action only annotates without failing.
- **`fail-on-unpinned-autoexec`** – Forces job termination if floating launchers are detected, preventing expensive Claude scans when basic security requirements fail.
- **`policy-prompt`** – Path to a custom markdown file containing the evaluation criteria Claude uses to judge plugin compliance.
- **`allowed-hosts`** – Comma-separated allow-list of git hosts permitted for cloning, acting as an SSRF guard during repository analysis.
- **`marketplace-path`** – Location of the plugin registry file (typically [`.claude-plugin/marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/.claude-plugin/marketplace.json)).

## Outputs and Machine-Readable Results

The action generates five JSON outputs defined in [`action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/action.yml) (lines 84-114), enabling downstream automation and dashboard integration:

- **`pin-scanned`** – Array of plugins analyzed during the static check phase.
- **`pin-failed`** – Array of plugins containing unwaived floating launchers.
- **`scanned`** – Detailed results from the Claude policy evaluation.
- **`failed`** – Specific policy violations detected by the AI scan.
- **`result`** – Aggregate verdict (`pass` or `fail`) suitable for gating merge requirements.

All cloning and analysis operations execute at the pinned SHA, ensuring **deterministic runtime** behavior regardless of upstream repository changes.

## Integration Examples

### Basic Workflow with Full Scanning

Configure your repository to validate plugins on pull requests targeting the plugin registry:

```yaml
name: Validate Plugins
on:
  pull_request:
    paths:
      - '.claude-plugin/**'

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Scan Plugins
        uses: ./.github/actions/scan-plugins
        with:
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          marketplace-path: .claude-plugin/marketplace.json
          fail-on-findings: "true"
          fail-on-unpinned-autoexec: "true"

```

### Consuming JSON Results in Subsequent Steps

Access the structured outputs for custom reporting or conditional logic:

```yaml
- name: Scan Plugins
  id: scan
  uses: ./.github/actions/scan-plugins
  with:
    anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}

- name: Process Results
  run: |
    echo "Overall status: ${{ steps.scan.outputs.result }}"
    echo "Detailed violations:"
    echo '${{ steps.scan.outputs.scanned }}' | jq '.[] | select(.passes == false)'

```

### Auth-Free Static Analysis Only

Run security checks without Anthropic credentials for cost-sensitive or public fork scenarios:

```yaml
- name: Static Security Check
  uses: ./.github/actions/scan-plugins
  with:
    marketplace-path: .claude-plugin/marketplace.json
    fail-on-unpinned-autoexec: "true"
    # No API key supplied; Claude scan is automatically skipped

```

## Core Implementation Files

Understanding the file structure helps customize or debug the action:

- **[`.github/actions/scan-plugins/action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/scan-plugins/action.yml)** – Composite action definition containing inputs, outputs, and the ordered step sequence (setup, static pin check, WIF token minting, Node/CLI installation, and main scan invocation).
- **[`scripts/static-pin-check.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/scripts/static-pin-check.sh)** – Bash script performing the auth-free launcher analysis and generating `pin-scanned`/`pin-failed` data.
- **[`scripts/scan.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/scripts/scan.sh)** – Orchestration script that invokes the Claude CLI against the policy prompt and formats GitHub annotations.
- **[`.github/workflows/validate-plugins.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/workflows/validate-plugins.yml)** – Reference implementation demonstrating production usage patterns.

## Summary

- The **scan-plugins composite GitHub Action** combines static analysis and AI policy verification to secure Claude plugin contributions.
- **Static pin checks** run without authentication, detecting floating launchers like `npx` and `pipx` that could introduce runtime supply-chain risks.
- **Claude policy scans** require Anthropic API keys or WIF credentials to evaluate plugins against customizable markdown prompts.
- **Deterministic execution** ensures all repository analysis occurs at pinned SHAs, preventing upstream tampering during CI execution.
- **Rich JSON outputs** (`pin-scanned`, `pin-failed`, `scanned`, `failed`, `result`) enable integration with security dashboards and automated gating workflows.

## Frequently Asked Questions

### What happens if I don't provide an Anthropic API key?

The action gracefully degrades to static analysis only. According to the source code in [`action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/action.yml) (lines 16-30), when neither `anthropic-api-key` nor `anthropic-federation-rule-id` is present, the Claude policy scan stage skips entirely while the static pin check continues to execute. This ensures you can validate basic security requirements on public forks or cost-constrained environments without incurring API charges.

### How does the action prevent supply-chain attacks via package managers?

The [`scripts/static-pin-check.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/scripts/static-pin-check.sh) component specifically hunts for **floating launchers**—commands like `npx`, `uvx`, `bunx`, and `pipx` that download and execute code from remote registries at runtime. By failing when `fail-on-unpinned-autoexec` is enabled, the action forces contributors to pin exact versions or use waivers, eliminating non-deterministic execution paths before they reach production.

### Can I customize the security policies that Claude uses for evaluation?

Yes. The `policy-prompt` input accepts a file path to a custom markdown document. When provided, the action passes this content to the Claude CLI via [`scripts/scan.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/scripts/scan.sh), allowing repository maintainers to define specific compliance rules regarding data handling, network access, or code quality standards beyond the default security baseline.

### What is the difference between `fail-on-findings` and `fail-on-unpinned-autoexec`?

`fail-on-unpinned-autoexec` triggers failure during the **static analysis phase** when floating launchers are detected, stopping the pipeline before expensive AI scanning begins. `fail-on-findings` controls behavior during the **Claude policy scan**, causing the job to fail when the AI detects violations of the configured policy prompt. You can enable both for defense-in-depth or use them independently based on your risk tolerance.