From 1f5da558ca20a4b5d0ab57aa6d15986c7af2cb4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 01:54:52 +0000 Subject: [PATCH 1/3] Initial plan From 2016a03af9948ed0d773914b839978f62a93f928 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:04:49 +0000 Subject: [PATCH 2/3] feat: add preflight binary check for codex in agent entrypoint Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/ff25ad6d-c857-47bb-ac9b-28f16da6f6a4 --- containers/agent/entrypoint.sh | 17 +++++++++++++++++ src/docker-manager.test.ts | 20 ++++++++++++++++++++ src/docker-manager.ts | 8 ++++++++ 3 files changed, 45 insertions(+) diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index cfa307b0..b931eeb2 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -717,6 +717,23 @@ if ! command -v node >/dev/null 2>&1; then fi AWFEOF fi + # If AWF_PREFLIGHT_BINARY is set, verify the named binary is reachable inside the + # chroot before exec'ing the user command. This provides a fast, human-readable + # diagnostic when the runner slot lacks the binary (e.g. codex not installed). + if [ -n "${AWF_PREFLIGHT_BINARY:-}" ]; then + # Validate the binary name contains only safe characters to prevent shell injection + # in the generated chroot startup script. + if [[ "${AWF_PREFLIGHT_BINARY}" =~ ^[a-zA-Z0-9_.-]+$ ]]; then + printf 'if ! command -v %s >/dev/null 2>&1; then\n' "${AWF_PREFLIGHT_BINARY}" >> "/host${SCRIPT_FILE}" + printf ' echo "[entrypoint][ERROR] Required binary '"'"'%s'"'"' is not available inside AWF chroot." >&2\n' "${AWF_PREFLIGHT_BINARY}" >> "/host${SCRIPT_FILE}" + printf ' echo "[entrypoint][ERROR] Ensure '"'"'%s'"'"' is installed on the runner and present in a PATH directory bind-mounted into /host." >&2\n' "${AWF_PREFLIGHT_BINARY}" >> "/host${SCRIPT_FILE}" + printf ' echo "[entrypoint][ERROR] Standard bind-mounted PATH directories: /usr/local/bin, /usr/bin, /bin, /opt." >&2\n' >> "/host${SCRIPT_FILE}" + printf ' exit 127\n' >> "/host${SCRIPT_FILE}" + printf 'fi\n' >> "/host${SCRIPT_FILE}" + else + echo "[entrypoint][WARN] AWF_PREFLIGHT_BINARY='${AWF_PREFLIGHT_BINARY}' contains unsafe characters; skipping preflight check." >&2 + fi + fi # Append the actual command arguments # Docker CMD passes commands as ['/bin/bash', '-c', 'command_string']. # Instead of writing the full [bash, -c, cmd] via printf '%q' (which creates diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index b4465330..f85f73b2 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -987,6 +987,26 @@ describe('docker-manager', () => { expect(environment.AWF_REQUIRE_NODE).toBeUndefined(); }); + it('should set AWF_PREFLIGHT_BINARY=codex when running codex command', () => { + const result = generateDockerCompose( + { ...mockConfig, agentCommand: 'codex --version' }, + mockNetworkConfig, + ); + const environment = result.services.agent.environment as Record; + + expect(environment.AWF_PREFLIGHT_BINARY).toBe('codex'); + }); + + it('should not set AWF_PREFLIGHT_BINARY for non-codex commands', () => { + const result = generateDockerCompose( + { ...mockConfig, agentCommand: 'echo test' }, + mockNetworkConfig, + ); + const environment = result.services.agent.environment as Record; + + expect(environment.AWF_PREFLIGHT_BINARY).toBeUndefined(); + }); + it('should pass GOROOT, CARGO_HOME, RUSTUP_HOME, JAVA_HOME, DOTNET_ROOT, BUN_INSTALL to container when env vars are set', () => { const originalGoroot = process.env.GOROOT; const originalCargoHome = process.env.CARGO_HOME; diff --git a/src/docker-manager.ts b/src/docker-manager.ts index c098361e..183c4733 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -824,6 +824,14 @@ export function generateDockerCompose( environment.AWF_REQUIRE_NODE = '1'; } + // For commands whose binary may be absent on some runner slots (e.g. codex), ask the + // agent entrypoint to verify the binary exists inside the chroot before exec'ing, so + // the failure is a clear diagnostic instead of a cryptic shell error. + const isCodexCommand = commandExecutableBase.toLowerCase() === 'codex'; + if (isCodexCommand) { + environment.AWF_PREFLIGHT_BINARY = 'codex'; + } + // When api-proxy is enabled with Copilot, set placeholder tokens early // so --env-all won't override them with real values from host environment if (config.enableApiProxy && config.copilotGithubToken) { From d4d30d596350442a3d8628ab8823656befe75614 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:25:31 +0000 Subject: [PATCH 3/3] fix: exclude AWF_PREFLIGHT_BINARY from env-all and tighten regex Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/003ed05c-0a7b-408c-b8f0-c18c8912a280 --- containers/agent/entrypoint.sh | 10 ++++++---- src/docker-manager.ts | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index b931eeb2..539f5a63 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -721,10 +721,12 @@ AWFEOF # chroot before exec'ing the user command. This provides a fast, human-readable # diagnostic when the runner slot lacks the binary (e.g. codex not installed). if [ -n "${AWF_PREFLIGHT_BINARY:-}" ]; then - # Validate the binary name contains only safe characters to prevent shell injection - # in the generated chroot startup script. - if [[ "${AWF_PREFLIGHT_BINARY}" =~ ^[a-zA-Z0-9_.-]+$ ]]; then - printf 'if ! command -v %s >/dev/null 2>&1; then\n' "${AWF_PREFLIGHT_BINARY}" >> "/host${SCRIPT_FILE}" + # Validate the binary name: must start with an alphanumeric char or underscore, + # then contain only [a-zA-Z0-9_.-] — prevents option injection (e.g. "-v") + # and shell meta-characters in the generated chroot startup script. + if [[ "${AWF_PREFLIGHT_BINARY}" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$ ]]; then + # Use "command -v -- " so the name is never parsed as an option. + printf 'if ! command -v -- %s >/dev/null 2>&1; then\n' "${AWF_PREFLIGHT_BINARY}" >> "/host${SCRIPT_FILE}" printf ' echo "[entrypoint][ERROR] Required binary '"'"'%s'"'"' is not available inside AWF chroot." >&2\n' "${AWF_PREFLIGHT_BINARY}" >> "/host${SCRIPT_FILE}" printf ' echo "[entrypoint][ERROR] Ensure '"'"'%s'"'"' is installed on the runner and present in a PATH directory bind-mounted into /host." >&2\n' "${AWF_PREFLIGHT_BINARY}" >> "/host${SCRIPT_FILE}" printf ' echo "[entrypoint][ERROR] Standard bind-mounted PATH directories: /usr/local/bin, /usr/bin, /bin, /opt." >&2\n' >> "/host${SCRIPT_FILE}" diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 183c4733..370a9d67 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -757,6 +757,9 @@ export function generateDockerCompose( // conflicting with AWF's internal routing (agent → Squid → internet). // AWF sets its own HTTP_PROXY/HTTPS_PROXY pointing to Squid. ...PROXY_ENV_VARS, + // Internal AWF control knobs — must never be inherited from the host environment + // via --env-all; they are set explicitly by generateDockerCompose when needed. + 'AWF_PREFLIGHT_BINARY', ]); // When api-proxy is enabled, exclude API keys from agent environment