How the scan-plugins Action Performs Policy Scanning in the Claude Community

The scan-plugins GitHub Action performs automated policy scanning by orchestrating Claude to analyze changed external plugins against Anthropic's safety policies using a structured prompt and JSON schema validation.

The anthropics/claude-plugins-community repository maintains a marketplace of community plugins, and the scan-plugins action ensures every external submission complies with security standards before merging. This action automates what would otherwise be a manual code review process, checking for credential exfiltration, unauthorized network calls, and other safety violations by leveraging the Claude CLI.

Overview of the Policy Scanning Architecture

The policy scanning workflow operates as a Claude-driven policy reviewer that executes within GitHub Actions. According to the source code in .github/actions/scan-plugins/scripts/scan.sh, the action follows a strict pipeline: authenticating with the Anthropic API, resolving which plugins changed in the current pull request, checking out each plugin at its exact commit hash, and invoking Claude with a detailed safety prompt.

The action relies on two critical policy definitions stored in the repository:

  • policy/prompt.md: Human-readable instructions telling Claude what safety criteria to evaluate
  • policy/schema.json: Structural validation rules ensuring Claude returns parseable JSON with required fields like passes, summary, and violations

Authentication and Environment Validation

Before scanning begins, the script validates the runtime environment at lines 7-19 of scan.sh. It first checks for either an ANTHROPIC_API_KEY or a federation rule ID, explicitly unsetting empty variables to prevent the Claude CLI from sending blank authentication headers.

The action also asserts that required inputs are defined:

  • MARKETPLACE_PATH: Path to the marketplace JSON file
  • BASE_REF: Git reference for comparison to detect changes
  • ALLOWED_HOSTS: Permitted domains for external plugin sources
  • SCAN_TIMEOUT_SECS: Maximum duration for individual plugin analysis

# Environment validation from scan.sh

if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
  unset ANTHROPIC_API_KEY  # Prevent empty header errors

fi

# Input validation ensures critical variables are populated

: "${MARKETPLACE_PATH:?}" "${BASE_REF:?}" "${ALLOWED_HOSTS:?}" "${SCAN_TIMEOUT_SECS:?}"

Resolving Scan Targets

The action uses the shared library lib/targets.sh to determine which plugins require scanning. The resolve_scan_targets function extracts {name, source} objects from the marketplace JSON and writes them to targets.json.

Target resolution follows two modes controlled by the SCAN_ALL_EXTERNAL environment variable:

  • Full scan: When SCAN_ALL_EXTERNAL=true, every external plugin entry is targeted
  • Incremental scan: Otherwise, only plugins added or changed relative to the BASE_REF commit are included (lines 30-38 of scan.sh)

# Target resolution logic

source "$(dirname "$0")/../lib/targets.sh"
resolve_scan_targets "$MARKETPLACE_PATH" "$BASE_REF" "$SCAN_ALL_EXTERNAL" > targets.json

Plugin Checkout and Pre-Validation

For each entry in targets.json, the script performs rigorous validation and checkout (lines 71-109 of scan.sh):

  1. URL Safety Check: Validates the plugin source URL against the ALLOWED_HOSTS list, skipping unsafe entries with warnings
  2. SHA Verification: Ensures the target commit hash is present and valid
  3. Subdirectory Handling: Respects optional subdirectory specifications for monorepo-style plugins
  4. Precise Checkout: Clones with --depth 1, fetches the exact SHA, and checks out the specific commit to prevent TOCTOU attacks

# Checkout process for each plugin

git clone --depth 1 "$URL" "$TMPDIR"
git -C "$TMPDIR" fetch --depth 1 origin "$SHA"
git -C "$TMPDIR" checkout "$SHA"

Claude Policy Analysis Execution

Inside the checked-out plugin directory, the action invokes the Claude CLI with restricted tool access and structured output requirements (lines 111-122 of scan.sh):

claude -p "$PROMPT" \
       --bare \
       --allowed-tools "Read,Glob,Grep" \
       --output-format json \
       --json-schema "$SCHEMA"

Key execution parameters:

  • Prompt injection: Loads policy/prompt.md which instructs Claude to examine plugin manifests, skills/ directories, hidden files, and configuration for safety violations
  • Tool restrictions: Limits Claude to read-only operations (Read, Glob, Grep) preventing any execution of untrusted code
  • Schema enforcement: Uses policy/schema.json to enforce output structure containing boolean passes, string summary, and array violations

The prompt specifically directs Claude to identify:

  • External network calls outside allowed domains
  • Credential exfiltration attempts
  • Unauthorized file system access
  • Dynamic code execution risks

Result Parsing and Violation Handling

After Claude returns the analysis, the script parses the JSON using jq and extracts specific fields (lines 26-50 of scan.sh):

  • passes: Boolean indicating overall safety
  • summary: Human-readable assessment
  • violations: Array of specific policy breaches
  • may_make_external_network_calls: Boolean flag for network activity
  • may_download_additional_software: Boolean flag for dynamic installation

If parsing fails, the action logs a warning but continues processing other plugins. Valid results are merged with static pin-check data (unpinned_autoexec_* flags) and appended to the scanned results array.

The fail/warning logic (lines 55-63) determines CI behavior:

  • When a plugin has passes: false, its name is added to the failed list
  • If FAIL_ON_FINDINGS=true, the action emits a GitHub error annotation and fails the workflow
  • If FAIL_ON_FINDINGS=false (default), it emits a warning annotation allowing the workflow to continue

Workflow Integration and Outputs

The action exports three workflow outputs for downstream consumption:

  • scanned: JSON array of all analyzed plugins with their verdicts
  • failed: Array of plugin names that failed policy checks
  • result: Overall pass/fail status

It also generates a GitHub step summary in markdown format, displaying:

  • Pass/fail icons for each plugin
  • External network and software download flags
  • Detailed violation descriptions

# Example workflow integration

- name: Scan plugins for policy compliance
  uses: anthropics/claude-plugins-community/.github/actions/scan-plugins@v1
  with:
    marketplace_path: marketplace.json
    base_ref: ${{ github.event.before }}
    allowed_hosts: 'github.com gitlab.com'
    scan_timeout_secs: 300
    fail_on_findings: true

Summary

  • The scan-plugins action automates security review by invoking Claude CLI against changed external plugins in the marketplace.
  • It validates the runtime environment and ensures the ANTHROPIC_API_KEY is properly configured before processing.
  • Target resolution uses lib/targets.sh to identify either all external plugins or only those modified in the current pull request.
  • Each plugin is checked out at its exact commit hash into a temporary directory for isolated analysis.
  • Claude evaluates plugins using the policy prompt (policy/prompt.md) and returns structured JSON validated against schema.json (policy/schema.json).
  • Results are parsed with jq to extract passes, violations, and safety flags, optionally failing the CI run based on the FAIL_ON_FINDINGS setting.

Frequently Asked Questions

What triggers the scan-plugins action to run?

The action triggers on pull requests that modify the marketplace.json file or external plugin definitions. According to .github/workflows/validate-plugins.yml, it executes as part of the plugin validation pipeline, comparing changes against the BASE_REF to determine which specific plugins require scanning.

How does the action prevent Claude from executing malicious code during scanning?

The action restricts Claude's capabilities using the --allowed-tools flag limited to Read, Glob, and Grep only. This read-only configuration ensures Claude can analyze file contents but cannot execute any code within the checked-out plugin repositories.

What happens if Claude returns invalid JSON or fails to parse the plugin?

If jq cannot parse Claude's output, the action logs a warning message identifying the specific plugin and continues processing the remaining targets. This prevents one malformed response from blocking the entire validation pipeline, though the individual plugin receives no verdict.

Can the scan-plugins action be used with private plugin repositories?

Yes, provided the GitHub Actions runner has appropriate authentication credentials and the repository URLs are included in the ALLOWED_HOSTS list. The action performs standard git clone operations, so it respects the repository access permissions of the workflow environment.

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 →