From aa38fc55ed355534f9e52b3c963b20a4b2b525d3 Mon Sep 17 00:00:00 2001 From: Molecule AI DevOps Engineer Date: Fri, 17 Apr 2026 15:21:35 +0000 Subject: [PATCH] fix(infra): wire ADMIN_TOKEN env placeholder to close issue #684 (PR #729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend Engineer's PR #729 introduces ADMIN_TOKEN — when set, only that value is accepted on /admin/* and /approvals/* routes, replacing the vulnerable workspace-bearer fallback. Without the env var wired into deployments the fix is code-only and the vulnerability stays open in every running instance. Changes: - `docker-compose.yml`: adds ADMIN_TOKEN env var to the platform service (blank default = backward-compat fallback, i.e. still vulnerable until set). NOTE: docker-compose.infra.yml has no platform service — the platform lives only in the full-stack docker-compose.yml, so that is the correct file. - `.env.example`: documents ADMIN_TOKEN with generation instructions and a clear warning that it must be set to close #684. - `infra/scripts/setup.sh`: prints a visible warning when ADMIN_TOKEN is unset so operators know the vulnerability is still open in that deployment. - `CLAUDE.md`: adds ADMIN_TOKEN to the env vars reference section. No Go code changed — go build ./... passes clean. Part of fix for #684 / PR #729 Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 7 +++++++ CLAUDE.md | 2 +- docker-compose.yml | 8 ++++++++ infra/scripts/setup.sh | 11 +++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 05d7dde6..d90f8d07 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,13 @@ REDIS_URL=redis://redis:6379 # Platform PORT=8080 +# ---- Admin credential — REQUIRED to close issue #684 (AdminAuth bearer bypass) ---- +# When ADMIN_TOKEN is set, only this value is accepted on /admin/* and /approvals/* routes. +# Without it, any valid workspace bearer token can call admin endpoints (backward compat +# fallback, still vulnerable). Set this in every environment, rotate when compromised. +# Generate: openssl rand -base64 32 +# Store in fly secrets / deployment env — NEVER commit the actual value here. +ADMIN_TOKEN= SECRETS_ENCRYPTION_KEY= # 32-byte key (raw or base64). Leave empty for plaintext (dev only). CONFIGS_DIR= # Path to workspace-configs-templates/ (auto-discovered if empty) PLUGINS_DIR= # Path to plugins/ directory (default: /plugins in container) diff --git a/CLAUDE.md b/CLAUDE.md index 27c377a0..aedab50f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -143,7 +143,7 @@ go run ./cmd/server # Run server (requires Postgres + Redis running) go build -o molecli ./cmd/cli # Build TUI dashboard ./molecli # Run TUI dashboard (requires platform running) ``` -Must run from `platform/` directory (not repo root). Env vars: `DATABASE_URL`, `REDIS_URL`, `PORT`, `PLATFORM_URL` (default `http://host.docker.internal:PORT` — passed to agent containers so they can reach the platform), `SECRETS_ENCRYPTION_KEY` (optional AES-256, 32 bytes), `CONFIGS_DIR` (auto-discovered), `PLUGINS_DIR` (deprecated — plugins are now installed per-workspace via API; the `plugins/` registry at repo root is auto-discovered), `ACTIVITY_RETENTION_DAYS` (default `7`), `ACTIVITY_CLEANUP_INTERVAL_HOURS` (default `6`), `CORS_ORIGINS` (comma-separated, default `http://localhost:3000,http://localhost:3001`), `RATE_LIMIT` (requests/min, default `600`), `WORKSPACE_DIR` (optional — global fallback host path for `/workspace` bind-mount; overridden by per-workspace `workspace_dir` column in DB; if neither is set, each workspace gets an isolated Docker named volume), `AWARENESS_URL` (optional — if set, injected into workspace containers along with a deterministic `AWARENESS_NAMESPACE` derived from workspace ID), `MOLECULE_IN_DOCKER` (optional — set to `1` when the platform itself runs inside Docker so the A2A proxy rewrites `127.0.0.1:` URLs to container hostnames; auto-detected via `/.dockerenv`), `MOLECULE_ENV` (optional — set to `production` to hide the `/admin/workspaces/:id/test-token` E2E helper endpoint; unset or any other value leaves it enabled), `MOLECULE_ENABLE_TEST_TOKENS` (optional — set to `1` to force-enable the test-token endpoint even when `MOLECULE_ENV=production`; intended for staging runs only), `MOLECULE_ORG_ID` (optional — the public repo's only SaaS hook. When set to a UUID, every non-allowlisted request must carry a matching `X-Molecule-Org-Id` header or gets a 404; when unset, the guard is a passthrough so self-hosted / dev / CI are unaffected. Set only by the private `molecule-controlplane` provisioner on Fly Machines tenant instances — never by self-hosters). +Must run from `platform/` directory (not repo root). Env vars: `DATABASE_URL`, `REDIS_URL`, `PORT`, `ADMIN_TOKEN` (**required to close issue #684** — when set, only this exact value is accepted on all `/admin/*` and `/approvals/*` routes; without it, any valid workspace bearer token passes AdminAuth, which is the #684 vulnerability. Generate: `openssl rand -base64 32`. Never commit the actual value — inject via `fly secrets set` or deployment env. PR #729), `PLATFORM_URL` (default `http://host.docker.internal:PORT` — passed to agent containers so they can reach the platform), `SECRETS_ENCRYPTION_KEY` (optional AES-256, 32 bytes), `CONFIGS_DIR` (auto-discovered), `PLUGINS_DIR` (deprecated — plugins are now installed per-workspace via API; the `plugins/` registry at repo root is auto-discovered), `ACTIVITY_RETENTION_DAYS` (default `7`), `ACTIVITY_CLEANUP_INTERVAL_HOURS` (default `6`), `CORS_ORIGINS` (comma-separated, default `http://localhost:3000,http://localhost:3001`), `RATE_LIMIT` (requests/min, default `600`), `WORKSPACE_DIR` (optional — global fallback host path for `/workspace` bind-mount; overridden by per-workspace `workspace_dir` column in DB; if neither is set, each workspace gets an isolated Docker named volume), `AWARENESS_URL` (optional — if set, injected into workspace containers along with a deterministic `AWARENESS_NAMESPACE` derived from workspace ID), `MOLECULE_IN_DOCKER` (optional — set to `1` when the platform itself runs inside Docker so the A2A proxy rewrites `127.0.0.1:` URLs to container hostnames; auto-detected via `/.dockerenv`), `MOLECULE_ENV` (optional — set to `production` to hide the `/admin/workspaces/:id/test-token` E2E helper endpoint; unset or any other value leaves it enabled), `MOLECULE_ENABLE_TEST_TOKENS` (optional — set to `1` to force-enable the test-token endpoint even when `MOLECULE_ENV=production`; intended for staging runs only), `MOLECULE_ORG_ID` (optional — the public repo's only SaaS hook. When set to a UUID, every non-allowlisted request must carry a matching `X-Molecule-Org-Id` header or gets a 404; when unset, the guard is a passthrough so self-hosted / dev / CI are unaffected. Set only by the private `molecule-controlplane` provisioner on Fly Machines tenant instances — never by self-hosters). **Workspace tier resource limits** (issue #14 — override the per-tier memory/CPU caps in `provisioner.ApplyTierConfig`; CPU_SHARES follows Docker's 1024 = 1 CPU convention, translated to NanoCPUs for a hard cap): - `TIER2_MEMORY_MB` / `TIER2_CPU_SHARES` — Standard tier (defaults `512` / `1024`) diff --git a/docker-compose.yml b/docker-compose.yml index 00431a98..408050a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -136,6 +136,14 @@ services: GITHUB_APP_ID: "${GITHUB_APP_ID:-}" GITHUB_APP_INSTALLATION_ID: "${GITHUB_APP_INSTALLATION_ID:-}" GITHUB_APP_PRIVATE_KEY_FILE: "/secrets/github-app.pem" + # ADMIN_TOKEN — required to fully close issue #684 (AdminAuth bearer bypass, PR #729). + # When set, only this exact value is accepted on all /admin/* and /approvals/* routes; + # workspace bearer tokens are no longer accepted as admin credentials. + # Unset (default) → backward-compat fallback: any valid workspace token passes AdminAuth + # (same behaviour as before PR #729, still vulnerable to #684). + # Generate: openssl rand -base64 32 + # Store in fly secrets / deployment env — NEVER commit the actual value. + ADMIN_TOKEN: "${ADMIN_TOKEN:-}" volumes: - ./workspace-configs-templates:/configs - ./org-templates:/org-templates:ro diff --git a/infra/scripts/setup.sh b/infra/scripts/setup.sh index babcc6ee..41ed0288 100755 --- a/infra/scripts/setup.sh +++ b/infra/scripts/setup.sh @@ -43,3 +43,14 @@ echo "==> Infrastructure ready!" echo " Postgres: localhost:5432" echo " Redis: localhost:6379" echo " Langfuse: localhost:3001" + +# Security check — issue #684 (AdminAuth bearer bypass, PR #729). +# Without ADMIN_TOKEN, any valid workspace bearer token can call /admin/* routes. +if [ -z "${ADMIN_TOKEN:-}" ]; then + echo "" + echo " ⚠ WARNING: ADMIN_TOKEN is not set." + echo " Until it is, AdminAuth falls back to accepting any workspace bearer token" + echo " — the #684 vulnerability is NOT closed in this deployment." + echo " Generate one: openssl rand -base64 32" + echo " Then export ADMIN_TOKEN= or add it to your .env before starting the platform." +fi