55
66import yaml
77
8+ from products .tasks .backend .constants import SANDBOX_AGENT_LAUNCH_UNSET_ENV_VARS
9+
810AGENTSH_DAEMON_PORT = 18080
911SESSION_ID_FILE = "/tmp/agentsh-session-id"
1012ENV_FILE = "/tmp/agent-env"
13+ GITHUB_ENV_FILE = "/tmp/agent-github-env"
14+ OAUTH_ENV_FILE = "/tmp/agent-oauth-env"
1115ENV_WRAPPER_SCRIPT = "/tmp/agentsh-env-wrapper.sh"
1216# Sourced via BASH_ENV on every `bash -c` the agent runs, so git/gh pick up a
13- # mid-session GitHub credential refresh (the backend rewrites ENV_FILE in place) .
17+ # mid-session GitHub credential refresh from its dedicated credential file .
1418BASH_ENV_SCRIPT = "/tmp/agentsh-bash-env.sh"
1519AGENTSH_AUDIT_DB = "/var/lib/agentsh/events.db"
1620INFRASTRUCTURE_DOMAINS = [
@@ -93,7 +97,22 @@ def _get_debug_only_ports() -> list[int]:
9397 return ports
9498
9599
96- def generate_env_wrapper () -> str :
100+ _MANAGED_CREDENTIAL_ENV_KEYS = ("GH_TOKEN" , "GITHUB_TOKEN" , "POSTHOG_PERSONAL_API_KEY" )
101+ _EXCLUDED_AGENT_ENV_KEYS = (
102+ * SANDBOX_AGENT_LAUNCH_UNSET_ENV_VARS ,
103+ "BASH_ENV" ,
104+ "PROMPT_COMMAND" ,
105+ "PYTHONSTARTUP" ,
106+ "PERL5OPT" ,
107+ "RUBYOPT" ,
108+ )
109+
110+
111+ def generate_env_wrapper (
112+ env_file : str = ENV_FILE ,
113+ github_env_file : str = GITHUB_ENV_FILE ,
114+ oauth_env_file : str = OAUTH_ENV_FILE ,
115+ ) -> str :
97116 """Generate a wrapper that restores the full sandbox environment.
98117
99118 ``agentsh exec`` starts child processes with a heavily stripped
@@ -104,25 +123,107 @@ def generate_env_wrapper() -> str:
104123 Network policy enforcement happens at the syscall level (ptrace) —
105124 it does not depend on proxy environment variables.
106125 """
126+ quoted_env_file = shlex .quote (env_file )
127+ quoted_github_env_file = shlex .quote (github_env_file )
128+ quoted_oauth_env_file = shlex .quote (oauth_env_file )
129+ excluded_names = " " .join ((* _MANAGED_CREDENTIAL_ENV_KEYS , * _EXCLUDED_AGENT_ENV_KEYS ))
130+ excluded_entries = "|" .join (f"{ name } =*" for name in (* _MANAGED_CREDENTIAL_ENV_KEYS , * _EXCLUDED_AGENT_ENV_KEYS ))
107131 return f"""\
108132 #!/bin/bash
133+ unset { excluded_names }
134+ while IFS= read -r -d $'\\ 0' line; do
135+ case "$line" in
136+ { excluded_entries } ) ;;
137+ *) export "$line" ;;
138+ esac
139+ done < { quoted_env_file } 2>/dev/null
140+
141+ while IFS= read -r -d $'\\ 0' line; do
142+ case "$line" in
143+ GH_TOKEN=*|GITHUB_TOKEN=*) export "$line" ;;
144+ esac
145+ done < { quoted_github_env_file } 2>/dev/null
146+
109147while IFS= read -r -d $'\\ 0' line; do
110- export "$line"
111- done < { ENV_FILE }
148+ case "$line" in
149+ POSTHOG_PERSONAL_API_KEY=*) export "$line" ;;
150+ esac
151+ done < { quoted_oauth_env_file } 2>/dev/null
112152exec "$@"
113153"""
114154
115155
116- def generate_bash_env_script () -> str :
156+ def generate_bash_env_script (
157+ env_file : str = ENV_FILE ,
158+ github_env_file : str = GITHUB_ENV_FILE ,
159+ oauth_env_file : str = OAUTH_ENV_FILE ,
160+ ) -> str :
117161 """
118- Generate the script sourced via ``BASH_ENV``.
162+ Generate the script sourced via ``BASH_ENV`` and used to initialize its env file.
163+
164+ The explicit invocation runs before the background agent-server launch. It
165+ atomically replaces the full environment with the current sandbox process
166+ environment, excluding launch hooks and credentials. Credential files are
167+ initialized only when absent, so a backend refresh that happened before startup
168+ wins. Sourced invocations stay cheap and only export GitHub credentials.
119169 """
170+ quoted_env_file = shlex .quote (env_file )
171+ quoted_github_env_file = shlex .quote (github_env_file )
172+ quoted_oauth_env_file = shlex .quote (oauth_env_file )
173+ excluded_entries = "|" .join (f"{ name } =*" for name in (* _MANAGED_CREDENTIAL_ENV_KEYS , * _EXCLUDED_AGENT_ENV_KEYS ))
120174 return f"""\
175+ if [[ "${{BASH_SOURCE[0]}}" == "$0" ]]; then
176+ set -euo pipefail
177+ umask 077
178+ env_tmp="$(mktemp { quoted_env_file } .tmp.XXXXXX)"
179+ github_tmp="$(mktemp { quoted_github_env_file } .tmp.XXXXXX)"
180+ oauth_tmp="$(mktemp { quoted_oauth_env_file } .tmp.XXXXXX)"
181+ trap 'rm -f "$env_tmp" "$github_tmp" "$oauth_tmp"' EXIT
182+
183+ while IFS= read -r -d $'\\ 0' kv 2>/dev/null; do
184+ case "$kv" in
185+ { excluded_entries } ) ;;
186+ *) printf '%s\\ 0' "$kv" >> "$env_tmp" ;;
187+ esac
188+ done < <(env -0)
189+ chmod 600 "$env_tmp"
190+ mv "$env_tmp" { quoted_env_file }
191+
192+ github_token="${{GITHUB_TOKEN:-${{GH_TOKEN:-}}}}"
193+ if [[ -n "$github_token" ]]; then
194+ printf 'GITHUB_TOKEN=%s\\ 0GH_TOKEN=%s\\ 0' "$github_token" "$github_token" > "$github_tmp"
195+ fi
196+ chmod 600 "$github_tmp"
197+ if [[ -e { quoted_github_env_file } || -L { quoted_github_env_file } ]]; then
198+ [[ -f { quoted_github_env_file } && ! -L { quoted_github_env_file } ]]
199+ chmod 600 { quoted_github_env_file }
200+ else
201+ if ! ln "$github_tmp" { quoted_github_env_file } 2>/dev/null; then
202+ [[ -f { quoted_github_env_file } && ! -L { quoted_github_env_file } ]]
203+ fi
204+ fi
205+
206+ if [[ -n "${{POSTHOG_PERSONAL_API_KEY:-}}" ]]; then
207+ printf 'POSTHOG_PERSONAL_API_KEY=%s\\ 0' "$POSTHOG_PERSONAL_API_KEY" > "$oauth_tmp"
208+ fi
209+ chmod 600 "$oauth_tmp"
210+ if [[ -e { quoted_oauth_env_file } || -L { quoted_oauth_env_file } ]]; then
211+ [[ -f { quoted_oauth_env_file } && ! -L { quoted_oauth_env_file } ]]
212+ chmod 600 { quoted_oauth_env_file }
213+ else
214+ if ! ln "$oauth_tmp" { quoted_oauth_env_file } 2>/dev/null; then
215+ [[ -f { quoted_oauth_env_file } && ! -L { quoted_oauth_env_file } ]]
216+ fi
217+ fi
218+ exit 0
219+ fi
220+
221+ unset GH_TOKEN GITHUB_TOKEN
121222while IFS= read -r -d $'\\ 0' kv 2>/dev/null; do
122223 case "$kv" in
123224 GH_TOKEN=*|GITHUB_TOKEN=*) export "$kv" ;;
124225 esac
125- done < { ENV_FILE } 2>/dev/null
226+ done < { quoted_github_env_file } 2>/dev/null
126227"""
127228
128229
0 commit comments