PRD 0029: Codex host credentials through egress #110

Merged
didericis merged 25 commits from codex/prd-codex-host-credentials into main 2026-06-01 23:16:25 -04:00
4 changed files with 20 additions and 24 deletions
Showing only changes of commit c8ab0c67a8 - Show all commits
+7 -5
View File
@@ -8,7 +8,7 @@ command, default image, and prompt/auth behavior.
from __future__ import annotations from __future__ import annotations
import os import os
from dataclasses import dataclass from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
@@ -71,6 +71,7 @@ class AgentProvisionPlan:
image: str image: str
dockerfile: str dockerfile: str
guest_env: dict[str, str] guest_env: dict[str, str]
env_vars: dict[str, str] = field(default_factory=dict)
dirs: tuple[AgentProvisionDir, ...] = () dirs: tuple[AgentProvisionDir, ...] = ()
files: tuple[AgentProvisionFile, ...] = () files: tuple[AgentProvisionFile, ...] = ()
pre_copy: tuple[AgentProvisionCommand, ...] = () pre_copy: tuple[AgentProvisionCommand, ...] = ()
@@ -124,17 +125,17 @@ def agent_provision_plan(
) -> AgentProvisionPlan: ) -> AgentProvisionPlan:
runtime = runtime_for(template) runtime = runtime_for(template)
resolved_guest_env = dict(guest_env or {}) resolved_guest_env = dict(guest_env or {})
env_vars: dict[str, str] = {}
dirs: list[AgentProvisionDir] = [] dirs: list[AgentProvisionDir] = []
files: list[AgentProvisionFile] = [] files: list[AgentProvisionFile] = []
pre_copy: list[AgentProvisionCommand] = [] pre_copy: list[AgentProvisionCommand] = []
verify: list[AgentProvisionCommand] = [] verify: list[AgentProvisionCommand] = []
if template == PROVIDER_CODEX: if template == PROVIDER_CODEX:
resolved_guest_env.setdefault( env_vars["CODEX_CA_CERTIFICATE"] = "/etc/ssl/certs/ca-certificates.crt"
"CODEX_CA_CERTIFICATE",
"/etc/ssl/certs/ca-certificates.crt",
)
auth_dir = resolved_guest_env.get("CODEX_HOME", f"{guest_home}/.codex") auth_dir = resolved_guest_env.get("CODEX_HOME", f"{guest_home}/.codex")
if forward_host_credentials:
env_vars["CODEX_HOME"] = auth_dir
dirs.append(AgentProvisionDir(auth_dir)) dirs.append(AgentProvisionDir(auth_dir))
config_path = f"{auth_dir}/config.toml" config_path = f"{auth_dir}/config.toml"
config_file = state_dir / "codex-config.toml" config_file = state_dir / "codex-config.toml"
@@ -177,6 +178,7 @@ def agent_provision_plan(
prompt_mode=runtime.prompt_mode, prompt_mode=runtime.prompt_mode,
image=runtime.image, image=runtime.image,
dockerfile=dockerfile, dockerfile=dockerfile,
env_vars=env_vars,
guest_env=resolved_guest_env, guest_env=resolved_guest_env,
dirs=tuple(dirs), dirs=tuple(dirs),
files=tuple(files), files=tuple(files),
+5
View File
@@ -12,6 +12,7 @@ from __future__ import annotations
import os import os
from datetime import datetime, timezone from datetime import datetime, timezone
from dataclasses import replace
from pathlib import Path from pathlib import Path
from ...agent_provider import agent_provision_plan, runtime_for from ...agent_provider import agent_provision_plan, runtime_for
@@ -231,6 +232,10 @@ def resolve_plan(
forward_host_credentials=provider.forward_host_credentials, forward_host_credentials=provider.forward_host_credentials,
host_env=dict(os.environ), host_env=dict(os.environ),
) )
guest_env = dict(agent_provision.guest_env)
for key, val in agent_provision.env_vars.items():
guest_env.setdefault(key, val)
agent_provision = replace(agent_provision, guest_env=guest_env)
return DockerBottlePlan( return DockerBottlePlan(
spec=spec, spec=spec,
+5 -18
View File
@@ -12,6 +12,7 @@ from __future__ import annotations
import os import os
from datetime import datetime, timezone from datetime import datetime, timezone
from dataclasses import replace
from pathlib import Path from pathlib import Path
from ...agent_provider import agent_provision_plan, runtime_for from ...agent_provider import agent_provision_plan, runtime_for
@@ -128,24 +129,6 @@ def resolve_plan(
if provider.template == "claude" and has_provider_auth: if provider.template == "claude" and has_provider_auth:
didericis marked this conversation as resolved Outdated
Outdated
Review

this should also be in the agent provisioner now, assuming we can evaluate has_provider_auth at that stage. If not we'll need a more generic hook to call into here/there should not be any logic specific to an specific type of agent in here anymore.

this should also be in the agent provisioner now, assuming we can evaluate `has_provider_auth` at that stage. If not we'll need a more generic hook to call into here/there should not be any logic specific to an specific type of agent in here anymore.
guest_env.setdefault("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1") guest_env.setdefault("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1")
guest_env.setdefault("DISABLE_ERROR_REPORTING", "1") guest_env.setdefault("DISABLE_ERROR_REPORTING", "1")
if provider.template == "codex":
# Codex is a Rust/rustls client: unlike the Node agents it does
# NOT consult the system trust store or honor NODE_EXTRA_CA_CERTS.
# It reads CODEX_CA_CERTIFICATE (falling back to SSL_CERT_FILE)
# for custom roots, across HTTPS *and* the wss responses channel.
# Point it at the bundle update-ca-certificates rebuilt with the
# egress MITM CA so Codex trusts the proxy and egress can inject
# the host bearer — without this, codex bottles need
# pipelock tls_passthrough, which disables auth injection.
guest_env["CODEX_CA_CERTIFICATE"] = (
"/etc/ssl/certs/ca-certificates.crt"
)
if provider.template == "codex" and provider.forward_host_credentials:
# Smolvm exec process trees do not reliably inherit the image
# user's login environment. Pin CODEX_HOME to the same path
# provision_provider_auth writes so Codex never falls back to a
# root or unset home and shows the sign-in picker.
guest_env["CODEX_HOME"] = "/home/node/.codex"
supervise_plan = None supervise_plan = None
if bottle.supervise: if bottle.supervise:
1
@@ -189,6 +172,10 @@ def resolve_plan(
forward_host_credentials=provider.forward_host_credentials, forward_host_credentials=provider.forward_host_credentials,
host_env=dict(os.environ), host_env=dict(os.environ),
) )
merged_guest_env = dict(agent_provision.guest_env)
for key, val in agent_provision.env_vars.items():
merged_guest_env.setdefault(key, val)
agent_provision = replace(agent_provision, guest_env=merged_guest_env)
return SmolmachinesBottlePlan( return SmolmachinesBottlePlan(
spec=spec, spec=spec,
+3 -1
View File
@@ -42,8 +42,9 @@ class TestAgentProviderRuntime(unittest.TestCase):
self.assertEqual("/tmp/Dockerfile.codex", plan.dockerfile) self.assertEqual("/tmp/Dockerfile.codex", plan.dockerfile)
self.assertEqual( self.assertEqual(
"/etc/ssl/certs/ca-certificates.crt", "/etc/ssl/certs/ca-certificates.crt",
plan.guest_env["CODEX_CA_CERTIFICATE"], plan.env_vars["CODEX_CA_CERTIFICATE"],
) )
self.assertEqual({}, plan.guest_env)
self.assertEqual(("/home/node/.codex",), tuple(d.guest_path for d in plan.dirs)) self.assertEqual(("/home/node/.codex",), tuple(d.guest_path for d in plan.dirs))
self.assertEqual( self.assertEqual(
("/home/node/.codex/config.toml",), ("/home/node/.codex/config.toml",),
@@ -70,6 +71,7 @@ class TestAgentProviderRuntime(unittest.TestCase):
"/run/codex-home/auth.json", "/run/codex-home/auth.json",
{f.guest_path for f in plan.files}, {f.guest_path for f in plan.files},
) )
self.assertEqual("/run/codex-home", plan.env_vars["CODEX_HOME"])
self.assertEqual(1, len(plan.pre_copy)) self.assertEqual(1, len(plan.pre_copy))
self.assertEqual(1, len(plan.verify)) self.assertEqual(1, len(plan.verify))
self.assertIn("CODEX_HOME=/run/codex-home", plan.verify[0].argv) self.assertIn("CODEX_HOME=/run/codex-home", plan.verify[0].argv)