From 235b05ad9be8af17751d265b97d73e9e3f20d91c Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 10 Feb 2026 06:34:14 -0500 Subject: [PATCH] Harden devcontainer: pin digests, guard SYS_ADMIN, fix mount filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback from #14–#19: - Pin base image and uv with SHA256 digests (#15) - Install fzf from GitHub releases instead of apt (#14) - Add SYS_ADMIN capability guard to prevent remount attacks (#16) - Fix mount filter to use target paths instead of source prefixes (#17) - Fix temp file leak in extract_mounts_to_file (#17) - Sort apt packages alphabetically (#18) - Remove unused Tailscale feature (#19) Co-Authored-By: Claude Opus 4.6 --- .zshrc | 5 ++--- Dockerfile | 31 ++++++++++++++++++------------- devcontainer.json | 3 +-- install.sh | 28 ++++++++++++++++++++++------ 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/.zshrc b/.zshrc index 40918d8..3edf163 100644 --- a/.zshrc +++ b/.zshrc @@ -51,6 +51,5 @@ _fzf_compgen_dir() { fdfind --type d --hidden --follow --exclude .git . "$1" } -# Source fzf key bindings and completion -source ~/.fzf/key-bindings.zsh -source ~/.fzf/completion.zsh +# Source fzf shell integration (built-in since fzf 0.48+) +eval "$(fzf --zsh)" diff --git a/Dockerfile b/Dockerfile index 77a5778..4a4aed1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ # Claude Code Devcontainer # Based on Microsoft devcontainer image for better devcontainer integration -FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 +ARG UV_VERSION=0.10.0 +FROM ghcr.io/astral-sh/uv:${UV_VERSION}@sha256:78a7ff97cd27b7124a5f3c2aefe146170793c56a1e03321dd31a289f6d82a04f AS uv +FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04@sha256:d94c97dd9cacf183d0a6fd12a8e87b526e9e928307674ae9c94139139c0c6eae ARG TZ ENV TZ="$TZ" @@ -13,9 +15,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ bubblewrap \ socat \ # Modern CLI tools - fzf \ - ripgrep \ fd-find \ + ripgrep \ tmux \ zsh \ # Build tools @@ -23,13 +24,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Utilities jq \ nano \ - vim \ unzip \ + vim \ # Network tools (for security testing) - iptables \ + dnsutils \ ipset \ + iptables \ iproute2 \ - dnsutils \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install git-delta @@ -40,7 +41,17 @@ RUN ARCH=$(dpkg --print-architecture) && \ rm /tmp/git-delta.deb # Install uv (Python package manager) via multi-stage copy -COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv +COPY --from=uv /uv /usr/local/bin/uv + +# Install fzf from GitHub releases (newer than apt, includes built-in shell integration) +ARG FZF_VERSION=0.67.0 +RUN ARCH=$(dpkg --print-architecture) && \ + case "${ARCH}" in \ + amd64) FZF_ARCH="linux_amd64" ;; \ + arm64) FZF_ARCH="linux_arm64" ;; \ + *) echo "Unsupported architecture: ${ARCH}" && exit 1 ;; \ + esac && \ + curl -fsSL "https://github.com/junegunn/fzf/releases/download/v${FZF_VERSION}/fzf-${FZF_VERSION}-${FZF_ARCH}.tar.gz" | tar -xz -C /usr/local/bin # Create directories and set ownership (combined for fewer layers) RUN mkdir -p /commandhistory /workspace /home/vscode/.claude /opt && \ @@ -88,12 +99,6 @@ RUN sh -c "$(curl -fsSL https://github.com/deluan/zsh-in-docker/releases/downloa -p git \ -x -# Download fzf shell integration (Ubuntu 24.04 package doesn't include these) -ARG FZF_VERSION=0.44.1 -RUN mkdir -p /home/vscode/.fzf && \ - curl -fsSL "https://raw.githubusercontent.com/junegunn/fzf/${FZF_VERSION}/shell/key-bindings.zsh" -o /home/vscode/.fzf/key-bindings.zsh && \ - curl -fsSL "https://raw.githubusercontent.com/junegunn/fzf/${FZF_VERSION}/shell/completion.zsh" -o /home/vscode/.fzf/completion.zsh - # Copy zsh configuration COPY --chown=vscode:vscode .zshrc /home/vscode/.zshrc.custom diff --git a/devcontainer.json b/devcontainer.json index effdf98..c87521e 100644 --- a/devcontainer.json +++ b/devcontainer.json @@ -10,8 +10,7 @@ } }, "features": { - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/tailscale/codespace/tailscale:1": {} + "ghcr.io/devcontainers/features/github-cli:1": {} }, "runArgs": [ "--cap-add=NET_ADMIN", diff --git a/install.sh b/install.sh index decd4b1..05d1041 100755 --- a/install.sh +++ b/install.sh @@ -76,6 +76,19 @@ check_devcontainer_cli() { fi } +check_no_sys_admin() { + local workspace="${1:-.}" + local dc_json="$workspace/.devcontainer/devcontainer.json" + [[ -f "$dc_json" ]] || return 0 + if jq -e \ + '.runArgs[]? | select(test("SYS_ADMIN"))' \ + "$dc_json" >/dev/null 2>&1; then + log_error "SYS_ADMIN capability detected in runArgs." + log_error "This defeats the read-only .devcontainer mount." + exit 1 + fi +} + get_workspace_folder() { echo "${1:-$(pwd)}" } @@ -101,12 +114,11 @@ extract_mounts_to_file() { custom_mounts=$(jq -c ' .mounts // [] | map( select( - (startswith("source=claude-code-bashhistory-") | not) and - (startswith("source=claude-code-config-") | not) and - (startswith("source=claude-code-gh-") | not) and - (startswith("source=${localEnv:HOME}/.gitconfig,") | not) and - # Security: read-only .devcontainer mount prevents container escape on rebuild - (startswith("source=${localWorkspaceFolder}/.devcontainer,") | not) + (contains("target=/commandhistory,") | not) and + (contains("target=/home/vscode/.claude,") | not) and + (contains("target=/home/vscode/.config/gh,") | not) and + (contains("target=/home/vscode/.gitconfig,") | not) and + (contains("target=/workspace/.devcontainer,") | not) ) ) | if length > 0 then . else empty end ' "$devcontainer_json" 2>/dev/null) || true @@ -114,6 +126,8 @@ extract_mounts_to_file() { if [[ -n "$custom_mounts" ]]; then echo "$custom_mounts" >"$temp_file" echo "$temp_file" + else + rm -f "$temp_file" fi } @@ -207,6 +221,7 @@ cmd_up() { workspace_folder="$(get_workspace_folder "${1:-}")" check_devcontainer_cli + check_no_sys_admin "$workspace_folder" log_info "Starting devcontainer in $workspace_folder..." devcontainer up --workspace-folder "$workspace_folder" @@ -218,6 +233,7 @@ cmd_rebuild() { workspace_folder="$(get_workspace_folder "${1:-}")" check_devcontainer_cli + check_no_sys_admin "$workspace_folder" log_info "Rebuilding devcontainer in $workspace_folder..." devcontainer up --workspace-folder "$workspace_folder" --remove-existing-container