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:
- Static pin check – runs without authentication, detects floating launchers
- 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 executoruvx– uv package runnerbunx– Bun package executorpipx– 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
::warningannotations for each unpinned launcher found - Produces JSON outputs
pin-scannedandpin-failedfor 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 viapolicy-promptinput) - 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 statussummary– human-readable assessmentviolations – specific policy failures
Results merge with static pin-check data, then emit GitHub annotations:
::warningfor policy failures (default, non-blocking)::errorwhenfail-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:
- Step summary table – pass/fail status, unpinned launcher flags, network/install indicators, and summaries per plugin
- Three structured outputs (
.github/actions/scan-plugins/action.ymllines 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-pluginsGitHub 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-findingsandfail-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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →