How the `scan-plugins` GitHub Action Performs Security Scanning of Claude Plugins

The scan-plugins GitHub Action combines a deterministic static analysis of unpinned package launchers with an AI-driven Claude policy review to secure external Claude Marketplace plugins.

The scan-plugins action, located in the anthropics/claude-plugins-community repository, provides automated security scanning for Claude plugin submissions. It processes entries from the marketplace manifest (.claude-plugin/marketplace.json) and applies both auth-free static checks and authenticated AI analysis to detect security risks before plugins reach users.

Two-Layer Security Architecture

The action implements a defense-in-depth approach with complementary scanning phases:

  1. Static pin check – runs without authentication, detects floating launchers
  2. Claude policy scan – requires Anthropic credentials, performs deep code analysis

This architecture ensures basic security validation even when API keys are unavailable or rate-limited.

Static Pin Check: Auth-Free Launcher Validation

Before any AI review, the action executes scripts/static-pin-check.sh to inspect each plugin's configuration for unpinned auto-execution launchers.

What It Detects

The scan examines .mcp.json (or plugin.json mcpServers entries) for these floating package-manager commands:

  • npx – Node.js package executor
  • uvx – uv package runner
  • bunx – Bun package executor
  • pipx – Python application installer

These launchers resolve code from package registries at runtime, bypassing the pinned source SHA that the marketplace manifest guarantees.

Static Pin Check Behavior

  • Emits ::warning annotations for each unpinned launcher found
  • Produces JSON outputs pin-scanned and pin-failed for downstream processing
  • Hard-fails the job when fail-on-unpinned-autoexec: "true" is set

In .github/actions/scan-plugins/action.yml (lines 45-53), this strict mode is configured:

fail-on-unpinned-autoexec:
  description: 'Fail the job if unpinned auto-execution launchers are detected'
  required: false
  default: 'false'

Authentication: API Key or Workload Identity

The Claude policy scan requires Anthropic credentials, but the action supports two authentication paths:

Method Inputs Required Use Case
Static API key anthropic-api-key Simple setups, single-organization scanning
Workload Identity Federation (WIF) anthropic-federation-rule-id, anthropic-organization-id, anthropic-service-account-id Enterprise environments, no long-lived secrets

If neither method is configured, the AI review skips gracefully while the static pin check still executes (.github/actions/scan-plugins/action.yml lines 16-24).

Claude Policy Scan: Deep Code Analysis

For each target that passes the pin check, the action performs an AI-powered security review through scripts/scan.sh.

Secure Repository Cloning

The action clones plugin repositories at their exact pinned SHA into temporary directories. To prevent SSRF attacks, it enforces an allowlist of hosts (lines 78-86):

ALLOWED_HOSTS=("github.com" "gitlab.com" "bitbucket.org")

Headless Claude CLI Invocation

The scan invokes the Claude CLI (claude -p) with strict operational constraints defined in scripts/scan.sh (lines 17-22):

  • Read-only tools only: Read, Glob, Grep
  • Policy prompt: policy/prompt.md (configurable via policy-prompt input)
  • JSON schema validation: policy/schema.json
  • Timeout control: scan-timeout-secs (default 300 seconds)

Verdict Processing

The action parses Claude's JSON response to extract:

  • passes – boolean approval status
  • summary – human-readable assessment
  • violations – specific policy failures

Results merge with static pin-check data, then emit GitHub annotations:

  • ::warning for policy failures (default, non-blocking)
  • ::error when fail-on-findings: "true" (lines 54-62)

Workflow Configuration Examples

Standard Pull Request Scanning


# .github/workflows/scan-plugins.yml

name: Scan Plugins
on:
  pull_request:
    paths:
      - '.claude-plugin/**'

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: anthropics/claude-plugins-community/.github/actions/scan-plugins@<PINNED-SHA>
        with:
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          fail-on-findings: "true"
          fail-on-unpinned-autoexec: "true"

Static-Only Linting (No AI Review)

- uses: anthropics/claude-plugins-community/.github/actions/scan-plugins@<PINNED-SHA>
  with:
    anthropic-api-key: ""
    fail-on-unpinned-autoexec: "true"

Consuming Scan Outputs

- id: scan
  uses: anthropics/claude-plugins-community/.github/actions/scan-plugins@<PINNED-SHA>
  with:
    anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}

- name: Process results
  run: |
    echo "Full verdicts: ${{ steps.scan.outputs.scanned }}"
    echo "Failed plugins: ${{ steps.scan.outputs.failed }}"
    echo "Overall: ${{ steps.scan.outputs.result }}"

Result Reporting and Outputs

After processing all entries, the action generates:

  1. Step summary table – pass/fail status, unpinned launcher flags, network/install indicators, and summaries per plugin
  2. Three structured outputs (.github/actions/scan-plugins/action.yml lines 84-115):
Output Description
scanned Full JSON array of all verdicts
failed Array of plugin names that failed policy
result Aggregate status: pass, fail, or skipped

Key Source Files

Path Purpose
.github/actions/scan-plugins/action.yml Composite action definition, inputs/outputs
.github/actions/scan-plugins/scripts/scan.sh Core orchestration: target resolution, cloning, CLI invocation
.github/actions/scan-plugins/scripts/static-pin-check.sh Deterministic launcher pinning validation
.github/actions/scan-plugins/policy/prompt.md Default Claude policy prompt
.github/actions/scan-plugins/policy/schema.json Verdict JSON schema

Summary

  • The scan-plugins GitHub Action provides two-layer security scanning: static pin checks (auth-free) plus AI policy review (authenticated)
  • Floating launchers (npx, uvx, bunx, pipx) are flagged as supply-chain risks before runtime
  • Flexible authentication supports API keys or Workload Identity Federation for enterprise environments
  • Configurable strictness allows annotation-only warnings or hard-fail enforcement via fail-on-findings and fail-on-unpinned-autoexec
  • SSRF protection through host allowlisting and pinned-SHA cloning ensures safe repository access
  • All scanning logic is implemented in shell scripts with explicit timeout and read-only tool constraints

Frequently Asked Questions

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

The AI policy scan skips gracefully, but the static pin check still executes. This ensures basic security validation runs on every workflow invocation regardless of authentication state.

Can the action block merges on security findings?

Yes. Set fail-on-findings: "true" to emit ::error annotations that fail the job, or fail-on-unpinned-autoexec: "true" to hard-fail on floating launchers. Use both for maximum enforcement.

How does the action prevent scanning malicious repositories?

The scripts/scan.sh script validates repository hosts against an allowlist (github.com, gitlab.com, bitbucket.org) and clones only at the exact SHA pinned in the marketplace manifest, eliminating tag-rewriting and redirect attacks.

What is the default timeout for Claude policy scans?

The default scan-timeout-secs is 300 seconds (5 minutes). Configure this input for slower or faster analysis based on plugin complexity and your runner constraints.

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 →