docker: give each agent its own Gitea hands, and close the cross-approval hole

Phase 5 of docs/agents/GITEA-SETUP.md, plus a correction to Phase 2 that the
runbook could not have known it needed.

gitea-mcp v1.7.0 goes into both images, pinned by the sha256 the release
publishes and smoke-tested with `--version` at build time, so a bad pin fails
the build instead of the agent. Each entrypoint registers it at user scope for
that container's own identity, remove-then-add so a restart is idempotent.

The token is passed BY PATH. `-e GITEA_ACCESS_TOKEN=$(cat …)` would write it in
cleartext into ~/.claude.json, which every session in the container reads;
GITEA_ACCESS_TOKEN_FILE is new in the pinned version and leaves the secret in
its read-only mount. Verified against the binary's own --help, not assumed.

The tool filter stops being an experiment. The names are in the release README:
each agent gets issues, notifications, labels, milestones and pull requests, and
NOT `pull_request_review_write`. That one matters because separate identities
open a hole the runbook did not name: Gitea refuses to let an author approve
their own pull request, and does nothing about sylph-decoder approving
sylph-port's. Two agents could satisfy `required_approvals = 1` between
themselves and then merge, since branch protection blocks pushes to main and
never blocked merges.

Withholding the tool is defence in depth; the controls are in branch protection,
and both docs now say so: approvals whitelisted to the human so an agent's
approval does not count, merges whitelisted to the human so an approved PR is
still merged by a person. Phase 2's check gains the step that actually tests it
-- approve the throwaway PR yourself, then confirm the agent STILL has no merge
button. Without that step, the check passes on an instance where the agents can
merge each other's work.

Also settles two entries on the runbook's own "not verified" list: the tool
filter names, and the Gitea version (1.25.5, whose API schema carries
enable_merge_whitelist and enable_approvals_whitelist under those names).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
This commit is contained in:
2026-09-04 16:39:12 +02:00
parent 59649824f6
commit 034e98eeb0
8 changed files with 295 additions and 34 deletions

View File

@@ -82,6 +82,24 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& npm cache clean --force \
&& rm -rf /var/lib/apt/lists/*
# ── gitea-mcp ────────────────────────────────────────────────────────────────
# The agent's hands on issues, pull requests and notifications — Gitea's own MCP
# server, so there is no second store of truth to drift out of sync with the
# first.
#
# PINNED AND CHECKSUMMED, not "whatever is at that URL today": this binary is
# handed a token that can write to the repository. The checksum is the one
# published in `gitea-mcp_1.7.0_checksums.txt` for the Linux x86_64 asset.
ARG GITEA_MCP_VERSION=1.7.0
ARG GITEA_MCP_SHA256=bbc9a7b462facd3c56b1558ee6054e91f2fca27a2878b5599afddcf57d446b8d
RUN curl -fsSL -o /tmp/gitea-mcp.tar.gz \
"https://gitea.com/gitea/gitea-mcp/releases/download/v${GITEA_MCP_VERSION}/gitea-mcp_Linux_x86_64.tar.gz" \
&& echo "${GITEA_MCP_SHA256} /tmp/gitea-mcp.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/gitea-mcp.tar.gz -C /usr/local/bin gitea-mcp \
&& chmod +x /usr/local/bin/gitea-mcp \
&& rm -f /tmp/gitea-mcp.tar.gz \
&& gitea-mcp --version
# ── The agent user ───────────────────────────────────────────────────────────
# NOT root, and not negotiable: Claude Code refuses --dangerously-skip-permissions
# when it has root privileges. uid/gid 1000 matches the host account so files