Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions containers/agent/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,25 @@ 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: 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 -- <binary>" 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}"
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
Expand Down
20 changes: 20 additions & 0 deletions src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>;

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<string, string>;

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;
Expand Down
11 changes: 11 additions & 0 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -824,6 +827,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';
}
Comment on lines +833 to +836

// 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) {
Expand Down
Loading