From 8b8e29e071f1f8cced8b26082becdce3ed941ba0 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 14:07:27 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20feat:=20add=20Woodpecker=20agent=20to=20?= =?UTF-8?q?docker-compose=20stack=20=E2=80=94=20enable=20CI=20pipeline=20e?= =?UTF-8?q?xecution=20(#670)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.example | 1 + BOOTSTRAP.md | 13 +++++++++++++ bin/disinto | 26 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/.env.example b/.env.example index 0ac4b8e..77b52f9 100644 --- a/.env.example +++ b/.env.example @@ -29,6 +29,7 @@ FORGE_BOT_USERNAMES= # [CONFIG] comma-separated bot userna # ── Woodpecker CI ───────────────────────────────────────────────────────── WOODPECKER_TOKEN= # [SECRET] Woodpecker API token WOODPECKER_SERVER=http://localhost:8000 # [CONFIG] Woodpecker server URL +WOODPECKER_AGENT_SECRET= # [SECRET] shared secret for server↔agent auth (auto-generated) # WOODPECKER_REPO_ID — now per-project, set in projects/*.toml [ci] section # Woodpecker Postgres (for direct DB queries) diff --git a/BOOTSTRAP.md b/BOOTSTRAP.md index 74a4e3c..b5c33bf 100644 --- a/BOOTSTRAP.md +++ b/BOOTSTRAP.md @@ -468,6 +468,19 @@ Meanwhile: | Dev-agent churns through issues without waiting for open PRs to land | No single-threaded enforcement | `WAITING_PRS` check in dev-poll holds new work — verify TOML `name` is consistent across invocations | | Label ping-pong (issue reopened then immediately re-closed) | `already_done` handler doesn't close issue | Review dev-agent log; `already_done` status should auto-close the issue | +## Security: Docker Socket Sharing in CI + +The `woodpecker-agent` service mounts `/var/run/docker.sock` to execute `type: docker` CI pipelines. This grants root-equivalent access to the Docker host — any CI pipeline step can run privileged containers, mount arbitrary host paths, or access other containers' data. + +**Mitigations:** + +- **Run disinto in an LXD/VM container, not on bare metal.** When the Docker daemon runs inside an LXD container, LXD's user namespace mapping and resource limits contain the blast radius. A compromised CI step cannot reach the real host. +- **`WOODPECKER_MAX_WORKFLOWS: 1`** limits concurrent CI resource usage, preventing a runaway pipeline from exhausting host resources. +- **`WOODPECKER_AGENT_SECRET`** authenticates the agent↔server gRPC connection. `disinto init` auto-generates this secret and stores it in `.env` (or `.env.enc` when SOPS is available). +- Consider setting `WOODPECKER_BACKEND_DOCKER_VOLUMES` on the agent to restrict which host volumes CI pipelines can mount. + +**Threat model:** PRs are created by the dev-agent (Claude) and auto-reviewed by the review-bot. A crafted backlog issue could theoretically produce a PR whose CI step exploits the Docker socket. The LXD containment boundary is the primary defense — treat the LXD container as the trust boundary, not the Docker daemon inside it. + ## Action Runner — disinto (harb-staging) Added 2026-03-19. Polls disinto repo for `action`-labeled issues. diff --git a/bin/disinto b/bin/disinto index e66fc47..1c8f88f 100755 --- a/bin/disinto +++ b/bin/disinto @@ -189,6 +189,7 @@ services: WOODPECKER_FORGEJO_CLIENT: ${WP_FORGEJO_CLIENT:-} WOODPECKER_FORGEJO_SECRET: ${WP_FORGEJO_SECRET:-} WOODPECKER_HOST: http://woodpecker:8000 + WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET:-} WOODPECKER_DATABASE_DRIVER: sqlite3 WOODPECKER_DATABASE_DATASOURCE: /var/lib/woodpecker/woodpecker.sqlite depends_on: @@ -196,6 +197,22 @@ services: networks: - disinto-net + woodpecker-agent: + image: woodpeckerci/woodpecker-agent:latest + restart: unless-stopped + security_opt: + - apparmor=unconfined + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + WOODPECKER_SERVER: woodpecker:9000 + WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET:-} + WOODPECKER_MAX_WORKFLOWS: 1 + depends_on: + - woodpecker + networks: + - disinto-net + dendrite: image: matrixdotorg/dendrite-monolith:latest restart: unless-stopped @@ -1283,6 +1300,15 @@ p.write_text(text) _WP_REPO_ID="" create_woodpecker_oauth "$forge_url" "$forge_repo" + # Generate WOODPECKER_AGENT_SECRET for server↔agent auth + local env_file="${FACTORY_ROOT}/.env" + if ! grep -q '^WOODPECKER_AGENT_SECRET=' "$env_file" 2>/dev/null; then + local agent_secret + agent_secret="$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 40)" + printf 'WOODPECKER_AGENT_SECRET=%s\n' "$agent_secret" >> "$env_file" + echo "Config: WOODPECKER_AGENT_SECRET generated and saved to .env" + fi + # Create labels on remote create_labels "$forge_repo" "$forge_url"