Add FedRAMP API Codex binary

This commit is contained in:
Jared Davenport
2026-07-07 16:50:48 -04:00
parent 777f5da55c
commit 7867f85bf7
7 changed files with 436 additions and 4 deletions

View File

@@ -4,7 +4,7 @@ on:
workflow_call:
env:
WINDOWS_BINARIES: "codex codex-code-mode-host codex-responses-api-proxy codex-windows-sandbox-setup codex-command-runner codex-app-server"
WINDOWS_BINARIES: "codex codex-fedramp-api codex-code-mode-host codex-responses-api-proxy codex-windows-sandbox-setup codex-command-runner codex-app-server"
jobs:
build-windows-binaries:
@@ -25,14 +25,14 @@ jobs:
- runner: windows-x64
target: x86_64-pc-windows-msvc
bundle: primary
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-fedramp-api codex-code-mode-host codex-responses-api-proxy"
runs_on:
group: ${{ github.event.repository.name }}-runners
labels: ${{ github.event.repository.name }}-windows-x64
- runner: windows-arm64
target: aarch64-pc-windows-msvc
bundle: primary
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-fedramp-api codex-code-mode-host codex-responses-api-proxy"
runs_on:
group: ${{ github.event.repository.name }}-runners
labels: ${{ github.event.repository.name }}-windows-arm64

View File

@@ -3,6 +3,7 @@ load("//bazel/platforms:release_binaries.bzl", "multiplatform_binaries")
codex_rust_crate(
name = "cli",
compile_data = ["src/fedramp_api_defaults.toml"],
crate_name = "codex_cli",
extra_binaries = [
"//codex-rs/bwrap:bwrap",

View File

@@ -9,6 +9,11 @@ build = "build.rs"
name = "codex"
path = "src/main.rs"
[[bin]]
name = "codex-fedramp-api"
path = "src/main.rs"
test = false
[lib]
name = "codex_cli"
path = "src/lib.rs"

View File

@@ -0,0 +1,168 @@
use anyhow::bail;
use codex_utils_cli::CliConfigOverrides;
use std::ffi::OsString;
use super::LoginSubcommand;
use super::Subcommand;
const FEDRAMP_API_BINARY_NAME: &str = "codex-fedramp-api";
const FEDRAMP_API_DEFAULTS_TOML: &str = include_str!("fedramp_api_defaults.toml");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CodexFlavor {
Normal,
FedRAMPApi,
}
impl CodexFlavor {
pub(crate) fn compiled() -> Self {
Self::from_compiled_binary_name(option_env!("CARGO_BIN_NAME"))
}
fn from_compiled_binary_name(binary_name: Option<&str>) -> Self {
match binary_name {
Some(FEDRAMP_API_BINARY_NAME) => Self::FedRAMPApi,
_ => Self::Normal,
}
}
fn is_fedramp_api(self) -> bool {
self == Self::FedRAMPApi
}
}
pub(crate) fn apply(
flavor: CodexFlavor,
root_config_overrides: &mut CliConfigOverrides,
subcommand: Option<&Subcommand>,
) -> anyhow::Result<()> {
if !flavor.is_fedramp_api() {
return Ok(());
}
reject_unsafe_cli_args(std::env::args_os().skip(1))?;
reject_unsupported_subcommand(subcommand)?;
root_config_overrides
.raw_overrides
.extend(blessed_config_overrides()?);
Ok(())
}
fn blessed_config_overrides() -> anyhow::Result<Vec<String>> {
let config: toml::Value = toml::from_str(FEDRAMP_API_DEFAULTS_TOML)?;
let mut overrides = Vec::new();
flatten_toml_value(&config, None, &mut overrides);
Ok(overrides)
}
fn flatten_toml_value(value: &toml::Value, prefix: Option<&str>, overrides: &mut Vec<String>) {
if let toml::Value::Table(table) = value {
if table.is_empty() {
if let Some(prefix) = prefix {
overrides.push(format!("{prefix}={{}}"));
}
return;
}
for (key, child) in table {
let path = match prefix {
Some(prefix) => format!("{prefix}.{key}"),
None => key.clone(),
};
flatten_toml_value(child, Some(&path), overrides);
}
return;
}
if let Some(prefix) = prefix {
overrides.push(format!("{prefix}={value}"));
}
}
fn reject_unsafe_cli_args(args: impl Iterator<Item = OsString>) -> anyhow::Result<()> {
for arg in args {
let arg = arg.to_string_lossy();
if arg == "--" {
break;
}
if matches!(
arg.as_ref(),
"-c" | "--config"
| "--enable"
| "--disable"
| "-p"
| "--profile"
| "--oss"
| "--local-provider"
| "--remote"
| "--remote-auth-token-env"
) || arg.starts_with("--config=")
|| arg.starts_with("-c")
|| arg.starts_with("--enable=")
|| arg.starts_with("--disable=")
|| arg.starts_with("--profile=")
|| arg.starts_with("-p")
|| arg.starts_with("--local-provider=")
|| arg.starts_with("--remote=")
|| arg.starts_with("--remote-auth-token-env=")
{
bail!("{arg} is not supported by codex-fedramp-api");
}
}
Ok(())
}
fn reject_unsupported_subcommand(subcommand: Option<&Subcommand>) -> anyhow::Result<()> {
match subcommand {
None
| Some(Subcommand::Exec(_))
| Some(Subcommand::Review(_))
| Some(Subcommand::Logout(_))
| Some(Subcommand::Completion(_))
| Some(Subcommand::Doctor(_))
| Some(Subcommand::Sandbox(_))
| Some(Subcommand::Debug(_))
| Some(Subcommand::Execpolicy(_))
| Some(Subcommand::Apply(_))
| Some(Subcommand::Resume(_))
| Some(Subcommand::Archive(_))
| Some(Subcommand::Delete(_))
| Some(Subcommand::Unarchive(_))
| Some(Subcommand::Fork(_)) => Ok(()),
Some(Subcommand::Login(login)) => {
if matches!(login.action, Some(LoginSubcommand::Status))
|| (login.with_api_key
&& !login.with_access_token
&& !login.use_device_code
&& login.api_key.is_none())
{
Ok(())
} else {
bail!("codex-fedramp-api only supports API-key login and login status");
}
}
Some(Subcommand::Mcp(_))
| Some(Subcommand::Plugin(_))
| Some(Subcommand::McpServer(_))
| Some(Subcommand::AppServer(_))
| Some(Subcommand::RemoteControl(_))
| Some(Subcommand::Update)
| Some(Subcommand::Cloud(_))
| Some(Subcommand::ResponsesApiProxy(_))
| Some(Subcommand::StdioToUds(_))
| Some(Subcommand::ExecServer(_))
| Some(Subcommand::Features(_)) => {
bail!("this command is not supported by codex-fedramp-api");
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
Some(Subcommand::App(_)) => {
bail!("this command is not supported by codex-fedramp-api");
}
}
}
#[cfg(test)]
#[path = "fedramp_api_tests.rs"]
mod tests;

View File

@@ -0,0 +1,50 @@
forced_login_method = "api"
openai_base_url = "https://gov.api.openai.com/v1"
model_provider = "openai"
mcp_servers = {}
model_providers = {}
plugins = {}
[features]
apps = false
enable_mcp_apps = false
plugins = false
remote_plugin = false
tool_suggest = false
memories = false
memory_tool = false
multi_agent = false
multi_agent_mode = false
multi_agent_v2 = false
remote_control = false
plugin_hooks = false
plugin_sharing = false
skill_mcp_dependency_install = false
mentions_v2 = false
[memories]
generate_memories = false
use_memories = false
dedicated_tools = false
[skills]
include_instructions = false
config = []
[skills.bundled]
enabled = false
[orchestrator.skills]
enabled = false
[orchestrator.mcp]
enabled = false
[analytics]
enabled = false
[otel]
exporter = "none"
trace_exporter = "none"
metrics_exporter = "none"
log_user_prompt = false

View File

@@ -0,0 +1,204 @@
use super::CodexFlavor;
use super::FEDRAMP_API_DEFAULTS_TOML;
use super::blessed_config_overrides;
use super::reject_unsafe_cli_args;
use codex_utils_cli::CliConfigOverrides;
use pretty_assertions::assert_eq;
#[test]
fn blessed_defaults_are_valid_toml() {
let parsed: toml::Value =
toml::from_str(FEDRAMP_API_DEFAULTS_TOML).expect("blessed defaults should parse");
let table = parsed
.as_table()
.expect("blessed defaults should be a TOML table");
assert_eq!(
table.get("openai_base_url"),
Some(&toml::Value::String(
"https://gov.api.openai.com/v1".to_string()
))
);
assert_eq!(
table.get("forced_login_method"),
Some(&toml::Value::String("api".to_string()))
);
assert_eq!(
table.get("model_provider"),
Some(&toml::Value::String("openai".to_string()))
);
assert_eq!(
table.get("mcp_servers"),
Some(&toml::Value::Table(toml::Table::new()))
);
assert_eq!(
table.get("model_providers"),
Some(&toml::Value::Table(toml::Table::new()))
);
assert_eq!(
table.get("plugins"),
Some(&toml::Value::Table(toml::Table::new()))
);
let features = table
.get("features")
.and_then(toml::Value::as_table)
.expect("features should be a TOML table");
for key in [
"apps",
"enable_mcp_apps",
"plugins",
"remote_plugin",
"tool_suggest",
"memories",
"memory_tool",
"multi_agent",
"multi_agent_mode",
"multi_agent_v2",
"remote_control",
"plugin_hooks",
"plugin_sharing",
"skill_mcp_dependency_install",
"mentions_v2",
] {
assert_eq!(
features.get(key),
Some(&toml::Value::Boolean(false)),
"{key} should be disabled"
);
}
let memories = table
.get("memories")
.and_then(toml::Value::as_table)
.expect("memories should be a TOML table");
for key in ["generate_memories", "use_memories", "dedicated_tools"] {
assert_eq!(
memories.get(key),
Some(&toml::Value::Boolean(false)),
"{key} should be disabled"
);
}
let analytics = table
.get("analytics")
.and_then(toml::Value::as_table)
.expect("analytics should be a TOML table");
assert_eq!(analytics.get("enabled"), Some(&toml::Value::Boolean(false)));
let otel = table
.get("otel")
.and_then(toml::Value::as_table)
.expect("otel should be a TOML table");
for key in ["exporter", "trace_exporter", "metrics_exporter"] {
assert_eq!(
otel.get(key),
Some(&toml::Value::String("none".to_string())),
"{key} should be disabled"
);
}
assert_eq!(
otel.get("log_user_prompt"),
Some(&toml::Value::Boolean(false))
);
}
#[test]
fn blessed_defaults_override_user_values() {
let mut config = toml::Value::Table(toml::Table::from_iter([
(
"openai_base_url".to_string(),
toml::Value::String("https://api.openai.com/v1".to_string()),
),
(
"model_provider".to_string(),
toml::Value::String("ollama".to_string()),
),
(
"mcp_servers".to_string(),
toml::Value::Table(toml::Table::from_iter([(
"attacker".to_string(),
toml::Value::Table(toml::Table::from_iter([(
"command".to_string(),
toml::Value::String("evil".to_string()),
)])),
)])),
),
(
"skills".to_string(),
toml::Value::Table(toml::Table::from_iter([(
"config".to_string(),
toml::Value::Array(vec![toml::Value::Table(toml::Table::from_iter([
(
"name".to_string(),
toml::Value::String("attacker".to_string()),
),
("enabled".to_string(), toml::Value::Boolean(true)),
]))]),
)])),
),
]));
let overrides = CliConfigOverrides {
raw_overrides: blessed_config_overrides().expect("blessed defaults should flatten"),
};
overrides
.apply_on_value(&mut config)
.expect("blessed defaults should apply");
assert_eq!(
config.get("openai_base_url"),
Some(&toml::Value::String(
"https://gov.api.openai.com/v1".to_string()
))
);
assert_eq!(
config.get("model_provider"),
Some(&toml::Value::String("openai".to_string()))
);
assert_eq!(
config.get("mcp_servers"),
Some(&toml::Value::Table(toml::Table::new()))
);
assert_eq!(
config
.get("skills")
.and_then(toml::Value::as_table)
.and_then(|skills| skills.get("config")),
Some(&toml::Value::Array(Vec::new()))
);
}
#[test]
fn rejects_unsafe_config_and_provider_flags() {
for args in [
vec!["-c", "openai_base_url=\"https://api.openai.com/v1\""],
vec!["--config=openai_base_url=\"https://api.openai.com/v1\""],
vec!["--enable=plugins"],
vec!["--profile", "custom"],
vec!["--oss"],
vec!["--local-provider=ollama"],
vec!["--remote=wss://example.com"],
] {
let args = args.into_iter().map(Into::into);
assert!(reject_unsafe_cli_args(args).is_err());
}
}
#[test]
fn allows_prompt_after_argument_separator() {
let args = vec!["--", "--enable", "plugins"]
.into_iter()
.map(Into::into);
assert!(reject_unsafe_cli_args(args).is_ok());
}
#[test]
fn compiled_binary_name_selects_flavor() {
assert_eq!(
CodexFlavor::from_compiled_binary_name(Some("codex-fedramp-api")),
CodexFlavor::FedRAMPApi
);
assert_eq!(
CodexFlavor::from_compiled_binary_name(Some("codex")),
CodexFlavor::Normal
);
}

View File

@@ -50,6 +50,7 @@ mod app_cmd;
mod desktop_app;
mod doctor;
mod exec_server_telemetry;
mod fedramp_api;
mod marketplace_cmd;
mod mcp_cmd;
mod plugin_cmd;
@@ -954,9 +955,10 @@ fn stage_str(stage: Stage) -> &'static str {
}
fn main() -> anyhow::Result<()> {
let flavor = fedramp_api::CodexFlavor::compiled();
let remote_control_disabled = codex_app_server::take_remote_control_disabled_env();
arg0_dispatch_or_else(move |arg0_paths: Arg0DispatchPaths| async move {
cli_main(arg0_paths, remote_control_disabled).await?;
cli_main(arg0_paths, remote_control_disabled, flavor).await?;
Ok(())
})
}
@@ -964,6 +966,7 @@ fn main() -> anyhow::Result<()> {
async fn cli_main(
arg0_paths: Arg0DispatchPaths,
remote_control_disabled: bool,
flavor: fedramp_api::CodexFlavor,
) -> anyhow::Result<()> {
let MultitoolCli {
config_overrides: mut root_config_overrides,
@@ -976,6 +979,7 @@ async fn cli_main(
// Fold --enable/--disable into config overrides so they flow to all subcommands.
let toggle_overrides = feature_toggles.to_overrides()?;
root_config_overrides.raw_overrides.extend(toggle_overrides);
fedramp_api::apply(flavor, &mut root_config_overrides, subcommand.as_ref())?;
let root_remote = remote.remote;
let root_remote_auth_token_env = remote.remote_auth_token_env;
let root_strict_config = interactive.strict_config;