mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
Add FedRAMP API Codex binary
This commit is contained in:
6
.github/workflows/rust-release-windows.yml
vendored
6
.github/workflows/rust-release-windows.yml
vendored
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
168
codex-rs/cli/src/fedramp_api.rs
Normal file
168
codex-rs/cli/src/fedramp_api.rs
Normal 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;
|
||||
50
codex-rs/cli/src/fedramp_api_defaults.toml
Normal file
50
codex-rs/cli/src/fedramp_api_defaults.toml
Normal 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
|
||||
204
codex-rs/cli/src/fedramp_api_tests.rs
Normal file
204
codex-rs/cli/src/fedramp_api_tests.rs
Normal 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
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user