You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
sec(infra): non-root container + capability drop (ADR-0003)
Container ran as root until now; PR #2 acknowledged this in
scripts/entrypoint.sh which still writes to /root/.claude/... The
collab feature gives any participant a PTY inside the container, so
"running as root" means a participant could cat /root/.local/share/opencode/*.sqlite
and read every other user's GitHub OAuth token, or env-print
SESSION_SECRET and the OAuth client secret.
Add a non-root user (uid 10001) and hand the runtime tree over to it.
Dockerfile
+ useradd opencode uid 10001 with /home/opencode as $HOME
+ chown -R 10001:10001 /app /home/opencode /var/opencode
/usr/local/bin/opencode-entrypoint
+ USER opencode + ENV HOME=/home/opencode at the very end so build
stages stay as root (faster, no permission gymnastics during build)
+ runtime dirs moved from /root/{.local,.config,.cache,.claude}
to /home/opencode/{.local,.config,.cache,.claude}
+ plugin cache copied from /root path so the existing
opencode-claude-auth pre-install is preserved across the move
scripts/entrypoint.sh
+ $HOME_DIR derived from $HOME (set by the Dockerfile) — writes
$CLAUDE_CREDENTIALS_JSON to $HOME_DIR/.claude/.credentials.json,
not the hard-coded /root path
docker-compose.yml
+ Volume mounts and bind-mount target moved to /home/opencode/...
+ Healthcheck switched to GET /healthz (ADR-0008) — finer-grained
than GET / which only fails on a fully-dead Bun process
DEPLOYMENT.md
+ ECS task definition gets "user": "10001:10001",
"linuxParameters.capabilities.drop: [ALL]", healthcheck on /healthz
+ EFS volumes wired via Access Points (posixUser 10001/10001) with
transitEncryption=ENABLED
+ One-time chown migration documented (aws ecs run-task with an
entrypoint override) — operator step, not workflow plumbing,
because it runs once per environment lifetime
+ EFS Access Point creation commands + the IAM perms the task role
needs (ClientMount + ClientWrite on the AP ARNs)
Local docker-compose continues to work: named volumes inherit their
uid from the container that first writes to them, so a fresh
"docker compose up -d --build" creates opencode-data as uid 10001 and
the bind-mount of ~/.claude/.credentials.json is read-only.
"Resource": ["<arn of fsap-data>", "<arn of fsap-workspaces>"]
293
+
}
294
+
```
295
+
296
+
`ClientRootAccess` is NOT needed after step 1; the chown is the only
297
+
operation that requires it.
298
+
299
+
**Step 4 — deploy the new task definition** (the one with `"user": "10001:10001"`).
300
+
ECS rolls it in. If the chown was successful, the container starts;
301
+
if not, you'll see "Permission denied" errors on the SQLite open and
302
+
the task crashes — re-run step 1.
303
+
304
+
> Local docker-compose is unaffected: named volumes inherit their uid
305
+
> from the container that first writes to them; a fresh
306
+
> `docker compose up -d --build` creates `opencode-data` as uid 10001.
307
+
217
308
Key points vs. the local `docker-compose.yml`:
218
309
219
310
-`ANTHROPIC_API_KEY` is now a real API key (was `dummy` locally). This bypasses the `opencode-claude-auth` plugin entirely — opencode sees a non-empty key and uses it directly.
0 commit comments