From 513193fa2bd263bbdc5e7232da5060823518a25b Mon Sep 17 00:00:00 2001 From: David O'Keeffe Date: Thu, 7 May 2026 14:26:29 +1000 Subject: [PATCH] feat: ENABLE_ toggles + default-deny for Gemini, OpenCode, Hermes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce per-CLI install toggles for Codex, OpenCode, Gemini, Hermes (replicating the existing ENABLE_HERMES pattern across the other three secondary CLIs), and default the three less-vetted CLIs to OFF. Security posture rationale (the substantive change vs. the original "toggles only" PR): the App container holds the rotated workspace PAT (~/.databrickscfg, written by pat_rotator.py) and all repos under ~/projects/, so any process that runs there has effective full workspace access. That makes the supply-chain bar for runtime CLIs higher than for a local dev tool. Claude Code (Anthropic) and Codex (OpenAI) ship from vendors with mature signing / SBOM / advisory pipelines and stay enabled by default. Gemini, OpenCode, and Hermes don't have equivalent supply-chain processes; they become opt-in. Operators flip ENABLE_=true in app.yaml to install them. Note that this changes the default user experience: a fresh deploy ships with two CLIs (Claude, Codex) instead of five. Operators who want the previous behaviour can flip the three toggles back in their own app.yaml override. Mechanics: - 4-line gate at the top of each setup_*.py: read env var, exit 0 if false - app.yaml documents all four toggles in one comment block Claude Code stays always-on — it's the primary CLI and setup_claude.py also creates ~/projects and writes app-wide MCP config that other agents can re-use. Disentangling those would expand scope; can be a follow-up. Smoke-tested locally: $ ENABLE_CODEX=false python3 setup_codex.py ENABLE_CODEX=false — skipping Codex CLI setup $ # exit 0, no further side effects Follow-ups (out of scope): - Flip the Python-level defaults to "false" too, so a missing app.yaml also fails-closed. Currently each setup_*.py reads os.environ.get("ENABLE_X", "true") which is a fail-open default. - Add an audit GH Action equivalent for the still-enabled CLIs against npm advisories (Codex's @openai/codex via dependency-audit.yml is already covered). Co-authored-by: Isaac --- app.yaml | 14 ++++++++++++-- setup_codex.py | 5 +++++ setup_gemini.py | 5 +++++ setup_opencode.py | 5 +++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app.yaml b/app.yaml index 1a2fbc0..2b265f3 100644 --- a/app.yaml +++ b/app.yaml @@ -14,9 +14,19 @@ env: value: databricks-claude-opus-4-6 - name: HERMES_FALLBACK_MODEL value: databricks-claude-opus-4-6 - # Set ENABLE_HERMES=false to skip Hermes Agent install. Other CLIs are unaffected. - - name: ENABLE_HERMES + # Per-CLI install toggle. Defaults reflect a "least-trusted code in the App + # container" stance: Claude Code (Anthropic) and Codex (OpenAI) ship from + # vendors with mature signing/SBOM/advisory pipelines and are enabled by + # default. Gemini, OpenCode, and Hermes are opt-in — set ENABLE_=true + # to install them. Claude Code is the primary CLI and isn't toggleable here. + - name: ENABLE_CODEX value: "true" + - name: ENABLE_OPENCODE + value: "false" + - name: ENABLE_GEMINI + value: "false" + - name: ENABLE_HERMES + value: "false" - name: CLAUDE_CODE_DISABLE_AUTO_MEMORY value: 0 - name: MAX_CONCURRENT_SESSIONS diff --git a/setup_codex.py b/setup_codex.py index 6be864f..743a09c 100644 --- a/setup_codex.py +++ b/setup_codex.py @@ -15,6 +15,11 @@ from utils import adapt_instructions_file, ensure_https, get_gateway_host, get_npm_version +# Opt-out: allow operators to disable Codex bundling without removing the file. +if os.environ.get("ENABLE_CODEX", "true").strip().lower() in ("false", "0", "no"): + print("ENABLE_CODEX=false — skipping Codex CLI setup") + raise SystemExit(0) + # Set HOME if not properly set if not os.environ.get("HOME") or os.environ["HOME"] == "/": os.environ["HOME"] = "/app/python/source_code" diff --git a/setup_gemini.py b/setup_gemini.py index ec77851..8ced395 100644 --- a/setup_gemini.py +++ b/setup_gemini.py @@ -17,6 +17,11 @@ from utils import adapt_instructions_file, ensure_https, get_gateway_host, get_npm_version +# Opt-out: allow operators to disable Gemini bundling without removing the file. +if os.environ.get("ENABLE_GEMINI", "true").strip().lower() in ("false", "0", "no"): + print("ENABLE_GEMINI=false — skipping Gemini CLI setup") + raise SystemExit(0) + # Set HOME if not properly set if not os.environ.get("HOME") or os.environ["HOME"] == "/": os.environ["HOME"] = "/app/python/source_code" diff --git a/setup_opencode.py b/setup_opencode.py index 071252a..1ab6338 100644 --- a/setup_opencode.py +++ b/setup_opencode.py @@ -13,6 +13,11 @@ from utils import ensure_https, get_gateway_host, get_npm_version +# Opt-out: allow operators to disable OpenCode bundling without removing the file. +if os.environ.get("ENABLE_OPENCODE", "true").strip().lower() in ("false", "0", "no"): + print("ENABLE_OPENCODE=false — skipping OpenCode CLI setup") + raise SystemExit(0) + # content-filter proxy local proxy — sanitizes empty content blocks before reaching Databricks # (see https://github.com/sst/opencode/issues/5028) CONTENT_FILTER_PROXY_URL = "http://127.0.0.1:4000"