Skip to content
Closed
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
2 changes: 2 additions & 0 deletions docs/pages/reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Optional required-by-mode variables:
| `OTEL_TRACES_EXPORTER` | `apiRs.extraEnv`. | Set to `otlp` to force OTLP trace export, or `none`/`off` to disable it. |
| `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | `apiRs.extraEnv`. | Enables OTLP trace export to Tempo, Jaeger, or another OTLP collector. |
| `apiRs.workflowHostSandbox`, `WORKFLOW_HOST_SANDBOX` | Helm value, default `true`; override with `apiRs.extraEnv`. | Runs workflow hosts in Kubernetes sandboxes instead of the api-rs process. Required for workflow-scoped principals. |
| `WORKFLOW_HOST_PASSTHROUGH_ENV` | `apiRs.extraEnv`; comma-delimited variable names. | Copies named variables from the api-rs process into workflow-host sandboxes only. Names listed only here are never copied into interactive session sandboxes; duplicate names are collapsed. |
| `SESSION_SANDBOX_PASSTHROUGH_ENV` | `apiRs.extraEnv`; comma-delimited variable names. | Copies named variables from the api-rs process into interactive session sandboxes and, for backwards compatibility, workflow-host sandboxes. |
| `apiRs.metrics.scrapeAnnotations` | Helm value, default `true`. | Adds Prometheus scrape annotations to the API-RS Pod template and Service. |
| `apiRs.metrics.path` | Helm value, default `/metrics`. | Metrics scrape path for annotation-based discovery. |
| `apiRs.metrics.annotations` | Helm value. | Additional scrape annotations for Prometheus-compatible collectors. |
Expand Down
3 changes: 3 additions & 0 deletions docs/public/md/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ Optional required-by-mode variables:
| `CENTAUR_ENVIRONMENT`, `DEPLOY_ENV`, `ENVIRONMENT` | `apiRs.extraEnv` or deployment env. | Deployment environment resource attribute for telemetry. |
| `OTEL_TRACES_EXPORTER` | `apiRs.extraEnv`. | Set to `otlp` to force OTLP trace export, or `none`/`off` to disable it. |
| `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | `apiRs.extraEnv`. | Enables OTLP trace export to Tempo, Jaeger, or another OTLP collector. |
| `apiRs.workflowHostSandbox`, `WORKFLOW_HOST_SANDBOX` | Helm value, default `true`; override with `apiRs.extraEnv`. | Runs workflow hosts in Kubernetes sandboxes instead of the api-rs process. Required for workflow-scoped principals. |
| `WORKFLOW_HOST_PASSTHROUGH_ENV` | `apiRs.extraEnv`; comma-delimited variable names. | Copies named variables from the api-rs process into workflow-host sandboxes only. Names listed only here are never copied into interactive session sandboxes; duplicate names are collapsed. |
| `SESSION_SANDBOX_PASSTHROUGH_ENV` | `apiRs.extraEnv`; comma-delimited variable names. | Copies named variables from the api-rs process into interactive session sandboxes and, for backwards compatibility, workflow-host sandboxes. |
| `apiRs.metrics.scrapeAnnotations` | Helm value, default `true`. | Adds Prometheus scrape annotations to the API-RS Pod template and Service. |
| `apiRs.metrics.path` | Helm value, default `/metrics`. | Metrics scrape path for annotation-based discovery. |
| `apiRs.metrics.annotations` | Helm value. | Additional scrape annotations for Prometheus-compatible collectors. |
Expand Down
106 changes: 105 additions & 1 deletion services/api-rs/crates/centaur-api-server/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,15 @@ struct SandboxArgs {
value_delimiter = ','
)]
passthrough_env: Vec<String>,
/// Comma-delimited API process variables copied only into workflow-host
/// sandboxes. Unlike SESSION_SANDBOX_PASSTHROUGH_ENV, these values are not
/// exposed to interactive session sandboxes.
#[arg(
long = "workflow-host-passthrough-env",
env = "WORKFLOW_HOST_PASSTHROUGH_ENV",
value_delimiter = ','
)]
workflow_host_passthrough_env: Vec<String>,
/// Operator-supplied sandbox env as a JSON list of `{"name","value"}`
/// objects — the chart renders `sandbox.extraEnv` into this (the same
/// contract as the Python control plane's `KUBERNETES_SANDBOX_EXTRA_ENV`).
Expand Down Expand Up @@ -1255,7 +1264,13 @@ impl SandboxArgs {
envs.push(("TOOLS_OVERLAY_PATH".to_owned(), value));
}

for name in self.passthrough_env_names() {
// Keep SESSION_SANDBOX_PASSTHROUGH_ENV available here for backwards
// compatibility, then layer workflow-host-only names on top. Upserting
// ensures duplicate and overlapping allowlist entries produce one env.
for name in self
.passthrough_env_names()
.chain(self.workflow_host_passthrough_env_names())
{
if let Ok(value) = env::var(name) {
if let Some((_, existing_value)) = envs
.iter_mut()
Expand Down Expand Up @@ -1287,6 +1302,14 @@ impl SandboxArgs {
.filter(|name| !name.is_empty())
}

fn workflow_host_passthrough_env_names(&self) -> impl Iterator<Item = &str> {
self.workflow_host_passthrough_env
.iter()
.flat_map(|entry| entry.split(','))
.map(str::trim)
.filter(|name| !name.is_empty())
}

fn discover_tool_proxy_fragment(
&self,
) -> Result<Option<DiscoveredToolProxyFragment>, ServerError> {
Expand Down Expand Up @@ -2935,6 +2958,87 @@ mod tests {
);
}

#[test]
fn workflow_host_passthrough_env_is_workflow_only_and_deduplicated() {
let _lock = ENV_LOCK.lock().unwrap();
let _env = EnvGuard::set(&[
("SESSION_SHARED", "session-value"),
("WORKFLOW_ONLY", "workflow-value"),
("OVERLAPPING_ENV", "shared-value"),
(GITHUB_TOKEN_ENV, "workflow-token-value"),
]);
let args = Args::try_parse_from([
"centaur-api-server",
"--database-url",
"postgres://postgres:postgres@localhost/centaur",
"--session-sandbox-backend",
"agent-k8s",
"--session-sandbox-workload",
"codex-app-server",
"--session-sandbox-passthrough-env",
"SESSION_SHARED,OVERLAPPING_ENV",
"--workflow-host-passthrough-env",
"WORKFLOW_ONLY,WORKFLOW_ONLY,OVERLAPPING_ENV,GITHUB_TOKEN",
"--kubernetes-sandbox-iron-proxy-mode",
"disabled",
])
.unwrap();

let workflow_spec = args.sandbox.workflow_host_spec(None).unwrap();
let session_env = args.sandbox.codex_app_server_env_template().unwrap();

let workflow_value = |key: &str| {
workflow_spec
.env
.iter()
.find(|env| env.name == key)
.map(|env| env.value.as_str())
};
let session_value = |key: &str| {
session_env
.iter()
.find(|(name, _)| name == key)
.map(|(_, value)| value.as_str())
};

// Preserve the existing shared passthrough behavior.
assert_eq!(workflow_value("SESSION_SHARED"), Some("session-value"));
assert_eq!(session_value("SESSION_SHARED"), Some("session-value"));
assert_eq!(session_value("OVERLAPPING_ENV"), Some("shared-value"));

// The new allowlist is workflow-host-only.
assert_eq!(workflow_value("WORKFLOW_ONLY"), Some("workflow-value"));
assert_eq!(session_value("WORKFLOW_ONLY"), None);

// Workflow-only passthrough is applied after the workflow template,
// while the interactive template retains its proxy placeholder.
assert_eq!(
workflow_value(GITHUB_TOKEN_ENV),
Some("workflow-token-value")
);
assert_eq!(session_value(GITHUB_TOKEN_ENV), Some(GITHUB_TOKEN_ENV));

// Duplicate entries within the new list and overlaps with the shared
// list are collapsed without changing the API process value.
assert_eq!(
workflow_spec
.env
.iter()
.filter(|env| env.name == "WORKFLOW_ONLY")
.count(),
1
);
assert_eq!(
workflow_spec
.env
.iter()
.filter(|env| env.name == "OVERLAPPING_ENV")
.count(),
1
);
assert_eq!(workflow_value("OVERLAPPING_ENV"), Some("shared-value"));
}

#[test]
fn codex_app_server_env_template_injects_auth_mode_and_placeholder() {
let args = Args::try_parse_from([
Expand Down
Loading