From d76da2973f80408a1dfdec02434ab00e92cf56e2 Mon Sep 17 00:00:00 2001 From: ia bot AR Date: Mon, 11 May 2026 11:24:00 +0000 Subject: [PATCH 1/8] hardening step1- add bun remove bwrap setuid --- CLAUDE.md | 101 ++++++++++++++++++++++++++++++++++ Dockerfile | 81 ++++++++++++++++++--------- README.md | 13 ++--- config/claude-memory-full.md | 30 ++++++---- config/claude-memory-slim.md | 85 +++++++++++++++------------- docker-compose.full.yaml | 6 +- docker-compose.yaml | 13 +++-- docs/configuration.md | 10 ++-- docs/dockerhub-description.md | 5 +- scripts/entrypoint.sh | 2 +- 10 files changed, 248 insertions(+), 98 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e74a1c6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,101 @@ +# CLAUDE.md + +Ce fichier fournit des instructions à Claude Code (claude.ai/code) lorsqu'il travaille sur du code dans ce dépôt. + +## Vue d'ensemble + +HolyClaude est un conteneur Docker préconfiguré qui embarque Claude Code CLI, CloudCLI (interface web), 7 CLI IA, un navigateur Chromium headless, et 50+ outils de développement — le tout en une seule image. + +## Commandes principales + +```bash +# Build de l'image complète +docker build -t holyclaude . + +# Build de l'image slim +docker build --build-arg VARIANT=slim -t holyclaude:slim . + +# Build multi-arch (ARM) +docker buildx build --platform linux/arm64 -t holyclaude . + +# Démarrage rapide (docker compose) +docker compose up -d +``` + +## Structure du projet + +``` +holyclaude/ +├── Dockerfile # Build single-stage (full/slim via ARG VARIANT) +├── docker-compose.yaml # Configuration minimale +├── docker-compose.full.yaml# Configuration complète avec tous les paramètres +├── scripts/ +│ ├── entrypoint.sh # Point d'entrée — remappe UID/GID, bootstrap, handoff s6 +│ ├── bootstrap.sh # Premier démarrage : settings, CLAUDE.md, git, hooks +│ └── notify.py # Notifications via Apprise (Discord, Telegram, etc.) +├── s6-overlay/s6-rc.d/ # Définitions de services s6-overlay +│ ├── cloudcli/ # CloudCLI (:3001), run sous l'utilisateur claude +│ └── xvfb/ # Xvfb (:99), affichage virtuel pour Chromium +├── config/ +│ ├── settings.json # Settings Claude Code par défaut (allowEdits) +│ ├── claude-memory-full.md # Template mémoire pour la variante full +│ └── claude-memory-slim.md # Template mémoire pour la variante slim +├── vendor/artifacts/ # Paquets CloudCLI patchés (vendored) +├── assets/ # Logo et bannière +├── docs/ # Documentation : architecture, configuration, ollama, troubleshooting +└── .github/workflows/ # CI/CD : build & push Docker Hub + GHCR (multi-arch, slim+full) +``` + +## Architecture + +Le conteneur suit ce cycle de vie : + +1. **`entrypoint.sh`** (root) — remappe UID/GID via PUID/PGID, crée les fichiers nécessaires, synchronise `.claude.json`, lance `bootstrap.sh` au premier démarrage, puis `exec /init` vers s6-overlay +2. **`bootstrap.sh`** (premier démarrage uniquement) — copie settings/CLAUDE.md, configure git, crée les hooks Codex/Gemini/Cursor, crée un sentinel (`.holyclaude-bootstrapped`) +3. **s6-overlay** (PID 1) — supervise CloudCLI et Xvfb, redémarre automatiquement en cas de crash + +Services supervisés par s6-overlay : +- **CloudCLI** (`claude-code-ui --port 3001`) — interface web, tourne sous l'utilisateur `claude` avec `WORKSPACES_ROOT=/workspace` +- **Xvfb** (`Xvfb :99 1920x1080x24 -nolisten tcp`) — affichage virtuel pour Chromium headless + +Les variables d'environnement Docker Compose ne passent pas automatiquement à travers s6-setuidgid. Les scripts `run` des services définissent explicitement les variables nécessaires. + +## Patches CloudCLI + +Le Dockerfile applique plusieurs patches au paquet CloudCLI vendored : +- **WebSocket** — préservation du type de frame binaire dans le proxy (Issue #11) +- **Shell** — préservation de la position de défilement (Issue #35) +- **Modèle** — support du modèle Claude personnalisé via SSE/select (Issue #36) + +## Variantes d'image + +Contrôlées par `ARG VARIANT=full` dans le Dockerfile. La variante est stockée dans `/etc/holyclaude-variant` et utilisée par bootstrap pour choisir le template CLAUDE.md. + +| Variante | Paquets supplémentaires | +|----------|------------------------| +| `full` (défaut) | Tous les paquets npm/pip/apt, Azure CLI, Junie, OpenCode | +| `slim` | Noyau uniquement (TypeScript, Python, Playwright, AI CLIs principaux) | + +## CI/CD + +GitHub Actions (`.github/workflows/docker-publish.yml`): +- Déclenché sur les tags `v*` +- Construit et pousse vers Docker Hub (`coderluii/holyclaude`) et GHCR +- Matrix `slim`/`full` avec plateformes `linux/amd64,linux/arm64` +- Tags semver + `latest` (full) / `slim` (slim) +- Met à jour la description Docker Hub + +## Notifications + +Les hooks Claude Code (`Stop`, `PostToolUseFailure`) appellent `notify.py`, qui envoie via Apprise si : +1. Le fichier `~/.claude/notify-on` existe +2. Une variable `NOTIFY_*` est configurée (Discord, Telegram, Slack, Email, etc.) + +## Persistance des données + +| Chemin hôte | Chemin conteneur | Contenu | +|-------------|------------------|---------| +| `./data/claude/` | `/home/claude/.claude/` | Settings, credentials, APIs keys | +| `./workspace/` | `/workspace/` | Code et projets | + +Le fichier `.claude.json` est persisté via un mécanisme de copie boot à boot (les symlinks sont écrasés par Claude Code). diff --git a/Dockerfile b/Dockerfile index 1532f56..4c02cfd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,8 +62,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ sudo \ && rm -rf /var/lib/apt/lists/* -# ---------- bubblewrap setuid (Codex CLI sandbox on restricted kernels) ---------- -RUN chmod u+s /usr/bin/bwrap +# ---------- bubblewrap: setuid intentionally removed for security hardening ---------- +# Codex CLI's sandbox works without setuid in most configurations. # ---------- Full-only system packages ---------- RUN if [ "$VARIANT" = "full" ]; then \ @@ -72,11 +72,11 @@ RUN if [ "$VARIANT" = "full" ]; then \ && rm -rf /var/lib/apt/lists/*; \ fi -# ---------- Azure CLI (full only) ---------- -RUN if [ "$VARIANT" = "full" ]; then \ - curl -sL https://aka.ms/InstallAzureCLIDeb | bash \ - && rm -rf /var/lib/apt/lists/*; \ - fi +# ---------- Azure CLI ---------- +# RUN if [ "$VARIANT" = "full" ]; then \ +# curl -sL https://aka.ms/InstallAzureCLIDeb | bash \ +# && rm -rf /var/lib/apt/lists/*; \ +# fi # ---------- GitHub CLI ---------- RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ @@ -94,30 +94,40 @@ RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen # ---------- Create claude user ---------- # node:22-bookworm-slim already has UID 1000 as 'node' — rename it to 'claude' RUN usermod -l claude -d /home/claude -m node && \ - groupmod -n claude node && \ - echo "claude ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/claude && \ - chmod 0440 /etc/sudoers.d/claude - -# ---------- Claude Code CLI (native installer) ---------- + groupmod -n claude node + +# ---------- bun (JavaScript runtime) ---------- +# Installed via npm for reliability (base image node:22 has npm built-in). +# BUN_INSTALL points to ~/.bun so bun install -g writes user-scoped packages. +RUN npm install -g bun && \ + mkdir -p /home/claude/.bun/bin && \ + ln -sf /usr/local/bin/bun /home/claude/.bun/bin/bun && \ + chown -R claude:claude /home/claude/.bun +ENV BUN_INSTALL=/home/claude/.bun \ + PATH="/home/claude/.bun/bin:${PATH}" + +# ---------- Claude Code CLI (via bun) ---------- # CRITICAL: WORKDIR must be non-root-owned or the installer hangs WORKDIR /workspace USER claude -RUN curl -fsSL https://claude.ai/install.sh | bash +RUN /home/claude/.bun/bin/bun install -g @anthropic-ai/claude-code USER root -ENV PATH="/home/claude/.local/bin:${PATH}" -# ---------- npm global packages (slim — always installed) ---------- -RUN npm i -g \ +# ---------- Global tools via bun (slim — always installed) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g \ typescript tsx \ pnpm \ vite esbuild \ eslint prettier \ serve nodemon concurrently \ dotenv-cli +USER root -# ---------- npm global packages (full only) ---------- +# ---------- Global tools via bun (full only) ---------- +USER claude RUN if [ "$VARIANT" = "full" ]; then \ - npm i -g \ + /home/claude/.bun/bin/bun install -g \ wrangler vercel netlify-cli \ pm2 \ prisma drizzle-kit \ @@ -126,6 +136,7 @@ RUN if [ "$VARIANT" = "full" ]; then \ sharp-cli json-server http-server \ @marp-team/marp-cli @cloudflare/next-on-pages; \ fi +USER root # ---------- Python packages (slim — always installed) ---------- RUN pip install --no-cache-dir --break-system-packages \ @@ -141,7 +152,7 @@ RUN pip install --no-cache-dir --break-system-packages \ # ---------- Python packages (full only) ---------- RUN if [ "$VARIANT" = "full" ]; then \ pip install --no-cache-dir --break-system-packages \ - reportlab weasyprint cairosvg fpdf2 PyMuPDF pdfkit img2pdf \ + reportlab weasyprint cairosvg fpdf2 PyMuPDF pdfkit img2pdf markitdown \ xlsxwriter xlrd \ matplotlib seaborn \ python-pptx \ @@ -149,8 +160,12 @@ RUN if [ "$VARIANT" = "full" ]; then \ httpie; \ fi -# ---------- AI CLI providers ---------- -RUN npm i -g @google/gemini-cli @openai/codex task-master-ai +# ---------- AI CLI providers (via bun, user-level) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g @google/gemini-cli @openai/codex task-master-ai +USER root + +# ---------- Cursor CLI (native installer — no bun/npm package) ---------- USER claude RUN curl -fsSL https://cursor.com/install | bash USER root @@ -162,10 +177,26 @@ RUN if [ "$VARIANT" = "full" ]; then \ fi USER root -# ---------- OpenCode CLI (full only) ---------- +# ---------- OpenCode CLI (full only, via bun) ---------- +USER claude RUN if [ "$VARIANT" = "full" ]; then \ - npm i -g opencode-ai; \ + /home/claude/.bun/bin/bun install -g opencode-ai; \ fi +USER root + +# ---------- uv (Python package manager, user-level) ---------- +USER claude +RUN curl -fsSL https://astral.sh/uv/install.sh | bash +USER root +RUN mkdir -p /home/claude/.bashrc.d && \ + chown claude:claude /home/claude/.bashrc.d && \ + echo 'alias pip="uv pip"' >> /home/claude/.bashrc && \ + echo 'alias python="uv run python3"' >> /home/claude/.bashrc + +# ---------- bun global tools (sift search, go-task) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g sift @go-task/cli +USER root COPY vendor/artifacts/siteboon-claude-code-ui-1.26.3.tgz /tmp/vendor/siteboon-claude-code-ui-1.26.3.tgz @@ -229,9 +260,9 @@ RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/a USER claude RUN mkdir -p /home/claude/.claude-code-ui/plugins && \ git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-starter.git /home/claude/.claude-code-ui/plugins/project-stats && \ - cd /home/claude/.claude-code-ui/plugins/project-stats && npm install && npm run build && \ + cd /home/claude/.claude-code-ui/plugins/project-stats && bun install && bun run build && \ git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-terminal.git /home/claude/.claude-code-ui/plugins/web-terminal && \ - cd /home/claude/.claude-code-ui/plugins/web-terminal && npm install && npm run build && \ + cd /home/claude/.claude-code-ui/plugins/web-terminal && bun install && bun run build && \ echo '{"project-stats":{"name":"project-stats","source":"https://github.com/cloudcli-ai/cloudcli-plugin-starter","enabled":true},"web-terminal":{"name":"web-terminal","source":"https://github.com/cloudcli-ai/cloudcli-plugin-terminal","enabled":true}}' > /home/claude/.claude-code-ui/plugins.json USER root diff --git a/README.md b/README.md index 4f3c637..9036eee 100644 --- a/README.md +++ b/README.md @@ -263,10 +263,9 @@ services: shm_size: 2g # Chromium needs this — don't remove network_mode: bridge cap_add: - - SYS_ADMIN # Required: Chromium sandboxing - SYS_PTRACE # Required: debugging tools - security_opt: - - seccomp=unconfined # Required: Chromium in Docker + # SYS_ADMIN and seccomp=unconfined intentionally removed. + # Chromium runs with --no-sandbox (configured in CHROMIUM_FLAGS). ports: - "3001:3001" # CloudCLI web UI volumes: @@ -295,7 +294,7 @@ Open `http://localhost:3001`. Create a CloudCLI account. Sign in with your Anthr **That's the whole setup. You're done.** -> **Why `SYS_ADMIN` + `seccomp=unconfined`?** Chromium needs these to run inside Docker — it's standard for any containerized browser (Playwright docs, Puppeteer docs, every CI pipeline that runs browser tests). Without them, Chromium crashes on startup. This is not a security risk unique to HolyClaude. +> **Why no `SYS_ADMIN` + `seccomp=unconfined`?** Chromium already runs with `--no-sandbox` (set via `CHROMIUM_FLAGS`), so these extra privileges are not needed. Removing them improves the container's security profile. If you need Chromium's sandbox, re-add `SYS_ADMIN` and a custom seccomp profile. > **Why `shm_size: 2g`?** Docker gives containers 64MB of shared memory by default. Chromium uses `/dev/shm` heavily for tab rendering. At 64MB, tabs crash randomly. 2GB is the recommended minimum for any Chromium-in-Docker setup. @@ -325,10 +324,9 @@ services: shm_size: 2g # Chromium shared memory — increase to 4g for heavy browser use network_mode: bridge cap_add: - - SYS_ADMIN # Required: Chromium sandboxing - SYS_PTRACE # Required: debugging tools (strace, lsof) - security_opt: - - seccomp=unconfined # Required: Chromium syscall requirements + # SYS_ADMIN and seccomp=unconfined intentionally removed. + # Chromium runs with --no-sandbox (configured in CHROMIUM_FLAGS). ports: # # CloudCLI web UI — this is the only port you need. @@ -567,7 +565,6 @@ The full image includes everything above, plus: | `wrangler`, `@cloudflare/next-on-pages` | Cloudflare Workers deployment | | `vercel` | Vercel deployment | | `netlify-cli` | Netlify deployment | -| `az` | Azure CLI for cloud deployment and management | | `prisma`, `drizzle-kit` | The two most popular Node.js ORMs | | `pm2` | Production process manager | | `eas-cli` | Expo / React Native builds | diff --git a/config/claude-memory-full.md b/config/claude-memory-full.md index 4612ca8..20194f0 100644 --- a/config/claude-memory-full.md +++ b/config/claude-memory-full.md @@ -23,16 +23,16 @@ You are running inside a **HolyClaude Docker container** (full variant). Everyth Both managed by s6-overlay — they auto-restart on failure. -## Node.js & npm (v22 LTS) +## Node.js & npm (v22 LTS) + Bun -### Global packages available: +### Global packages available (via bun): - **Languages:** typescript, tsx -- **Package managers:** pnpm, npm (built-in) +- **Package managers:** pnpm, bun (also available), npm (built-in) - **Build tools:** vite, esbuild - **Code quality:** eslint, prettier - **Dev servers:** serve, nodemon, http-server - **Utilities:** concurrently, dotenv-cli -- **Deployment:** wrangler (Cloudflare), vercel, netlify-cli, @cloudflare/next-on-pages, az (Azure) +- **Deployment:** wrangler (Cloudflare), vercel, netlify-cli, @cloudflare/next-on-pages - **Databases:** prisma, drizzle-kit - **Process management:** pm2 - **Mobile:** eas-cli (Expo) @@ -42,11 +42,12 @@ Both managed by s6-overlay — they auto-restart on failure. ### Installing additional packages: ```bash -npm i -g # Global install -npm i # Project-local install +bun install -g # Global install (user-level, preferred over npm -g) +npm i -g # Fallback global install +npm i # Project-local install ``` -## Python 3 +## Python 3 + uv ### Installed packages: - **HTTP:** requests, httpx, httpie @@ -61,11 +62,19 @@ npm i # Project-local install - **Browser:** playwright - **Web framework:** fastapi, uvicorn -### Installing additional packages: +### Package manager: uv +```bash +uv # uv replaces pip and venv +uv pip install # Install packages (alias: `pip` → `uv pip`) +uv run python3