Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ENV TZ="$TZ"
ARG CLAUDE_CODE_VERSION=latest

# Install basic development tools and iptables/ipset
RUN apt-get update && apt-get install -y --no-install-recommends \
RUN apt-get update && apt-get install poppler-utils -y --no-install-recommends \
less \
git \
procps \
Expand Down
171 changes: 154 additions & 17 deletions .devcontainer/init-firewall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
ipset destroy allowed-domains 2>/dev/null || true
ipset destroy allowed-domains-v6 2>/dev/null || true

# Detect IPv6 support. Without matching ip6tables rules, dual-stack networks
# let IPv6 egress bypass the allowlist below entirely. On hosts where IPv6 is
# disabled (e.g. ipv6.disable=1), ip6tables cannot operate - there is no IPv6
# traffic to filter, so skip IPv6 rules rather than fail container startup.
if ip6tables -L -n >/dev/null 2>&1; then
IPV6_ENABLED=true
ip6tables -F
ip6tables -X
ip6tables -t mangle -F 2>/dev/null || true
ip6tables -t mangle -X 2>/dev/null || true
else
IPV6_ENABLED=false
echo "WARNING: ip6tables unavailable - skipping IPv6 firewall rules"
fi

# 2. Selectively restore ONLY internal Docker DNS resolution
if [ -n "$DOCKER_DNS_RULES" ]; then
Expand All @@ -37,8 +53,21 @@ iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Same DNS/SSH/localhost allowances for IPv6
if [ "$IPV6_ENABLED" = true ]; then
ip6tables -A OUTPUT -p udp --dport 53 -j ACCEPT
ip6tables -A INPUT -p udp --sport 53 -j ACCEPT
ip6tables -A OUTPUT -p tcp --dport 22 -j ACCEPT
ip6tables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A OUTPUT -o lo -j ACCEPT
fi

# Create ipset with CIDR support
ipset create allowed-domains hash:net
if [ "$IPV6_ENABLED" = true ]; then
ipset create allowed-domains-v6 hash:net family inet6
fi

# Fetch GitHub meta information and aggregate + add their IP ranges
echo "Fetching GitHub IP ranges..."
Expand All @@ -61,34 +90,105 @@ while read -r cidr; do
fi
echo "Adding GitHub range $cidr"
ipset add allowed-domains "$cidr"
done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | aggregate -q)

# Resolve and add other allowed domains
for domain in \
"registry.npmjs.org" \
"api.anthropic.com" \
"sentry.io" \
"statsig.com" \
"marketplace.visualstudio.com" \
"vscode.blob.core.windows.net" \
"update.code.visualstudio.com"; do
done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | grep -v ':' | aggregate -q)

if [ "$IPV6_ENABLED" = true ]; then
echo "Processing GitHub IPv6 ranges..."
while read -r cidr; do
if [[ ! "$cidr" =~ ^[0-9a-fA-F:]+/[0-9]{1,3}$ ]]; then
echo "ERROR: Invalid IPv6 CIDR range from GitHub meta: $cidr"
exit 1
fi
echo "Adding GitHub IPv6 range $cidr"
ipset add allowed-domains-v6 "$cidr"
done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | grep ':' | sort -u)
fi

# Resolve and add other allowed domains.
#
# Domains listed in REQUIRED_DOMAINS must resolve - without them the container
# cannot do its job, so failing loudly is correct. Everything else is
# best-effort: telemetry and marketplace endpoints come and go, and a single
# NXDOMAIN there used to abort the whole script and leave the container
# unusable. Those are now skipped with a warning.
REQUIRED_DOMAINS=(
"api.anthropic.com"
"registry.npmjs.org"
)

OPTIONAL_DOMAINS=(
"sentry.io"
"statsig.com"
"marketplace.visualstudio.com"
"vscode.blob.core.windows.net"
"update.code.visualstudio.com"
)

skipped_domains=()

for domain in "${REQUIRED_DOMAINS[@]}" "${OPTIONAL_DOMAINS[@]}"; do
required=false
for req in "${REQUIRED_DOMAINS[@]}"; do
if [ "$domain" = "$req" ]; then
required=true
break
fi
done

echo "Resolving $domain..."
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}' || true)
if [ -z "$ips" ]; then
echo "ERROR: Failed to resolve $domain"
exit 1
if [ "$required" = true ]; then
echo "ERROR: Failed to resolve required domain $domain"
exit 1
fi
echo "WARNING: Failed to resolve $domain - skipping (not required)"
skipped_domains+=("$domain")
continue
fi

while read -r ip; do
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "ERROR: Invalid IP from DNS for $domain: $ip"
exit 1
if [ "$required" = true ]; then
echo "ERROR: Invalid IP from DNS for $domain: $ip"
exit 1
fi
echo "WARNING: Invalid IP from DNS for $domain: $ip - skipping"
continue
fi
echo "Adding $ip for $domain"
ipset add allowed-domains "$ip"
done < <(echo "$ips")

# Also add AAAA records so allowed domains work first-class over IPv6.
# A missing AAAA record is not an error: IPv6 attempts are rejected fast
# below and clients fall back to IPv4.
if [ "$IPV6_ENABLED" = true ]; then
ipv6s=$(dig +noall +answer AAAA "$domain" | awk '$4 == "AAAA" {print $5}' || true)
while read -r ip; do
if [ -n "$ip" ]; then
if [[ ! "$ip" =~ ^[0-9a-fA-F:]+$ ]]; then
if [ "$required" = true ]; then
echo "ERROR: Invalid IPv6 from DNS for $domain: $ip"
exit 1
fi
echo "WARNING: Invalid IPv6 from DNS for $domain: $ip - skipping"
continue
fi
echo "Adding $ip for $domain (IPv6)"
ipset add allowed-domains-v6 "$ip"
fi
done < <(echo "$ipv6s")
fi
done

if [ ${#skipped_domains[@]} -gt 0 ]; then
echo "NOTE: ${#skipped_domains[@]} optional domain(s) could not be resolved and were not allowlisted:"
for domain in "${skipped_domains[@]}"; do
echo " - $domain"
done
fi

# Get host IP from default route
HOST_IP=$(ip route | grep default | cut -d" " -f3)
if [ -z "$HOST_IP" ]; then
Expand Down Expand Up @@ -118,6 +218,32 @@ iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
# Explicitly REJECT all other outbound traffic for immediate feedback
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited

# IPv6: same default-deny posture, so IPv6 cannot bypass the IPv4 allowlist
if [ "$IPV6_ENABLED" = true ]; then
# Link-local and ICMPv6 are required for neighbor discovery; without
# them IPv6 breaks entirely, even for allowed destinations
ip6tables -A INPUT -s fe80::/10 -j ACCEPT
ip6tables -A OUTPUT -d fe80::/10 -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
ip6tables -A OUTPUT -p ipv6-icmp -j ACCEPT

# Set default policies to DROP
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT DROP

# Allow established connections for already approved traffic
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Then allow only specific outbound traffic to allowed domains
ip6tables -A OUTPUT -m set --match-set allowed-domains-v6 dst -j ACCEPT

# REJECT (not DROP) so blocked IPv6 attempts fail fast and clients
# fall back to IPv4 instead of hanging
ip6tables -A OUTPUT -j REJECT --reject-with icmp6-adm-prohibited
fi

echo "Firewall configuration complete"
echo "Verifying firewall rules..."
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
Expand All @@ -127,6 +253,17 @@ else
echo "Firewall verification passed - unable to reach https://example.com as expected"
fi

# Verify the block also holds over IPv6 (in IPv4-only environments curl -6
# cannot connect at all, so this check passes there too)
if [ "$IPV6_ENABLED" = true ]; then
if curl -6 --connect-timeout 5 https://example.com >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed - was able to reach https://example.com over IPv6"
exit 1
else
echo "Firewall verification passed - unable to reach https://example.com over IPv6 as expected"
fi
fi

# Verify GitHub API access
if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed - unable to reach https://api.github.com"
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/log-issue-events.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ jobs:
permissions:
issues: read
steps:
- name: Log issue creation to Statsig
- name: Log issue event to Statsig
env:
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
EVENT_NAME: ${{ github.event.action == 'closed' && 'github_issue_closed' || 'github_issue_created' }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
ISSUE_TITLE: ${{ github.event.issue.title }}
AUTHOR: ${{ github.event.issue.user.login }}
CREATED_AT: ${{ github.event.issue.created_at }}
CLOSED_AT: ${{ github.event.issue.closed_at }}
run: |
# All values are now safely passed via environment variables
# No direct templating in the shell script to prevent injection attacks
Expand All @@ -27,14 +29,15 @@ jobs:
-H "statsig-api-key: $STATSIG_API_KEY" \
-d '{
"events": [{
"eventName": "github_issue_created",
"eventName": "'"$EVENT_NAME"'",
"metadata": {
"issue_number": "'"$ISSUE_NUMBER"'",
"repository": "'"$REPO"'",
"title": "'"$(echo "$ISSUE_TITLE" | sed "s/\"/\\\\\"/g")"'",
"author": "'"$AUTHOR"'",
"created_at": "'"$CREATED_AT"'"
"created_at": "'"$CREATED_AT"'",
"closed_at": "'"$CLOSED_AT"'"
},
"time": '"$(date +%s)000"'
}]
}'
}'
3 changes: 2 additions & 1 deletion examples/gateway/aws/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ DOCKERFILE="${DOCKERFILE:-./Dockerfile}"
CLAUDE_BINARY="${CLAUDE_BINARY:-./claude}" # prebuilt linux-x64 Claude Code release binary (includes the gateway subcommand)
DIST_URL="${DIST_URL:-}" # optional: download URL, used only if $CLAUDE_BINARY is missing
DIST_SHA256="${DIST_SHA256:-}" # REQUIRED with DIST_URL: expected sha256 of the binary (verified fail-closed)
DIST_SHA256="${DIST_SHA256,,}" # normalize to lowercase — openssl emits lowercase hex; some tools (PowerShell Get-FileHash) publish uppercase
# normalize to lowercase — openssl emits lowercase hex; some tools (PowerShell Get-FileHash) publish uppercase
DIST_SHA256="$(printf '%s' "${DIST_SHA256}" | LC_ALL=C tr '[:upper:]' '[:lower:]')"
# Obtain DIST_SHA256 out-of-band — never from the server that serves DIST_URL.
# For binaries from the standard Claude Code release channel, verify the
# release's GPG-signed manifest.json and copy the platform checksum from it:
Expand Down
2 changes: 1 addition & 1 deletion examples/gateway/gcp/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
set -euo pipefail

# ---- configuration (env-overridable) ----------------------------------------
PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project 2>/dev/null)}"
PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project 2>/dev/null || true)}"
REGION="${REGION:-${CLOUDSDK_COMPUTE_REGION:-us-east5}}" # guide §1 uses us-east5 (Agent Platform model region)

SA_NAME="${SA_NAME:-claude-gateway}" # §2 service account
Expand Down
1 change: 1 addition & 0 deletions examples/settings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ These may be applied at any level of the [settings hierarchy](https://code.claud
- Settings files must be valid JSON
- Before deploying configuration files to your organization, test them locally by applying to `managed-settings.json`, `settings.json` or `settings.local.json`
- The `sandbox` property only applies to the `Bash` tool; it does not apply to other tools (like Read, Write, WebSearch, WebFetch, MCPs), hooks, or internal commands
- The sandbox is available on macOS, Linux, and WSL2 only. When it cannot initialize (e.g. native Windows, or Linux hosts without bubblewrap), Claude Code warns and runs Bash commands unsandboxed unless `failIfUnavailable` is set to `true` — [`settings-bash-sandbox.json`](./settings-bash-sandbox.json) sets it so the sandbox requirement fails closed

## Deploying via MDM

Expand Down
1 change: 1 addition & 0 deletions examples/settings/settings-bash-sandbox.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"allowManagedPermissionRulesOnly": true,
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"excludedCommands": [],
Expand Down
2 changes: 1 addition & 1 deletion plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Learn more in the [official plugins documentation](https://docs.claude.com/en/do
| [plugin-dev](./plugin-dev/) | Comprehensive toolkit for developing Claude Code plugins with 7 expert skills and AI-assisted creation | **Command:** `/plugin-dev:create-plugin` - 8-phase guided workflow for building plugins<br>**Agents:** `agent-creator`, `plugin-validator`, `skill-reviewer`<br>**Skills:** Hook development, MCP integration, plugin structure, settings, commands, agents, and skill development |
| [pr-review-toolkit](./pr-review-toolkit/) | Comprehensive PR review agents specializing in comments, tests, error handling, type design, code quality, and code simplification | **Command:** `/pr-review-toolkit:review-pr` - Run with optional review aspects (comments, tests, errors, types, code, simplify, all)<br>**Agents:** `comment-analyzer`, `pr-test-analyzer`, `silent-failure-hunter`, `type-design-analyzer`, `code-reviewer`, `code-simplifier` |
| [ralph-wiggum](./ralph-wiggum/) | Interactive self-referential AI loops for iterative development. Claude works on the same task repeatedly until completion | **Commands:** `/ralph-loop`, `/cancel-ralph` - Start/stop autonomous iteration loops<br>**Hook:** Stop - Intercepts exit attempts to continue iteration |
| [security-guidance](./security-guidance/) | Security reminder hook that warns about potential security issues when editing files | **Hook:** PreToolUse - Monitors 9 security patterns including command injection, XSS, eval usage, dangerous HTML, pickle deserialization, and os.system calls |
| [security-guidance](./security-guidance/) | Security reminder hook that warns about potential security issues when editing files | **Hook:** PostToolUse - Regex pattern warnings (~25 patterns) on Edit/Write/MultiEdit/NotebookEdit, plus an agentic multi-file review triggered by `git commit`/`git push`<br>**Hook:** Stop - LLM review of the full session diff |

## Installation

Expand Down
29 changes: 20 additions & 9 deletions plugins/commit-commands/commands/clean_gone.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,27 @@ You need to execute the following bash commands to clean up stale local branches
3. **Finally, remove worktrees and delete [gone] branches (handles both regular and worktree branches)**
Execute this command:
```bash
# Process all [gone] branches, removing '+' prefix if present
git branch -v | grep '\[gone\]' | sed 's/^[+* ]//' | awk '{print $1}' | while read branch; do
# Process all branches whose upstream has been deleted
git for-each-ref --format='%(refname:short)%09%(upstream:track)' refs/heads | while IFS=$'\t' read -r branch tracking; do
[ "$tracking" = "[gone]" ] || continue
echo "Processing branch: $branch"
# Find and remove worktree if it exists
worktree=$(git worktree list | grep "\\[$branch\\]" | awk '{print $1}')
if [ ! -z "$worktree" ] && [ "$worktree" != "$(git rev-parse --show-toplevel)" ]; then
echo " Removing worktree: $worktree"
git worktree remove --force "$worktree"
fi
# Find the associated worktree using Git's stable machine-readable format
root_worktree=$(git rev-parse --show-toplevel)
current_worktree=
while IFS= read -r -d '' field; do
case "$field" in
"worktree "*)
current_worktree=${field#worktree }
;;
"branch refs/heads/$branch")
if [ -n "$current_worktree" ] && [ "$current_worktree" != "$root_worktree" ]; then
echo " Removing worktree: $current_worktree"
git worktree remove --force "$current_worktree"
fi
break
;;
esac
done < <(git worktree list --porcelain -z)
# Delete the branch
echo " Deleting branch: $branch"
git branch -D "$branch"
Expand All @@ -50,4 +62,3 @@ After executing these commands, you will:
- Provide feedback on which worktrees and branches were removed

If no branches are marked as [gone], report that no cleanup was needed.

Loading
Loading