From dc2bf6b9371153d377595761e7402572e82ec7ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Nov 2025 15:06:15 +0000 Subject: [PATCH] feat: add devcontainer setup for safe Claude Code development Add comprehensive development container configuration that enables: - Python 3.12+ development environment with uv package manager - Claude Code integration for AI-assisted development - Network firewall for safe use of --dangerously-skip-permissions mode The firewall restricts outbound traffic to approved destinations only (GitHub, PyPI, Anthropic API, etc.) while maintaining full development capabilities. This allows developers to safely use Claude Code's dangerous mode within the isolated container environment. --- .devcontainer/Dockerfile | 119 ++++++++++++++++++++++++ .devcontainer/devcontainer.json | 63 +++++++++++++ .devcontainer/init-firewall.sh | 158 ++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/init-firewall.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..940b5dd --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,119 @@ +ARG PYTHON_VERSION=3.12 +FROM python:${PYTHON_VERSION}-bookworm + +ARG TZ +ENV TZ="$TZ" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Core system utilities + less \ + procps \ + sudo \ + man-db \ + unzip \ + # Shell and terminal + zsh \ + fzf \ + # Text editors + vim \ + # Version control + git \ + gh \ + # Network utilities + ca-certificates \ + curl \ + wget \ + dnsutils \ + iproute2 \ + # Firewall and security tools + iptables \ + ipset \ + aggregate \ + # Build tools + make \ + build-essential \ + # Development utilities + jq \ + gnupg2 \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Create non-root user +ARG USERNAME=dev +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ + && echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME + +# Ensure user has access to shared directories +RUN mkdir -p /usr/local/share && \ + chown -R $USERNAME:$USERNAME /usr/local/share + +# Persist zsh history +RUN mkdir /commandhistory \ + && touch /commandhistory/.zsh_history \ + && chown -R $USERNAME /commandhistory + +# Set `DEVCONTAINER` environment variable to help with orientation +ENV DEVCONTAINER=true + +# Create workspace and config directories and set permissions +RUN mkdir -p /workspace /home/$USERNAME/.claude && \ + chown -R $USERNAME:$USERNAME /workspace /home/$USERNAME/.claude + +WORKDIR /workspace + +# Install git-delta +ARG GIT_DELTA_VERSION=0.18.2 +RUN ARCH=$(dpkg --print-architecture) && \ + wget "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ + dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ + rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" + +# Switch to non-root user +USER $USERNAME + +# Install uv +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/home/$USERNAME/.local/bin:$PATH" + +# Set the default shell to zsh +ENV SHELL=/bin/zsh + +# Set the default editor +ENV EDITOR=vim +ENV VISUAL=vim + +# Install and configure zsh with powerline10k theme +ARG ZSH_IN_DOCKER_VERSION=1.2.0 + +RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \ + -t gallifrey \ + -p git \ + -p gh \ + -p fzf \ + -p python \ + -a "export HISTFILE=/commandhistory/.zsh_history" \ + -a "export HISTSIZE=10000" \ + -a "export SAVEHIST=10000" \ + -a "setopt SHARE_HISTORY" \ + -x + +# Install Claude Code using the official installer +RUN curl -fsSL https://claude.ai/install.sh | bash + +# Copy and set up firewall script +COPY init-firewall.sh /usr/local/bin/ +USER root +RUN chmod +x /usr/local/bin/init-firewall.sh && \ + echo "$USERNAME ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/$USERNAME-firewall && \ + chmod 0440 /etc/sudoers.d/$USERNAME-firewall +USER $USERNAME + +# Configure git-delta as default pager for git +RUN git config --global core.pager delta && \ + git config --global interactive.diffFilter "delta --color-only" && \ + git config --global delta.navigate true && \ + git config --global delta.light false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..3ca7ee7 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,63 @@ +{ + "name": "Claude Code Sandbox", + "build": { + "dockerfile": "Dockerfile", + "args": { + "TZ": "${localEnv:TZ:America/New_York}", + "GIT_DELTA_VERSION": "0.18.2", + "ZSH_IN_DOCKER_VERSION": "1.2.0", + "PYTHON_VERSION": "3.12" + } + }, + "runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"], + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code", + "ms-python.python", + "ms-python.vscode-pylance", + "charliermarsh.ruff", + "astral-sh.ty@prerelease", + "tamasfe.even-better-toml" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "python.defaultInterpreterPath": "/workspace/.venv/bin/python", + "python.terminal.activateEnvironment": true, + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "zsh" + } + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + } + } + } + } + }, + "remoteUser": "dev", + "mounts": [ + "source=claude-code-history-${devcontainerId},target=/commandhistory,type=volume", + "source=claude-code-config-${devcontainerId},target=/home/dev/.claude,type=volume" + ], + "containerEnv": { + "CLAUDE_CONFIG_DIR": "/home/dev/.claude", + "POWERLEVEL9K_DISABLE_GITSTATUS": "true" + }, + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", + "workspaceFolder": "/workspace", + "postCreateCommand": "uv sync", + "postStartCommand": "sudo /usr/local/bin/init-firewall.sh", + "waitFor": "postStartCommand" +} diff --git a/.devcontainer/init-firewall.sh b/.devcontainer/init-firewall.sh new file mode 100644 index 0000000..dd1e9f4 --- /dev/null +++ b/.devcontainer/init-firewall.sh @@ -0,0 +1,158 @@ +#!/bin/bash +set -e + +echo "๐Ÿ”ฅ Initializing firewall..." + +# Save Docker DNS nameserver before flushing rules +DOCKER_DNS=$(grep nameserver /etc/resolv.conf | head -n1 | awk '{print $2}') +echo "๐Ÿ“‹ Docker DNS: $DOCKER_DNS" + +# Flush existing rules +echo "๐Ÿงน Flushing existing rules..." +iptables -F +iptables -X +iptables -t nat -F +iptables -t nat -X +iptables -t mangle -F +iptables -t mangle -X + +# Destroy and recreate ipset for allowed domains +ipset destroy allowed-domains 2>/dev/null || true +ipset create allowed-domains hash:ip + +# Function to resolve domain and add to ipset +add_domain() { + local domain=$1 + echo " ๐ŸŒ Resolving $domain..." + local ips=$(dig +short "$domain" @$DOCKER_DNS | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') + if [ -z "$ips" ]; then + echo " โš ๏ธ Warning: Could not resolve $domain" + return + fi + for ip in $ips; do + echo " โœ“ Adding $ip ($domain)" + ipset add allowed-domains "$ip" 2>/dev/null || true + done +} + +# Function to add CIDR range to ipset +add_cidr() { + local cidr=$1 + echo " ๐Ÿ“ฆ Adding CIDR range: $cidr" + ipset add allowed-domains "$cidr" 2>/dev/null || true +} + +echo "๐Ÿ“ Building allowlist..." + +# Add Docker DNS +if [ -n "$DOCKER_DNS" ]; then + echo " ๐Ÿณ Adding Docker DNS: $DOCKER_DNS" + ipset add allowed-domains "$DOCKER_DNS" +fi + +# Fetch and add GitHub IP ranges +echo " ๐Ÿ™ Fetching GitHub IP ranges..." +GITHUB_IPS=$(curl -s https://api.github.com/meta | jq -r '.git[]' 2>/dev/null || echo "") +if [ -n "$GITHUB_IPS" ]; then + for cidr in $GITHUB_IPS; do + add_cidr "$cidr" + done +else + echo " โš ๏ธ Warning: Could not fetch GitHub IPs, adding fallback domains" + add_domain "github.com" + add_domain "api.github.com" +fi + +echo " ๐Ÿค– Adding Anthropic API domains..." +add_domain "api.anthropic.com" +add_domain "claude.ai" + +echo " ๐Ÿ”ง Adding VSCode and development tool domains..." +add_domain "marketplace.visualstudio.com" +add_domain "vscode-sync.trafficmanager.net" +add_domain "update.code.visualstudio.com" +add_domain "vscode.download.prss.microsoft.com" +add_domain "main.vscode-cdn.net" + +echo " ๐Ÿ“ฆ Adding VSCode extension gallery API domains..." +add_domain "anthropic.gallery.vsassets.io" +add_domain "ms-python.gallery.vsassets.io" +add_domain "charliermarsh.gallery.vsassets.io" +add_domain "astral-sh.gallery.vsassets.io" +add_domain "tamasfe.gallery.vsassets.io" + +echo " ๐Ÿ“ฆ Adding VSCode extension CDN domains..." +add_domain "anthropic.gallerycdn.vsassets.io" +add_domain "ms-python.gallerycdn.vsassets.io" +add_domain "charliermarsh.gallerycdn.vsassets.io" +add_domain "astral-sh.gallerycdn.vsassets.io" +add_domain "tamasfe.gallerycdn.vsassets.io" + +echo " ๐Ÿ Adding Python package index..." +add_domain "pypi.org" +add_domain "files.pythonhosted.org" + +echo " ๐ŸŒ Adding additional development resources..." +add_domain "astral.sh" +add_domain "github.com" +add_domain "raw.githubusercontent.com" + +# Detect host network +HOST_NETWORK=$(ip route | grep default | awk '{print $3}' | cut -d'.' -f1-3).0/24 +echo " ๐Ÿ  Host network detected: $HOST_NETWORK" +ipset add allowed-domains "$HOST_NETWORK" 2>/dev/null || true + +echo "๐Ÿ›ก๏ธ Setting up iptables rules..." + +# Set default policies to DROP +iptables -P INPUT DROP +iptables -P FORWARD DROP +iptables -P OUTPUT DROP + +# Allow localhost traffic +iptables -A INPUT -i lo -j ACCEPT +iptables -A OUTPUT -o lo -j ACCEPT + +# Allow established and related connections +iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + +# Allow DNS queries to Docker DNS +iptables -A OUTPUT -p udp -d $DOCKER_DNS --dport 53 -j ACCEPT +iptables -A OUTPUT -p tcp -d $DOCKER_DNS --dport 53 -j ACCEPT + +# Allow SSH (if needed) +iptables -A INPUT -p tcp --dport 22 -j ACCEPT +iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT + +# Allow outbound traffic to allowed domains +iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT + +# Reject everything else with ICMP response for faster feedback +iptables -A OUTPUT -j REJECT --reject-with icmp-host-prohibited + +echo "โœ… Firewall configured successfully!" + +# Verify firewall is working +echo "๐Ÿงช Testing firewall..." + +# Test that we can reach GitHub API (should succeed) +if curl -s --max-time 5 https://api.github.com > /dev/null 2>&1; then + echo " โœ“ GitHub API accessible" +else + echo " โŒ ERROR: Cannot reach GitHub API (this should work)" + echo " โš ๏ธ Firewall verification failed!" +fi + +# Test that we cannot reach example.com (should fail) +if curl -s --max-time 5 https://example.com > /dev/null 2>&1; then + echo " โŒ ERROR: example.com is accessible (should be blocked)" + echo " โš ๏ธ Firewall verification failed!" +else + echo " โœ“ example.com blocked (as expected)" +fi + +echo "๐ŸŽ‰ Firewall initialization complete!" +echo "" +echo "โ„น๏ธ You can now run Claude Code with --dangerously-skip-permissions" +echo " The firewall restricts network access to approved destinations only."