mirror of
https://github.com/openai/codex.git
synced 2026-05-02 18:37:01 +00:00
## Summary This change adds websocket authentication at the app-server transport boundary and enforces it before JSON-RPC `initialize`, so authenticated deployments reject unauthenticated clients during the websocket handshake rather than after a connection has already been admitted. During rollout, websocket auth is opt-in for non-loopback listeners so we do not break existing remote clients. If `--ws-auth ...` is configured, the server enforces auth during websocket upgrade. If auth is not configured, non-loopback listeners still start, but app-server logs a warning and the startup banner calls out that auth should be configured before real remote use. The server supports two auth modes: a file-backed capability token, and a standard HMAC-signed JWT/JWS bearer token verified with the `jsonwebtoken` crate, with optional issuer, audience, and clock-skew validation. Capability tokens are normalized, hashed, and compared in constant time. Short shared secrets for signed bearer tokens are rejected at startup. Requests carrying an `Origin` header are rejected with `403` by transport middleware, and authenticated clients present credentials as `Authorization: Bearer <token>` during websocket upgrade. ## Validation - `cargo test -p codex-app-server transport::auth` - `cargo test -p codex-cli app_server_` - `cargo clippy -p codex-app-server --all-targets -- -D warnings` - `just bazel-lock-check` Note: in the broad `cargo test -p codex-app-server connection_handling_websocket` run, the touched websocket auth cases passed, but unrelated Unix shutdown tests failed with a timeout in this environment. --------- Co-authored-by: Eric Traut <etraut@openai.com>
80 lines
2.3 KiB
Rust
80 lines
2.3 KiB
Rust
use clap::Parser;
|
|
use codex_app_server::AppServerTransport;
|
|
use codex_app_server::AppServerWebsocketAuthArgs;
|
|
use codex_app_server::run_main_with_transport;
|
|
use codex_arg0::Arg0DispatchPaths;
|
|
use codex_arg0::arg0_dispatch_or_else;
|
|
use codex_core::config_loader::LoaderOverrides;
|
|
use codex_protocol::protocol::SessionSource;
|
|
use codex_utils_cli::CliConfigOverrides;
|
|
use std::path::PathBuf;
|
|
|
|
// Debug-only test hook: lets integration tests point the server at a temporary
|
|
// managed config file without writing to /etc.
|
|
const MANAGED_CONFIG_PATH_ENV_VAR: &str = "CODEX_APP_SERVER_MANAGED_CONFIG_PATH";
|
|
|
|
#[derive(Debug, Parser)]
|
|
struct AppServerArgs {
|
|
/// Transport endpoint URL. Supported values: `stdio://` (default),
|
|
/// `ws://IP:PORT`.
|
|
#[arg(
|
|
long = "listen",
|
|
value_name = "URL",
|
|
default_value = AppServerTransport::DEFAULT_LISTEN_URL
|
|
)]
|
|
listen: AppServerTransport,
|
|
|
|
/// Session source used to derive product restrictions and metadata.
|
|
#[arg(
|
|
long = "session-source",
|
|
value_name = "SOURCE",
|
|
default_value = "vscode",
|
|
value_parser = SessionSource::from_startup_arg
|
|
)]
|
|
session_source: SessionSource,
|
|
|
|
#[command(flatten)]
|
|
auth: AppServerWebsocketAuthArgs,
|
|
}
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move {
|
|
let args = AppServerArgs::parse();
|
|
let managed_config_path = managed_config_path_from_debug_env();
|
|
let loader_overrides = LoaderOverrides {
|
|
managed_config_path,
|
|
..Default::default()
|
|
};
|
|
let transport = args.listen;
|
|
let session_source = args.session_source;
|
|
let auth = args.auth.try_into_settings()?;
|
|
|
|
run_main_with_transport(
|
|
arg0_paths,
|
|
CliConfigOverrides::default(),
|
|
loader_overrides,
|
|
/*default_analytics_enabled*/ false,
|
|
transport,
|
|
session_source,
|
|
auth,
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn managed_config_path_from_debug_env() -> Option<PathBuf> {
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
if let Ok(value) = std::env::var(MANAGED_CONFIG_PATH_ENV_VAR) {
|
|
return if value.is_empty() {
|
|
None
|
|
} else {
|
|
Some(PathBuf::from(value))
|
|
};
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|