-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathsandbox-entrypoint.sh
More file actions
106 lines (93 loc) · 4.4 KB
/
Copy pathsandbox-entrypoint.sh
File metadata and controls
106 lines (93 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/bin/sh
# Docker sandbox entrypoint for MulmoClaude.
#
# Runs as root to perform setup steps that require elevated privileges,
# then drops to the host user's UID:GID via setpriv (part of util-linux,
# already in node:22-slim — no extra install).
#
# Why not just `--user ${UID}:${GID}`?
# That flag runs the ENTIRE container (including this entrypoint)
# as the non-root user, which prevents writing to /etc/passwd and
# fixing socket permissions. The entrypoint-then-drop pattern is
# the standard Docker solution for "I need root setup but non-root
# runtime". See #259 for the full motivation.
set -e
# When HOST_UID is unset, the container was started with `--user`
# (non-SSH mode). Skip all root setup and exec directly — we're
# already running as the target user with zero capabilities.
if [ -z "${HOST_UID:-}" ]; then
exec "$@"
fi
TARGET_UID="$HOST_UID"
TARGET_GID="${HOST_GID:-1000}"
# 1. Add a /etc/passwd entry for the target UID if it doesn't exist.
# SSH refuses to operate when the running user has no passwd entry
# ("No user exists for uid NNN"). On macOS the host UID is
# typically 501, which isn't in the container's passwd (only root=0
# and node=1000 are).
if ! getent passwd "$TARGET_UID" > /dev/null 2>&1; then
echo "sandbox:x:${TARGET_UID}:${TARGET_GID}::/home/node:/bin/sh" >> /etc/passwd
fi
# 1b. Ensure /home/node is writable by the target user.
# The base image (node:22-slim) creates this directory owned by
# node:node (1000:1000). When running as a different UID (e.g. 501
# on macOS), Claude CLI and git/ssh can't create config files
# there (.ssh/, .gitconfig, etc.) without this chown.
chown -R "$TARGET_UID:$TARGET_GID" /home/node 2>/dev/null || true
# 2. Make the SSH agent socket accessible to the target user.
# Docker Desktop for Mac's magic socket (/run/host-services/
# ssh-auth.sock) is created as root:root mode 660. The target UID
# (e.g. 501) has no group membership to read it. A broad chmod is
# acceptable here because we're inside an isolated container with
# --cap-drop ALL — there's no other user to protect against.
if [ -S "${SSH_AUTH_SOCK:-}" ]; then
chmod 666 "$SSH_AUTH_SOCK" 2>/dev/null || true
fi
# 3. Restrict SSH agent usage to whitelisted hosts only.
# Without this, the container could use the forwarded agent to
# authenticate to ANY SSH server the host has access to (production
# servers, private infrastructure, etc.). The SSH config sets
# `IdentityAgent none` as the default and only enables the agent
# for explicitly allowed hosts.
#
# SANDBOX_SSH_ALLOWED_HOSTS: comma-separated list of hostnames.
# Default: github.com (the most common use case for git+ssh).
# Example: github.com,gitlab.com,bitbucket.org
if [ -S "${SSH_AUTH_SOCK:-}" ]; then
ALLOWED_HOSTS="${SANDBOX_SSH_ALLOWED_HOSTS:-github.com}"
SSH_DIR="/home/node/.ssh"
SSH_CONFIG="${SSH_DIR}/config"
mkdir -p "$SSH_DIR"
# SSH uses first-match-wins, so whitelisted hosts MUST come BEFORE
# the catch-all `Host *` block. Otherwise `IdentityAgent none`
# matches first and the per-host overrides are never reached.
cat > "$SSH_CONFIG" <<SSHEOF
# Auto-generated by sandbox-entrypoint.sh — do not edit.
# Restrict SSH agent forwarding to whitelisted hosts only.
# Override via SANDBOX_SSH_ALLOWED_HOSTS env var.
SSHEOF
# Per-host overrides FIRST (most-specific → least-specific)
echo "$ALLOWED_HOSTS" | tr ',' '\n' | while read -r host; do
host=$(echo "$host" | tr -d ' ')
if [ -n "$host" ]; then
printf "Host %s\n IdentityAgent %s\n StrictHostKeyChecking accept-new\n\n" "$host" "$SSH_AUTH_SOCK" >> "$SSH_CONFIG"
fi
done
# Catch-all LAST: block agent for everything else
cat >> "$SSH_CONFIG" <<SSHEOF
Host *
IdentityAgent none
StrictHostKeyChecking accept-new
SSHEOF
chmod 700 "$SSH_DIR"
chmod 600 "$SSH_CONFIG"
chown -R "${TARGET_UID}:${TARGET_GID}" "$SSH_DIR"
fi
# 4. Drop privileges and exec the actual command (typically `claude`).
# setpriv is part of util-linux, already present in node:22-slim.
# --init-groups initialises supplementary groups from /etc/group.
# --inh-caps=-all clears inheritable capabilities so the child
# process (claude) runs with zero capabilities even though the
# container was started with CHOWN/FOWNER/DAC_OVERRIDE/SETUID/SETGID
# for the entrypoint's setup steps.
exec setpriv --reuid="$TARGET_UID" --regid="$TARGET_GID" --init-groups --inh-caps=-all -- "$@"