From c83a3a7612dc7aa55dea0b399d14925493d9013e Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Mon, 10 Aug 2026 07:04:17 +0000 Subject: [PATCH 1/2] docs: name every reserved sandbox_env key, and guard the list --- .pre-commit-config.yaml | 18 ++++++++++++++---- docs/tend.example.yaml | 14 ++++++++++++-- .../install-tend/references/tend.example.yaml | 14 ++++++++++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d32bfd81..4f1717c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -128,7 +128,7 @@ repos: - id: sandbox-env-reserved-parity name: reserved sandbox_env set stays in sync language: system - files: ^(generator/src/tend/config\.py|proxy/setup-sandbox\.sh)$ + files: ^(generator/src/tend/config\.py|proxy/setup-sandbox\.sh|docs/tend\.example\.yaml)$ entry: python3 args: - -c @@ -154,6 +154,13 @@ repos: heredoc_keys = set(re.findall(r'^([A-Za-z_][A-Za-z0-9_]*)=', heredoc, re.M)) heredoc_keys |= set(re.findall(r'ANTHROPIC_DUMMY="([A-Za-z_][A-Za-z0-9_]*)=', sh)) + # The adopter only meets this set as a rejection at `init`, so the + # `sandbox_env` docs have to name every member — a key reserved in + # code but absent from the docs reads as settable right up until + # the run fails. + docs = open("docs/tend.example.yaml").read() + documented = set(re.findall(r"`([A-Za-z_][A-Za-z0-9_]*)`", docs)) + problems = [] for name in sorted(config_set - shell_set): problems.append(f"'{name}' is reserved in config.py but NOT guarded in setup-sandbox.sh") @@ -161,15 +168,18 @@ repos: problems.append(f"'{name}' is guarded in setup-sandbox.sh but NOT reserved in config.py") for name in sorted(heredoc_keys - config_set): problems.append(f"'{name}' is written to AGENT_ENV_FILE but NOT reserved in config.py — an adopter's sandbox_env could shadow it") + for name in sorted(config_set - documented): + problems.append(f"'{name}' is reserved in config.py but NOT named in docs/tend.example.yaml's sandbox_env list") for p in problems: print(p) if problems: print( "\nERROR: the reserved sandbox_env set drifted between " - "config.py, setup-sandbox.sh, and the AGENT_ENV_FILE " - "heredoc. The shell case is the security boundary; keep " - "every env-file key reserved and the two sets aligned." + "config.py, setup-sandbox.sh, the AGENT_ENV_FILE heredoc, " + "and docs/tend.example.yaml. The shell case is the security " + "boundary; keep every env-file key reserved, the two sets " + "aligned, and every reserved name documented." ) sys.exit(1) pass_filenames: false diff --git a/docs/tend.example.yaml b/docs/tend.example.yaml index c7a0b987..8b4aff14 100644 --- a/docs/tend.example.yaml +++ b/docs/tend.example.yaml @@ -183,8 +183,18 @@ bot_name: my-project-bot # - ~/.cargo/bin # # - `sandbox_env`: NAME: VALUE pairs added to the agent's launch env. Reserved -# keys (proxy routing, CA trust, dummy credentials, PATH) are rejected — use -# `sandbox_path` for PATH: +# keys are rejected at `init`, and again inside the sandbox so a hand-edited +# workflow can't smuggle one past: +# +# - proxy routing — `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, `http_proxy`, +# `https_proxy`, `no_proxy` +# - CA trust — `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE` +# - dummy credentials — `GH_TOKEN`, `GITHUB_TOKEN`, `ANTHROPIC_API_KEY`, +# `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REMOTE` +# - the sandbox's own locations — `HOME`, `PATH`, `XDG_CONFIG_HOME`, +# `XDG_CACHE_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME` +# +# Use `sandbox_path` to extend PATH: # # sandbox_env: # RUST_BACKTRACE: "1" diff --git a/plugins/install-tend/skills/install-tend/references/tend.example.yaml b/plugins/install-tend/skills/install-tend/references/tend.example.yaml index c7a0b987..8b4aff14 100644 --- a/plugins/install-tend/skills/install-tend/references/tend.example.yaml +++ b/plugins/install-tend/skills/install-tend/references/tend.example.yaml @@ -183,8 +183,18 @@ bot_name: my-project-bot # - ~/.cargo/bin # # - `sandbox_env`: NAME: VALUE pairs added to the agent's launch env. Reserved -# keys (proxy routing, CA trust, dummy credentials, PATH) are rejected — use -# `sandbox_path` for PATH: +# keys are rejected at `init`, and again inside the sandbox so a hand-edited +# workflow can't smuggle one past: +# +# - proxy routing — `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, `http_proxy`, +# `https_proxy`, `no_proxy` +# - CA trust — `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE` +# - dummy credentials — `GH_TOKEN`, `GITHUB_TOKEN`, `ANTHROPIC_API_KEY`, +# `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REMOTE` +# - the sandbox's own locations — `HOME`, `PATH`, `XDG_CONFIG_HOME`, +# `XDG_CACHE_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME` +# +# Use `sandbox_path` to extend PATH: # # sandbox_env: # RUST_BACKTRACE: "1" From b404998b590d3cb6287ae2c62410d5ba3dc1c15e Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Mon, 10 Aug 2026 07:12:19 +0000 Subject: [PATCH 2/2] docs: scope the docs parity check to the sandbox_env bullet, regroup CLAUDE_CODE_REMOTE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documented set was matched file-wide, so ANTHROPIC_API_KEY and CLAUDE_CODE_OAUTH_TOKEN were satisfied by the auth section further up the file — deleting either from the sandbox_env list kept the hook green, which is the drift the leg exists to catch. Anchor on the bullet and stop at the next top-level entry. CLAUDE_CODE_REMOTE=1 suppresses interactive prompts rather than standing in for a credential; give it its own group in both the docs file and its byte-identical references/ mirror. --- .pre-commit-config.yaml | 8 ++++++-- docs/tend.example.yaml | 3 ++- .../skills/install-tend/references/tend.example.yaml | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f1717c1..90d7fb35 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -157,9 +157,13 @@ repos: # The adopter only meets this set as a rejection at `init`, so the # `sandbox_env` docs have to name every member — a key reserved in # code but absent from the docs reads as settable right up until - # the run fails. + # the run fails. Scope the search to that bullet: file-wide, an + # unrelated mention elsewhere (the auth section names + # ANTHROPIC_API_KEY and CLAUDE_CODE_OAUTH_TOKEN) would stand in for + # the list entry and hide the drift this leg exists to catch. docs = open("docs/tend.example.yaml").read() - documented = set(re.findall(r"`([A-Za-z_][A-Za-z0-9_]*)`", docs)) + section = re.search(r"^# - `sandbox_env`:(.*?)^# - `", docs, re.S | re.M).group(1) + documented = set(re.findall(r"`([A-Za-z_][A-Za-z0-9_]*)`", section)) problems = [] for name in sorted(config_set - shell_set): diff --git a/docs/tend.example.yaml b/docs/tend.example.yaml index 8b4aff14..c934ded1 100644 --- a/docs/tend.example.yaml +++ b/docs/tend.example.yaml @@ -190,7 +190,8 @@ bot_name: my-project-bot # `https_proxy`, `no_proxy` # - CA trust — `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE` # - dummy credentials — `GH_TOKEN`, `GITHUB_TOKEN`, `ANTHROPIC_API_KEY`, -# `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REMOTE` +# `CLAUDE_CODE_OAUTH_TOKEN` +# - prompt suppression — `CLAUDE_CODE_REMOTE` # - the sandbox's own locations — `HOME`, `PATH`, `XDG_CONFIG_HOME`, # `XDG_CACHE_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME` # diff --git a/plugins/install-tend/skills/install-tend/references/tend.example.yaml b/plugins/install-tend/skills/install-tend/references/tend.example.yaml index 8b4aff14..c934ded1 100644 --- a/plugins/install-tend/skills/install-tend/references/tend.example.yaml +++ b/plugins/install-tend/skills/install-tend/references/tend.example.yaml @@ -190,7 +190,8 @@ bot_name: my-project-bot # `https_proxy`, `no_proxy` # - CA trust — `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE` # - dummy credentials — `GH_TOKEN`, `GITHUB_TOKEN`, `ANTHROPIC_API_KEY`, -# `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REMOTE` +# `CLAUDE_CODE_OAUTH_TOKEN` +# - prompt suppression — `CLAUDE_CODE_REMOTE` # - the sandbox's own locations — `HOME`, `PATH`, `XDG_CONFIG_HOME`, # `XDG_CACHE_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME` #