From 9f9995704a836b280926b4f374068166831be457 Mon Sep 17 00:00:00 2001 From: LarsenCundric Date: Mon, 25 May 2026 12:58:56 -0700 Subject: [PATCH 1/4] feat(agent): free DeepSeek Codex path via control plane (ENG-4785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the box side of the 'no Codex sub' option. The cloud PR adds the setup choice + the codex_use_free WS command; this makes the box honor it. - bootstrap.sh: write an INERT [model_providers.browser-use-free] + [profiles.browser-use-free] block in ~/.codex/config.toml (base_url at the control plane, wire_api=chat, auth.command=bu-cp-token), plus the bu-cp-token helper that hands codex the box token. Not the default — codex exec reads the default provider and ignores --profile, so making it default would route own-sub users through us. Idempotent. - box_agent.py: handle the codex_use_free WS command — set the top-level profile line so the free profile becomes this box's default, then nudge the auth poll loop. check_codex_authed() now treats an active browser-use-free profile as authed (the free path has no codex login; the control plane holds the credential), so codex_authed flips and the setup wizard completes. Pairs with cloud PR #4551. --- agent/bootstrap.sh | 62 +++++++++++++++++++++++++++++++ agent/box_agent.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/agent/bootstrap.sh b/agent/bootstrap.sh index 122fd28..9c95a3a 100755 --- a/agent/bootstrap.sh +++ b/agent/bootstrap.sh @@ -104,6 +104,68 @@ fi chmod 0644 "$CODEX_CONFIG" ' || echo "bootstrap: codex config write failed (non-fatal)" >&2 +# --- free-tier Codex provider (ENG-4785) ---------------------------------- +# Ship an INERT `browser-use-free` provider + profile in ~/.codex/config.toml +# that routes Codex through the cloud control-plane proxy to DeepSeek V4 on +# OpenRouter. It is deliberately NOT the default model_provider — `codex exec` +# reads the default and ignores --profile, so making it default here would +# silently route own-sub users through us. The cloud flips it on per-box by +# writing a top-level `profile = "browser-use-free"` line (codex_use_free WS +# command) only when the user picks the "no sub" option in setup. +# +# base_url points at the control plane (NOT openrouter.ai) so the CP holds the +# OpenRouter key server-side; the box authenticates with its box token, which +# the `bu-cp-token` helper (below) hands to codex via auth.command. Idempotent: +# skipped if the provider block is already present. +if [ -f /etc/bux/env ]; then + # shellcheck disable=SC1091 + . /etc/bux/env || true +fi +# wss://host -> https://host, ws://host -> http://host; drop trailing slash. +CP_BASE="${BUX_CLOUD_URL:-wss://api.browser-use.com}" +CP_BASE="${CP_BASE/#wss:/https:}" +CP_BASE="${CP_BASE/#ws:/http:}" +CP_BASE="${CP_BASE%/}/api/codex/v1" +sudo -u bux -H CP_BASE="$CP_BASE" bash -c ' +CODEX_CONFIG="$HOME/.codex/config.toml" +mkdir -p "$(dirname "$CODEX_CONFIG")" +if [ ! -f "$CODEX_CONFIG" ] || ! grep -qE "^\[model_providers\.browser-use-free\]" "$CODEX_CONFIG"; then + cat >> "$CODEX_CONFIG" <&2 + +# bu-cp-token: hands Codex the box token as a bearer for the control-plane +# proxy. Codex's auth.command runs this and uses stdout as the token. Reading +# from /etc/bux/env at call time means token rotation is picked up without +# rewriting config.toml. +cat > /usr/local/bin/bu-cp-token <<'TOKENEOF' +#!/usr/bin/env bash +# Print the box token for Codex's control-plane provider auth (ENG-4785). +set -euo pipefail +[ -f /etc/bux/env ] && . /etc/bux/env || true +printf '%s' "${BUX_BOX_TOKEN:-}" +TOKENEOF +chmod 0755 /usr/local/bin/bu-cp-token + # --- agent shell helpers -------------------------------------------------- # install.sh creates these symlinks on first boot, but new helpers added to # agent/ after a box has already been provisioned never get linked into diff --git a/agent/box_agent.py b/agent/box_agent.py index 29b7260..24c0ef4 100644 --- a/agent/box_agent.py +++ b/agent/box_agent.py @@ -248,6 +248,31 @@ async def check_claude_authed() -> bool: return '"loggedin": true' in text or '"loggedin":true' in text +def _codex_free_profile_active() -> bool: + """True if config.toml selects the free DeepSeek profile as default. + + The free path (ENG-4785) needs no `codex login` — the control plane holds + the credential, the box just routes to it. So "authed" for this box means + the top-level `profile = "browser-use-free"` line is present. Checked + before shelling out to `codex login status` so the free path reports + authed without a sign-in. + """ + try: + cfg = '/home/bux/.codex/config.toml' + with open(cfg, encoding='utf-8') as f: + for line in f: + stripped = line.strip() + if stripped.startswith('['): + # Top-level keys only — stop at the first table header. + break + if stripped.startswith('profile') and '=' in stripped: + value = stripped.split('=', 1)[1].strip().strip('"').strip("'") + return value == 'browser-use-free' + except Exception: + return False + return False + + async def check_codex_authed() -> bool: """Shell out to `codex login status`; return True iff logged in. @@ -256,7 +281,12 @@ async def check_codex_authed() -> bool: failure. Matching plain-text substrings keeps us forward-compatible with minor copy changes; the false case (CLI not installed, etc.) just keeps codex_authed=false until the user installs / signs in. + + The free DeepSeek path short-circuits this: it has no login, so an + active `browser-use-free` profile counts as authed. """ + if _codex_free_profile_active(): + return True try: proc = await asyncio.create_subprocess_exec( CODEX_BIN, @@ -950,6 +980,11 @@ async def _handle(self, raw: str | bytes) -> None: await self._codex_login_start() elif cmd == 'codex_login_cancel': await self._codex_login_cancel() + elif cmd == 'codex_use_free': + # Point Codex at the free DeepSeek path (ENG-4785): select the + # inert browser-use-free profile bootstrap wrote, then report + # codex_authed so the wizard completes. No login involved. + await self._codex_use_free() elif cmd == 'ping': await self._send({'type': 'pong'}) else: @@ -2067,6 +2102,64 @@ async def _codex_login_cancel(self) -> None: await self._codex_login_cleanup() await self._send({'type': 'ack', 'cmd': 'codex_login_cancel', 'ok': True}) + async def _codex_use_free(self) -> None: + """Select the free DeepSeek profile (ENG-4785). + + Bootstrap already wrote the inert `[profiles.browser-use-free]` block; + this just makes it the default by ensuring a top-level + `profile = "browser-use-free"` line, which `codex exec` honors. Then + nudge the auth poll loop — `_codex_free_profile_active()` now returns + true, so it sends `codex_authed` and the wizard completes. No login. + """ + script = r''' +set -euo pipefail +CFG="$HOME/.codex/config.toml" +mkdir -p "$(dirname "$CFG")" +touch "$CFG" +if grep -qE '^[[:space:]]*profile[[:space:]]*=' "$CFG"; then + # Rewrite an existing top-level profile line (only before the first table). + awk ' + BEGIN { seen_table = 0 } + /^\[/ { seen_table = 1 } + (!seen_table && $0 ~ /^[[:space:]]*profile[[:space:]]*=/) { + print "profile = \"browser-use-free\""; next + } + { print } + ' "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG" +else + # Prepend so it lands at top level, before any [table] headers. + printf 'profile = "browser-use-free"\n%s' "$(cat "$CFG")" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG" +fi +chmod 0644 "$CFG" +''' + try: + proc = await asyncio.create_subprocess_exec( + 'sudo', + '-u', + 'bux', + '-H', + 'bash', + '-c', + script, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, + ) + res = await _run_with_timeout(proc, 10) + except Exception: + LOG.exception('codex_use_free: failed to write profile') + res = None + + if res is None or proc.returncode != 0: + detail = (res[0].decode(errors='replace') if res else '') or 'config write failed' + await self._send( + {'type': 'ack', 'cmd': 'codex_use_free', 'ok': False, 'error': detail[:200]} + ) + return + + await self._send({'type': 'ack', 'cmd': 'codex_use_free', 'ok': True}) + # Wake the auth poll loop so codex_authed flips immediately. + self._codex_auth_wakeup.set() + async def _codex_login_cleanup(self) -> None: """Tear down any in-flight codex_login pty + reader task. Mirror of _claude_login_cleanup.""" From 20d56351a5c32cae6bb8c1413ed8457cc78044a0 Mon Sep 17 00:00:00 2001 From: LarsenCundric Date: Mon, 25 May 2026 13:38:28 -0700 Subject: [PATCH 2/4] fix(agent): address Cubic review on #264 (ENG-4785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - P0 (bootstrap.sh): don't source /etc/bux/env as root — sourcing executes the file as shell, so a tampered line would run as root. grep only the BUX_CLOUD_URL / BUX_BOX_TOKEN values we need (URL/token, no shell metachars). Applied to both the provider-config block and the bu-cp-token helper. - P2 (box_agent.py): the grep gate matched any 'profile =' (incl. inside a table) but the awk only rewrote a top-level one, so codex_use_free could report success without setting the default profile. Replace with a single awk pass that always sets the top-level key (replace-in-place or insert above the first table), plus a verify step that fails if it isn't set. --- agent/bootstrap.sh | 17 +++++++++++++---- agent/box_agent.py | 39 +++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/agent/bootstrap.sh b/agent/bootstrap.sh index 9c95a3a..6f48b6a 100755 --- a/agent/bootstrap.sh +++ b/agent/bootstrap.sh @@ -117,9 +117,13 @@ chmod 0644 "$CODEX_CONFIG" # OpenRouter key server-side; the box authenticates with its box token, which # the `bu-cp-token` helper (below) hands to codex via auth.command. Idempotent: # skipped if the provider block is already present. +# Parse only BUX_CLOUD_URL out of /etc/bux/env rather than sourcing it — the +# file is root-owned but sourcing executes its contents as shell, so a single +# tampered line would run as root. grep the one key/value we need; the value +# is a URL with no shell metacharacters so plain assignment is safe. +BUX_CLOUD_URL='' if [ -f /etc/bux/env ]; then - # shellcheck disable=SC1091 - . /etc/bux/env || true + BUX_CLOUD_URL="$(grep -E '^BUX_CLOUD_URL=' /etc/bux/env | tail -n1 | cut -d= -f2- | tr -d '"'\''')" fi # wss://host -> https://host, ws://host -> http://host; drop trailing slash. CP_BASE="${BUX_CLOUD_URL:-wss://api.browser-use.com}" @@ -160,9 +164,14 @@ chmod 0644 "$CODEX_CONFIG" cat > /usr/local/bin/bu-cp-token <<'TOKENEOF' #!/usr/bin/env bash # Print the box token for Codex's control-plane provider auth (ENG-4785). +# Parse only BUX_BOX_TOKEN out of /etc/bux/env — don't source it, so a +# tampered env file can't execute arbitrary shell when codex invokes us. set -euo pipefail -[ -f /etc/bux/env ] && . /etc/bux/env || true -printf '%s' "${BUX_BOX_TOKEN:-}" +token='' +if [ -f /etc/bux/env ]; then + token="$(grep -E '^BUX_BOX_TOKEN=' /etc/bux/env | tail -n1 | cut -d= -f2- | tr -d '"'\''')" +fi +printf '%s' "$token" TOKENEOF chmod 0755 /usr/local/bin/bu-cp-token diff --git a/agent/box_agent.py b/agent/box_agent.py index 24c0ef4..9727d7f 100644 --- a/agent/box_agent.py +++ b/agent/box_agent.py @@ -2111,25 +2111,36 @@ async def _codex_use_free(self) -> None: nudge the auth poll loop — `_codex_free_profile_active()` now returns true, so it sends `codex_authed` and the wizard completes. No login. """ + # Set the TOP-LEVEL `profile` key (the one `codex exec` honors). awk does + # it in a single pass so the gate can't drift from the rewrite scope: a + # top-level profile line (before the first [table]) is replaced in place; + # if none exists, the key is inserted at the very top. An in-table + # `profile = ...` is left alone — it isn't the default and must not be + # mistaken for one. Either way the top-level key ends up set, so success + # always means the free profile is the default. script = r''' set -euo pipefail CFG="$HOME/.codex/config.toml" mkdir -p "$(dirname "$CFG")" touch "$CFG" -if grep -qE '^[[:space:]]*profile[[:space:]]*=' "$CFG"; then - # Rewrite an existing top-level profile line (only before the first table). - awk ' - BEGIN { seen_table = 0 } - /^\[/ { seen_table = 1 } - (!seen_table && $0 ~ /^[[:space:]]*profile[[:space:]]*=/) { - print "profile = \"browser-use-free\""; next - } - { print } - ' "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG" -else - # Prepend so it lands at top level, before any [table] headers. - printf 'profile = "browser-use-free"\n%s' "$(cat "$CFG")" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG" -fi +awk ' + BEGIN { seen_table = 0; replaced = 0 } + /^[[:space:]]*\[/ { + # First table header: if we never replaced a top-level profile line, + # emit one now (above this header) so it stays top-level. + if (!seen_table && !replaced) { print "profile = \"browser-use-free\""; replaced = 1 } + seen_table = 1 + } + (!seen_table && $0 ~ /^[[:space:]]*profile[[:space:]]*=/) { + print "profile = \"browser-use-free\""; replaced = 1; next + } + { print } + END { if (!replaced) print "profile = \"browser-use-free\"" } +' "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG" +# Verify the top-level key is actually set before reporting success. +awk '/^[[:space:]]*\[/ { exit } + /^[[:space:]]*profile[[:space:]]*=[[:space:]]*"browser-use-free"/ { found = 1 } + END { exit(found ? 0 : 1) }' "$CFG" chmod 0644 "$CFG" ''' try: From dc24ec145efd17b5aa244c05d5d407c41f44b287 Mon Sep 17 00:00:00 2001 From: LarsenCundric Date: Mon, 25 May 2026 17:07:21 -0700 Subject: [PATCH 3/4] fix(agent): point free Codex at BUX_CP_CODEX_URL (PrivateLink), not public host (ENG-4785) The CP is internal-only and reachable from the box VPC only over PrivateLink; the public BUX_CLOUD_URL host has no /api/codex route. So bootstrap now reads BUX_CP_CODEX_URL (the PrivateLink endpoint DNS the provisioner writes into /etc/bux/env) as Codex's base_url, instead of deriving it from BUX_CLOUD_URL. Empty (not set / not staging) -> skip writing the free-Codex provider block. Also: tighten _codex_free_profile_active() to match the top-level 'profile' key exactly (not startswith), so 'profile_dir'/'profiles' can't be mistaken for the default-profile selector. Pairs with cloud #4561 (writes BUX_CP_CODEX_URL, staging-gated). --- agent/bootstrap.sh | 27 ++++++++++++++++----------- agent/box_agent.py | 10 +++++++--- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/agent/bootstrap.sh b/agent/bootstrap.sh index 6f48b6a..e93e352 100755 --- a/agent/bootstrap.sh +++ b/agent/bootstrap.sh @@ -117,19 +117,23 @@ chmod 0644 "$CODEX_CONFIG" # OpenRouter key server-side; the box authenticates with its box token, which # the `bu-cp-token` helper (below) hands to codex via auth.command. Idempotent: # skipped if the provider block is already present. -# Parse only BUX_CLOUD_URL out of /etc/bux/env rather than sourcing it — the -# file is root-owned but sourcing executes its contents as shell, so a single -# tampered line would run as root. grep the one key/value we need; the value -# is a URL with no shell metacharacters so plain assignment is safe. -BUX_CLOUD_URL='' +# +# base_url = BUX_CP_CODEX_URL: the PrivateLink interface-endpoint DNS in front +# of the CP Codex proxy. The CP is internal-only — it is NOT reachable at the +# public BUX_CLOUD_URL host (that routes to the public API backend, which has +# no /api/codex route). Only the box VPC can reach the proxy, over PrivateLink. +# So we use the endpoint DNS the provisioner wrote into /etc/bux/env, NOT a +# derivation of BUX_CLOUD_URL. Parse only that one key (don't source the file — +# sourcing as root would execute a tampered line). +BUX_CP_CODEX_URL='' if [ -f /etc/bux/env ]; then - BUX_CLOUD_URL="$(grep -E '^BUX_CLOUD_URL=' /etc/bux/env | tail -n1 | cut -d= -f2- | tr -d '"'\''')" + BUX_CP_CODEX_URL="$(grep -E '^BUX_CP_CODEX_URL=' /etc/bux/env | tail -n1 | cut -d= -f2- | tr -d '"'\''')" fi -# wss://host -> https://host, ws://host -> http://host; drop trailing slash. -CP_BASE="${BUX_CLOUD_URL:-wss://api.browser-use.com}" -CP_BASE="${CP_BASE/#wss:/https:}" -CP_BASE="${CP_BASE/#ws:/http:}" -CP_BASE="${CP_BASE%/}/api/codex/v1" +# Empty (not configured for this env) -> skip writing the free-Codex provider. +if [ -z "$BUX_CP_CODEX_URL" ]; then + echo "bootstrap: BUX_CP_CODEX_URL not set; skipping free-tier Codex provider" >&2 +else +CP_BASE="${BUX_CP_CODEX_URL%/}/api/codex/v1" sudo -u bux -H CP_BASE="$CP_BASE" bash -c ' CODEX_CONFIG="$HOME/.codex/config.toml" mkdir -p "$(dirname "$CODEX_CONFIG")" @@ -156,6 +160,7 @@ TOMLEOF fi chmod 0644 "$CODEX_CONFIG" ' || echo "bootstrap: codex free-tier provider write failed (non-fatal)" >&2 +fi # bu-cp-token: hands Codex the box token as a bearer for the control-plane # proxy. Codex's auth.command runs this and uses stdout as the token. Reading diff --git a/agent/box_agent.py b/agent/box_agent.py index 9727d7f..5048e45 100644 --- a/agent/box_agent.py +++ b/agent/box_agent.py @@ -265,9 +265,13 @@ def _codex_free_profile_active() -> bool: if stripped.startswith('['): # Top-level keys only — stop at the first table header. break - if stripped.startswith('profile') and '=' in stripped: - value = stripped.split('=', 1)[1].strip().strip('"').strip("'") - return value == 'browser-use-free' + if '=' in stripped: + key = stripped.split('=', 1)[0].strip() + # Exact key match — not startswith, so `profile_dir` / + # `profiles` don't get mistaken for the top-level `profile`. + if key == 'profile': + value = stripped.split('=', 1)[1].strip().strip('"').strip("'") + return value == 'browser-use-free' except Exception: return False return False From c12e8548879fe569d242e7d1249df43525fb07b5 Mon Sep 17 00:00:00 2001 From: LarsenCundric Date: Mon, 25 May 2026 17:11:59 -0700 Subject: [PATCH 4/4] fix(agent): normalize BUX_CP_CODEX_URL scheme before building base_url (Cubic, ENG-4785) Coerce the value to an absolute https:// base_url: tolerate a bare host or a stray ws/wss value rather than emitting a scheme-less base_url that silently breaks Codex routing. --- agent/bootstrap.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/agent/bootstrap.sh b/agent/bootstrap.sh index e93e352..1ab807d 100755 --- a/agent/bootstrap.sh +++ b/agent/bootstrap.sh @@ -133,6 +133,17 @@ fi if [ -z "$BUX_CP_CODEX_URL" ]; then echo "bootstrap: BUX_CP_CODEX_URL not set; skipping free-tier Codex provider" >&2 else +# Normalize the scheme: Codex needs an absolute https:// base_url. The value is +# meant to be a full URL, but tolerate a bare host (or ws/wss left over from a +# misconfigured SSM value) by coercing to https:// rather than emitting an +# invalid scheme-less base_url that silently breaks routing. +case "$BUX_CP_CODEX_URL" in + https://*) ;; + http://*) ;; + wss://*) BUX_CP_CODEX_URL="https://${BUX_CP_CODEX_URL#wss://}" ;; + ws://*) BUX_CP_CODEX_URL="http://${BUX_CP_CODEX_URL#ws://}" ;; + *) BUX_CP_CODEX_URL="https://${BUX_CP_CODEX_URL}" ;; +esac CP_BASE="${BUX_CP_CODEX_URL%/}/api/codex/v1" sudo -u bux -H CP_BASE="$CP_BASE" bash -c ' CODEX_CONFIG="$HOME/.codex/config.toml"