From cba3dbf58ad77a956b3d29c16b72cb10b8e34bec Mon Sep 17 00:00:00 2001 From: "Stuart B. Wilkins" Date: Sat, 18 Jul 2026 08:16:31 -0400 Subject: [PATCH 1/2] ENH: opt-in GH_TOKEN_* passthrough for bwopencode Add --github-tokens to forward GH_TOKEN_* env vars into the sandbox so the agent can authenticate gh(1) via per-command token selection. Off by default; values are printed as REDACTED under --dry-run so tokens never reach the terminal. Assisted-by: opencode:claude-opus-4-8 Assisted-by: opencode:claude-sonnet-4-6 --- bin/bwopencode | 30 ++++++++++++++++++++++++++++++ docs/bw-wrappers.md | 4 +++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/bin/bwopencode b/bin/bwopencode index fbd76f4..9931616 100755 --- a/bin/bwopencode +++ b/bin/bwopencode @@ -72,6 +72,7 @@ source "${SCRIPT_DIR}/../lib/bwrap_sandbox_lib.sh" # ═══════════════════════════════════════════════════════════════════ INIT_AUTH=0 +FORWARD_GH_TOKENS=0 OC_CONFIG_BINDS=() # ═══════════════════════════════════════════════════════════════════ @@ -101,6 +102,10 @@ parse_wrapper_args() { INIT_AUTH=1 shift ;; + --github-tokens) + FORWARD_GH_TOKENS=1 + shift + ;; --new-session) # shellcheck disable=SC2034 # consumed by sourced bwrap_sandbox_lib.sh (sandboxed_exec) FORCE_NEW_SESSION=1 @@ -179,6 +184,11 @@ Wrapper options (consumed before opencode sees the command line): Symlinks are resolved before the safety check is applied. --rw-path PATH Mount PATH (file or directory) read-write into the sandbox. Same blocked-path rules as --ro-path. May be repeated. + --github-tokens Forward any GH_TOKEN_* environment variables into the + sandbox so the agent can authenticate the gh(1) CLI. + Off by default. When set, use per-command token selection: + GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo + GH_TOKEN="$GH_TOKEN_PERSONAL" gh issue list -R user/repo All other arguments are passed through to opencode unchanged. @@ -366,6 +376,9 @@ build_opencode_cache_mount() { # ── OpenCode env vars ──────────────────────────────────────────── # Pass through any OPENCODE_* env vars (config overrides, etc.) # Also pass through AIFAPIM_* env vars for NSLS-II Hermes gateway. +# With --github-tokens, also forward GH_TOKEN_* vars so the agent +# can authenticate gh(1) via per-command: +# GH_TOKEN="$GH_TOKEN_NSLS2" gh build_opencode_env() { # AIFAPIM gateway config pass_through_if_set AIFAPIM_HOST @@ -377,6 +390,23 @@ build_opencode_env() { BWRAP_ARGS+=(--setenv "${_key}" "${!_key}") fi done < <(compgen -e) + + # GitHub CLI token passthrough (opt-in via --github-tokens) + # In --dry-run mode the value is replaced with REDACTED so that tokens + # are not printed to the terminal in the bwrap command output. + if [[ "${FORWARD_GH_TOKENS}" -eq 1 ]]; then + local _gh_val + while IFS= read -r _key; do + if [[ "${_key}" == GH_TOKEN_* ]]; then + if [[ "${DRY_RUN}" -eq 1 ]]; then + _gh_val="REDACTED" + else + _gh_val="${!_key}" + fi + BWRAP_ARGS+=(--setenv "${_key}" "${_gh_val}") + fi + done < <(compgen -e) + fi } # ═══════════════════════════════════════════════════════════════════ diff --git a/docs/bw-wrappers.md b/docs/bw-wrappers.md index 82b4ec1..79d0551 100644 --- a/docs/bw-wrappers.md +++ b/docs/bw-wrappers.md @@ -46,7 +46,9 @@ project-dotfiles to the host data dir; `auth.json` is persisted only if it already exists on the host (use `--init-auth` once to create it for personal accounts). -No options beyond the common set. +| Option | Description | +| --- | --- | +| `--github-tokens` | Forward every `GH_TOKEN_*` environment variable into the sandbox so the agent can authenticate `gh`. Off by default. In `--dry-run` mode, token values are printed as `REDACTED`. Use per-command token selection inside the sandbox: `GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo` | ## `bwclaude` From 338cf5209ad3beb0912f97f5df9928af73866b95 Mon Sep 17 00:00:00 2001 From: "Stuart B. Wilkins" Date: Sat, 18 Jul 2026 15:30:57 -0400 Subject: [PATCH 2/2] ENH: opt-in GH_TOKEN_* passthrough for bwcodex/bwclaude/bwcopilot Extends the --github-tokens flag added to bwopencode (cb57193) to the remaining wrapper scripts, so gh(1) can be authenticated with per-command token selection across all four sandboxed agent CLIs. Assisted-by: opencode:claude-sonnet-5 --- bin/bwclaude | 27 +++++++++++++++++++++++++++ bin/bwcodex | 27 +++++++++++++++++++++++++++ bin/bwcopilot | 27 +++++++++++++++++++++++++++ docs/bw-wrappers.md | 8 +++++++- 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/bin/bwclaude b/bin/bwclaude index a22a085..3a67802 100755 --- a/bin/bwclaude +++ b/bin/bwclaude @@ -83,6 +83,7 @@ source "${SCRIPT_DIR}/../lib/bwrap_sandbox_lib.sh" INIT_AUTH=0 DEBUG_MODE=0 +FORWARD_GH_TOKENS=0 # ═══════════════════════════════════════════════════════════════════ # Tool-specific functions @@ -115,6 +116,10 @@ parse_wrapper_args() { DEBUG_MODE=1 shift ;; + --github-tokens) + FORWARD_GH_TOKENS=1 + shift + ;; --new-session) # shellcheck disable=SC2034 # consumed by sourced bwrap_sandbox_lib.sh (sandboxed_exec) FORCE_NEW_SESSION=1 @@ -197,6 +202,11 @@ Wrapper options (consumed before claude sees the command line): Symlinks are resolved before the safety check is applied. --rw-path PATH Mount PATH (file or directory) read-write into the sandbox. Same blocked-path rules as --ro-path. May be repeated. + --github-tokens Forward any GH_TOKEN_* environment variables into the + sandbox so the agent can authenticate the gh(1) CLI. + Off by default. When set, use per-command token selection: + GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo + GH_TOKEN="$GH_TOKEN_PERSONAL" gh issue list -R user/repo All other arguments are passed through to claude unchanged. @@ -382,6 +392,23 @@ build_claude_env() { fi done < <(compgen -e) + # GitHub CLI token passthrough (opt-in via --github-tokens) + # In --dry-run mode the value is replaced with REDACTED so that tokens + # are not printed to the terminal in the bwrap command output. + if [[ "${FORWARD_GH_TOKENS}" -eq 1 ]]; then + local _gh_val + while IFS= read -r _key; do + if [[ "${_key}" == GH_TOKEN_* ]]; then + if [[ "${DRY_RUN}" -eq 1 ]]; then + _gh_val="REDACTED" + else + _gh_val="${!_key}" + fi + BWRAP_ARGS+=(--setenv "${_key}" "${_gh_val}") + fi + done < <(compgen -e) + fi + # Enforce after the loop so host env cannot override these. BWRAP_ARGS+=(--setenv CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS 1) BWRAP_ARGS+=(--setenv CLAUDE_CODE_USE_FOUNDRY 1) diff --git a/bin/bwcodex b/bin/bwcodex index 7f3b346..48ea0cb 100755 --- a/bin/bwcodex +++ b/bin/bwcodex @@ -107,6 +107,7 @@ source "${SCRIPT_DIR}/../lib/bwrap_sandbox_lib.sh" INIT_AUTH=0 DEBUG_MODE=0 PERSIST_CONFIG=0 +FORWARD_GH_TOKENS=0 # ═══════════════════════════════════════════════════════════════════ # Tool-specific functions @@ -143,6 +144,10 @@ parse_wrapper_args() { DEBUG_MODE=1 shift ;; + --github-tokens) + FORWARD_GH_TOKENS=1 + shift + ;; --new-session) # shellcheck disable=SC2034 # consumed by sourced bwrap_sandbox_lib.sh (sandboxed_exec) FORCE_NEW_SESSION=1 @@ -233,6 +238,11 @@ Wrapper options (consumed before codex sees the command line): Symlinks are resolved before the safety check is applied. --rw-path PATH Mount PATH (file or directory) read-write into the sandbox. Same blocked-path rules as --ro-path. May be repeated. + --github-tokens Forward any GH_TOKEN_* environment variables into the + sandbox so the agent can authenticate the gh(1) CLI. + Off by default. When set, use per-command token selection: + GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo + GH_TOKEN="$GH_TOKEN_PERSONAL" gh issue list -R user/repo All other arguments are passed through to codex unchanged. @@ -419,6 +429,23 @@ build_codex_env() { if [[ "${DEBUG_MODE}" -eq 1 ]]; then BWRAP_ARGS+=(--setenv RUST_LOG "debug") fi + + # GitHub CLI token passthrough (opt-in via --github-tokens) + # In --dry-run mode the value is replaced with REDACTED so that tokens + # are not printed to the terminal in the bwrap command output. + if [[ "${FORWARD_GH_TOKENS}" -eq 1 ]]; then + local _gh_val + while IFS= read -r _key; do + if [[ "${_key}" == GH_TOKEN_* ]]; then + if [[ "${DRY_RUN}" -eq 1 ]]; then + _gh_val="REDACTED" + else + _gh_val="${!_key}" + fi + BWRAP_ARGS+=(--setenv "${_key}" "${_gh_val}") + fi + done < <(compgen -e) + fi } # ── Codex install dir (stub) ───────────────────────────────────── diff --git a/bin/bwcopilot b/bin/bwcopilot index 437b65d..f5b2435 100755 --- a/bin/bwcopilot +++ b/bin/bwcopilot @@ -76,6 +76,7 @@ source "${SCRIPT_DIR}/../lib/bwrap_sandbox_lib.sh" # ═══════════════════════════════════════════════════════════════════ INIT_AUTH=0 +FORWARD_GH_TOKENS=0 # ═══════════════════════════════════════════════════════════════════ # Tool-specific functions @@ -104,6 +105,10 @@ parse_wrapper_args() { INIT_AUTH=1 shift ;; + --github-tokens) + FORWARD_GH_TOKENS=1 + shift + ;; --new-session) # shellcheck disable=SC2034 # consumed by sourced bwrap_sandbox_lib.sh (sandboxed_exec) FORCE_NEW_SESSION=1 @@ -184,6 +189,11 @@ Wrapper options (consumed before copilot sees the command line): Symlinks are resolved before the safety check is applied. --rw-path PATH Mount PATH (file or directory) read-write into the sandbox. Same blocked-path rules as --ro-path. May be repeated. + --github-tokens Forward any GH_TOKEN_* environment variables into the + sandbox so the agent can authenticate the gh(1) CLI. + Off by default. When set, use per-command token selection: + GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo + GH_TOKEN="$GH_TOKEN_PERSONAL" gh issue list -R user/repo All other arguments are passed through to copilot unchanged. @@ -366,6 +376,23 @@ build_copilot_env() { BWRAP_ARGS+=(--setenv "${_key}" "${!_key}") fi done + + # GitHub CLI token passthrough (opt-in via --github-tokens) + # In --dry-run mode the value is replaced with REDACTED so that tokens + # are not printed to the terminal in the bwrap command output. + if [[ "${FORWARD_GH_TOKENS}" -eq 1 ]]; then + local _gh_val + while IFS= read -r _key; do + if [[ "${_key}" == GH_TOKEN_* ]]; then + if [[ "${DRY_RUN}" -eq 1 ]]; then + _gh_val="REDACTED" + else + _gh_val="${!_key}" + fi + BWRAP_ARGS+=(--setenv "${_key}" "${_gh_val}") + fi + done < <(compgen -e) + fi } # ═══════════════════════════════════════════════════════════════════ diff --git a/docs/bw-wrappers.md b/docs/bw-wrappers.md index 79d0551..e711b31 100644 --- a/docs/bw-wrappers.md +++ b/docs/bw-wrappers.md @@ -80,6 +80,7 @@ Additional options: | Option | Effect | | --- | --- | | `--debug` | Enable verbose debug logging: sets `ANTHROPIC_LOG=debug`, `NODE_DEBUG=http,https,tls`, and passes `--debug --verbose` to `claude` | +| `--github-tokens` | Forward every `GH_TOKEN_*` environment variable into the sandbox so the agent can authenticate `gh`. Off by default. In `--dry-run` mode, token values are printed as `REDACTED`. Use per-command token selection inside the sandbox: `GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo` | On shared accounts, auth is ephemeral. On a personal machine, run `bwclaude --init-auth` once to persist credentials. @@ -93,7 +94,11 @@ inside a bubblewrap sandbox. bwcopilot [bwcopilot-options] [copilot arguments...] ``` -No options beyond the common set. +Additional options: + +| Option | Effect | +| --- | --- | +| `--github-tokens` | Forward every `GH_TOKEN_*` environment variable into the sandbox so the agent can authenticate `gh`. Off by default. In `--dry-run` mode, token values are printed as `REDACTED`. Use per-command token selection inside the sandbox: `GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo` | On shared accounts, auth tokens are ephemeral. On a personal machine, run `bwcopilot --init-auth` once to persist tokens. @@ -122,6 +127,7 @@ Additional options: | --- | --- | | `--debug` | Enable verbose Codex logging (sets `RUST_LOG=debug`) | | `--persist-config` | Bind-mount `~/.codex/config.toml` read-write into the sandbox so changes (e.g. project-trust grants) persist across sessions. Without this flag, `config.toml` is staged read-write into a per-session copy that is discarded on exit. Concurrent `--persist-config` sessions may race on writes | +| `--github-tokens` | Forward every `GH_TOKEN_*` environment variable into the sandbox so the agent can authenticate `gh`. Off by default. In `--dry-run` mode, token values are printed as `REDACTED`. Use per-command token selection inside the sandbox: `GH_TOKEN="$GH_TOKEN_NSLS2" gh pr list -R NSLS2/repo` | Note: Codex CLI also enforces its own inner Landlock-based sandbox for agent tool calls. The outer `bwrap` here is complementary — it scopes