How the SSRF Guard Works in common.sh for Claude Plugin Validation
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, 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) wraps the predicate has_unsafe_chars (lines 37-44) to detect quotes, backticks, semicolons, and other injection vectors.
# 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./_-]+$
# 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) must contain the target host. The implementation iterates through space-separated entries (lines 70-74) and supports wildcard subdomains (*.example.com):
# 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.
# From common.sh - fatal error reporting
die() {
record_result "fatal" "$1"
exit 1
}
Usage in Plugin Validation Workflows
Validation scripts source common.sh and invoke assert_safe_url before any network operations:
#!/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:
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_stringfunction 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_HOSTSenvironment variable restricts valid destinations to explicitly approved domains and subdomains. - Fail-safe design: The
diefunction 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. 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 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.
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 →