# How the SSRF Guard Works in common.sh for Claude Plugin Validation

> Discover how the SSRF guard in common.sh protects Claude plugins. Learn about URL sanitization, HTTPS enforcement, and host allow-listing for secure CI validation.

- Repository: [Anthropic/claude-plugins-community](https://github.com/anthropics/claude-plugins-community)
- Tags: internals
- Published: 2026-08-30

---

**The SSRF guard validates external URLs through a three-step process—sanitizing input strings, enforcing HTTPS-only patterns with strict regex validation, and verifying hosts against an allow-list while blocking raw IP addresses—to prevent Server-Side Request Forgery attacks during CI validation.**

The **SSRF (Server-Side Request Forgery) guard** is a critical security mechanism implemented in the `anthropics/claude-plugins-community` repository. Located in [`.github/actions/validate-plugins/lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/lib/common.sh), this validation layer ensures that malicious plugins cannot force the GitHub Actions runner to make requests to internal metadata endpoints or unauthorized internal services during the validation workflow.

## The Three-Layer Validation Architecture

The guard operates through sequential validation functions that progressively restrict what constitutes a permissible URL. Each layer aborts execution immediately upon detecting unsafe input.

### Layer 1: String Sanitization with assert_safe_string

Before any URL parsing occurs, the script validates that the input contains no shell metacharacters. The `assert_safe_string` function (lines 47-51 in [`common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/common.sh)) wraps the predicate `has_unsafe_chars` (lines 37-44) to detect quotes, backticks, semicolons, and other injection vectors.

```bash

# From common.sh - preliminary input validation

assert_safe_string "$USER_INPUT"  # Rejects strings with shell metacharacters

```

If `has_unsafe_chars` detects dangerous characters, the script terminates before the URL reaches any network-facing logic.

### Layer 2: HTTPS Pattern Enforcement

The `assert_safe_url` function (lines 53-78) serves as the primary SSRF filter. It enforces two strict requirements:

- **Protocol restriction**: The URL must begin with `https://`
- **Character whitelist**: Only alphanumerics, dots, slashes, underscores, and hyphens are permitted via the regex `^https://[A-Za-z0-9./_-]+$`

```bash

# Line 58-60 in common.sh - pattern validation

if [[ ! "$url" =~ ^https://[A-Za-z0-9./_-]+$ ]]; then
  die "URL contains invalid characters or protocol: $url"
fi

```

This regex prevents URL encoding attacks, credential embedding (`https://user:pass@host`), and protocol downgrade attempts.

### Layer 3: Host Verification and IP Blocking

After pattern validation, the guard extracts the hostname (lines 62-64) and applies two security policies:

**Block bare IP addresses**: The script rejects any host matching IPv4 patterns (`^[0-9.]+$`) or containing colons (potential IPv6) at line 65. This prevents access to cloud metadata endpoints like `169.254.169.254`.

**Allow-list enforcement**: The environment variable `ALLOWED_HOSTS` (populated via [`action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/action.yml)) must contain the target host. The implementation iterates through space-separated entries (lines 70-74) and supports wildcard subdomains (`*.example.com`):

```bash

# Lines 70-74 - allow-list iteration

for allowed in $ALLOWED_HOSTS; do
  if [[ "$host" == "$allowed" ]] || [[ "$allowed" == \*.* && "$host" == ${allowed:1} ]]; then
    return 0  # Host explicitly permitted

  fi
done
die "Host $host not in allowed list"  # Line 76

```

## Error Handling with die() and record_result()

Every validation failure triggers the `die` function (lines 13-14), which logs the error through `record_result` using a "fatal" status and exits the script immediately. This design ensures that partial failures cannot bypass validation, and the CI output clearly identifies which specific URL triggered the rejection.

```bash

# From common.sh - fatal error reporting

die() {
  record_result "fatal" "$1"
  exit 1
}

```

## Usage in Plugin Validation Workflows

Validation scripts source [`common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/common.sh) and invoke `assert_safe_url` before any network operations:

```bash
#!/bin/bash

# In .github/actions/validate-plugins/validate-entrypoint.sh

source "$ACTION_PATH/lib/common.sh"

MANIFEST_URL="${PLUGIN_ENTRY_URL}"
assert_safe_url "$MANIFEST_URL"  # SSRF guard triggers here

# Only reaches this line if URL passes all checks

git clone "$MANIFEST_URL" "$TMP_DIR"

```

This pattern ensures that variables containing user-supplied plugin metadata cannot reach `curl`, `git`, or `wget` commands without sanitization.

## Configuring ALLOWED_HOSTS in action.yml

The allow-list is defined in the action metadata and injected as an environment variable. To permit additional hosts like `example.com`, modify [`.github/actions/validate-plugins/action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/action.yml):

```yaml
inputs:
  ALLOWED_HOSTS:
    description: "Space-separated list of allowed hosts"
    default: "github.com raw.githubusercontent.com example.com"

```

After updating this configuration, the guard accepts URLs such as `https://example.com/plugin.json` while continuing to block unauthorized domains and raw IP addresses.

## Summary

- **Input sanitization**: The `assert_safe_string` function blocks shell injection attempts before URL parsing begins.
- **Protocol enforcement**: Only HTTPS URLs containing alphanumeric, dot, slash, underscore, and hyphen characters pass the regex validation in `assert_safe_url`.
- **IP blocking**: Raw IPv4 and IPv6 addresses are explicitly rejected to prevent cloud metadata service access.
- **Allow-list control**: The `ALLOWED_HOSTS` environment variable restricts valid destinations to explicitly approved domains and subdomains.
- **Fail-safe design**: The `die` function ensures immediate termination with clear error logging when any check fails.

## Frequently Asked Questions

### What is SSRF and why does the Claude plugin validator need protection against it?

SSRF (Server-Side Request Forgery) is an attack where malicious input tricks a server into making unauthorized requests to internal resources. In the context of the `anthropics/claude-plugins-community` validation workflow, an attacker could submit a plugin manifest containing URLs pointing to `http://169.254.169.254/latest/meta-data/` or other internal endpoints, causing the GitHub Actions runner to expose sensitive cloud credentials or internal service data.

### How does the guard distinguish between allowed hosts and malicious URLs?

The guard extracts the hostname from the validated URL and compares it against the space-separated `ALLOWED_HOSTS` list defined in [`action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/action.yml). It checks for exact matches or wildcard subdomain patterns (e.g., `*.github.com`), rejecting any host not explicitly listed. Additionally, it blocks any hostname resembling an IP address—whether IPv4 (`^[0-9.]+$`) or IPv6 (contains `:`)—ensuring that only registered domain names can be accessed.

### Can the allow-list be modified to include private repositories or internal domains?

Yes, administrators can extend the `ALLOWED_HOSTS` input in [`.github/actions/validate-plugins/action.yml`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/action.yml) to include private GitHub Enterprise instances, internal artifact repositories, or other trusted domains. Each addition must be intentional and reviewed, as the default configuration restricts requests to public GitHub infrastructure only (`github.com` and `raw.githubusercontent.com`).

### What happens when a plugin URL fails the SSRF validation check?

When validation fails—whether due to unsafe characters, non-HTTPS protocol, disallowed characters in the URL, IP address usage, or a missing allow-list entry—the `assert_safe_url` function calls `die`. This logs a fatal error via `record_result`, prints a descriptive message to stderr, and exits the script with a non-zero status. The CI workflow then marks the validation check as failed, preventing the malicious plugin from entering the repository.