Current section

Files

Jump to
agent_session_manager lib asm runtime_auth codex_materialization.ex
Raw

lib/asm/runtime_auth/codex_materialization.ex

defmodule ASM.RuntimeAuth.CodexMaterialization do
@moduledoc """
Codex-specific governed materialization guard owned by ASM.
Codex-native auth, OAuth, and app-server mechanics remain in `codex_sdk`.
This module only decides whether a governed ASM run may hand a materialized
Codex command/env/cwd/config-root envelope to that SDK boundary.
"""
alias ASM.{Error, RuntimeAuth}
alias CliSubprocessCore.ExecutionSurface
@default_openai_base_url "https://api.openai.com/v1"
@metadata_key :codex_materialization
@materialized_keys [:codex_materialized_runtime, :materialized_runtime]
@sources [:verified_materializer, :provider_auth_backend]
@target_auth_postures [
:auth_state_preinstalled,
:materialize_on_attach,
:proxy_only,
:no_credentials
]
@provider_override_keys [
:command,
:cmd,
:cli_path,
:codex_path,
:codex_path_override,
:cwd,
:working_directory,
:env,
:process_env,
:config_root,
:auth_root,
:codex_home,
:codex_home_override,
:config_values,
:config_overrides,
# secret-safe: key-name registry (names, not values)
:api_key,
:base_url,
:openai_base_url,
:openai_api_key,
:codex_api_key,
:provider_backend,
:model_provider,
:oss_provider,
:ollama,
:ollama_model,
:ollama_base_url
]
@transport_override_keys [
:command,
:cmd,
:cwd,
:env,
:process_env,
:config_root,
:auth_root,
:codex_home,
:config_values,
:config_overrides,
# secret-safe: key-name registry (names, not values)
:api_key,
:base_url,
:openai_base_url,
# secret-safe: key-name registry (names, not values)
:auth_token,
:auth_token_env
]
@ambient_env_keys [
"CODEX_API_KEY",
"OPENAI_API_KEY",
"CODEX_HOME",
"OPENAI_BASE_URL"
]
@provider_env_keys [
"CODEX_API_KEY",
"OPENAI_API_KEY",
"CODEX_HOME",
"OPENAI_BASE_URL",
"CODEX_PROVIDER_BACKEND",
"CODEX_OSS_PROVIDER",
"CODEX_OLLAMA_BASE_URL"
]
@enforce_keys [
:command,
:cwd,
:env,
:config_root,
:credential_lease_ref,
:native_auth_assertion_ref,
:connector_binding_ref,
:provider_account_ref,
:native_auth_assertion
]
# api_key, env, and native_auth_assertion carry live credentials — keep
# them out of inspect output (crash reports, Logger metadata, :observer).
@derive {Inspect, except: [:api_key, :env, :native_auth_assertion]}
defstruct [
:command,
:cwd,
:env,
:config_root,
:credential_lease_ref,
:native_auth_assertion_ref,
:connector_binding_ref,
:provider_account_ref,
:native_auth_assertion,
api_key: nil,
base_url: nil,
clear_env?: true,
source: :verified_materializer,
target_auth_posture: :materialize_on_attach
]
@type t :: %__MODULE__{
command: String.t(),
cwd: String.t(),
env: %{optional(String.t()) => String.t()},
config_root: String.t(),
credential_lease_ref: String.t(),
native_auth_assertion_ref: String.t(),
connector_binding_ref: String.t(),
provider_account_ref: String.t(),
native_auth_assertion: map(),
api_key: String.t() | nil,
base_url: String.t() | nil,
clear_env?: true,
source: atom(),
target_auth_posture: atom()
}
@spec authorize_config(map(), keyword()) :: {:ok, t() | nil} | {:error, Error.t()}
def authorize_config(config, provider_opts)
when is_map(config) and is_list(provider_opts) do
metadata = Map.get(config, :metadata, %{})
runtime_auth = Map.get(config, :runtime_auth, metadata)
if RuntimeAuth.governed_context?(runtime_auth) or RuntimeAuth.governed_context?(metadata) do
with :ok <- require_governed_authority(runtime_auth, metadata),
{:ok, materialized_attrs} <- fetch_materialized_runtime(config),
{:ok, materialization} <- new(materialized_attrs, runtime_auth),
:ok <- reject_unmanaged_process_env(materialization),
:ok <- reject_provider_smuggling(provider_opts),
:ok <- reject_execution_surface_smuggling(Map.get(config, :execution_surface)),
:ok <- reject_backend_smuggling(Map.get(config, :backend_opts, [])) do
{:ok, materialization}
end
else
{:ok, nil}
end
end
@spec new(term(), RuntimeAuth.t() | map()) :: {:ok, t()} | {:error, Error.t()}
def new(%__MODULE__{} = materialization, runtime_auth) do
validate_materialization(materialization, runtime_auth)
end
def new(attrs, runtime_auth) when is_map(attrs) or is_list(attrs) do
with {:ok, source} <- normalize_source(attr(attrs, :source, :verified_materializer)),
{:ok, posture} <-
normalize_target_auth_posture(
attr(attrs, :target_auth_posture, :materialize_on_attach)
),
{:ok, env} <- normalize_env(attr(attrs, :env, %{})),
{:ok, config_root} <- required_string(attrs, :config_root),
{:ok, env} <- put_config_root_env(env, config_root),
{:ok, assertion_ref} <-
materialized_ref(attrs, runtime_auth, :native_auth_assertion_ref),
{:ok, lease_ref} <- materialized_ref(attrs, runtime_auth, :credential_lease_ref),
{:ok, binding_ref} <- materialized_ref(attrs, runtime_auth, :connector_binding_ref),
{:ok, account_ref} <- materialized_ref(attrs, runtime_auth, :provider_account_ref),
{:ok, assertion} <- native_auth_assertion(attrs, assertion_ref) do
%__MODULE__{
command: string_attr(attrs, :command) || string_attr(attrs, :codex_path),
cwd: string_attr(attrs, :cwd),
env: env,
config_root: config_root,
credential_lease_ref: lease_ref,
native_auth_assertion_ref: assertion_ref,
connector_binding_ref: binding_ref,
provider_account_ref: account_ref,
native_auth_assertion: assertion,
api_key: string_attr(attrs, :api_key),
base_url: string_attr(attrs, :base_url),
clear_env?: attr(attrs, :clear_env?, true),
source: source,
target_auth_posture: posture
}
|> validate_materialization(runtime_auth)
end
end
def new(attrs, _runtime_auth) do
{:error,
config_error(
"codex materialized runtime must be a map, keyword list, or %#{inspect(__MODULE__)}{}",
cause: attrs
)}
end
@spec redacted_evidence(t() | nil) :: map() | nil
def redacted_evidence(nil), do: nil
def redacted_evidence(%__MODULE__{} = materialization) do
%{
source: materialization.source,
target_auth_posture: materialization.target_auth_posture,
clear_env?: materialization.clear_env?,
command: :redacted_materialized_command,
cwd: :redacted_materialized_cwd,
config_root: :redacted_materialized_config_root,
env_keys: materialization.env |> Map.keys() |> Enum.sort(),
credential_lease_ref: materialization.credential_lease_ref,
native_auth_assertion_ref: materialization.native_auth_assertion_ref,
native_auth_introspection_level:
Map.get(materialization.native_auth_assertion, :introspection_level),
native_auth_limits: Map.get(materialization.native_auth_assertion, :limits),
connector_binding_ref: materialization.connector_binding_ref,
provider_account_ref: materialization.provider_account_ref
}
end
@spec put_redacted_metadata(map(), t() | nil) :: map()
def put_redacted_metadata(metadata, nil) when is_map(metadata), do: metadata
def put_redacted_metadata(metadata, %__MODULE__{} = materialization) when is_map(metadata) do
Map.put(metadata, @metadata_key, redacted_evidence(materialization))
end
@spec codex_option_attrs(t() | nil) :: keyword()
def codex_option_attrs(nil), do: []
def codex_option_attrs(%__MODULE__{} = materialization) do
[
api_key: materialization.api_key || "",
base_url: materialization.base_url || default_codex_base_url(),
codex_path_override: materialization.command
]
end
@spec thread_attrs(t() | nil) :: keyword()
def thread_attrs(nil), do: []
def thread_attrs(%__MODULE__{} = materialization), do: [working_directory: materialization.cwd]
@spec exec_attrs(t() | nil) :: keyword()
def exec_attrs(nil), do: []
def exec_attrs(%__MODULE__{} = materialization) do
[env: materialization.env, clear_env?: materialization.clear_env?]
end
@spec connect_opts(t() | nil) :: keyword()
def connect_opts(nil), do: []
def connect_opts(%__MODULE__{} = materialization) do
[
cwd: materialization.cwd,
process_env: materialization.env,
clear_env?: materialization.clear_env?,
codex_home: materialization.config_root
]
end
@spec metadata_key() :: atom()
def metadata_key, do: @metadata_key
defp require_governed_authority(runtime_auth, metadata) do
if RuntimeAuth.governed_authority?(runtime_auth) or RuntimeAuth.governed_authority?(metadata) do
:ok
else
{:error,
config_error(
"governed Codex strict mode requires credential lease, native auth assertion, authority, connector, and provider-account evidence before materialization",
cause: %{runtime_auth: runtime_auth, metadata: metadata}
)}
end
end
defp fetch_materialized_runtime(config) do
Enum.find_value(@materialized_keys, fn key ->
case Map.fetch(config, key) do
{:ok, value} -> {:ok, value}
:error -> nil
end
end) ||
{:error,
config_error(
"governed Codex strict mode requires a materialized runtime from the verified provider-auth materializer"
)}
end
defp validate_materialization(%__MODULE__{} = materialization, runtime_auth) do
cond do
not present?(materialization.command) ->
{:error, config_error("codex materialized runtime is missing command")}
not present?(materialization.cwd) ->
{:error, config_error("codex materialized runtime is missing cwd")}
materialization.clear_env? != true ->
{:error, config_error("codex materialized runtime must set clear_env? true")}
not materialized_refs_match?(materialization, runtime_auth) ->
{:error,
config_error(
"codex materialized runtime refs do not match governed runtime_auth binding",
cause: ref_match_cause(materialization, runtime_auth)
)}
true ->
{:ok, materialization}
end
end
defp reject_unmanaged_process_env(%__MODULE__{} = materialization) do
unmanaged =
Enum.filter(@ambient_env_keys, fn key ->
case ASM.Env.get(key) do
nil -> false
"" -> false
value -> Map.get(materialization.env, key) != value
end
end)
if unmanaged == [] do
:ok
else
{:error,
config_error(
"governed Codex strict mode rejects unmanaged ambient provider auth environment",
cause: %{env_keys: unmanaged}
)}
end
end
defp reject_provider_smuggling(provider_opts) when is_list(provider_opts) do
provider_opts
|> reject_key_smuggling(@provider_override_keys, "provider options")
|> continue_if_ok(fn ->
reject_env_smuggling(Keyword.get(provider_opts, :env), "provider env")
end)
|> continue_if_ok(fn ->
reject_model_payload_smuggling(Keyword.get(provider_opts, :model_payload))
end)
end
defp reject_execution_surface_smuggling(nil), do: :ok
defp reject_execution_surface_smuggling(%ExecutionSurface{} = execution_surface) do
reject_transport_smuggling(execution_surface.transport_options, "execution_surface")
end
defp reject_execution_surface_smuggling(execution_surface) when is_map(execution_surface) do
execution_surface
|> attr(:transport_options, [])
|> reject_transport_smuggling("execution_surface")
end
defp reject_execution_surface_smuggling(execution_surface) when is_list(execution_surface) do
execution_surface
|> Keyword.get(:transport_options, [])
|> reject_transport_smuggling("execution_surface")
end
defp reject_execution_surface_smuggling(_other), do: :ok
defp reject_backend_smuggling(backend_opts) when is_list(backend_opts) do
backend_opts
|> Keyword.get(:connect_opts, [])
|> reject_transport_smuggling("connect_opts")
|> continue_if_ok(fn ->
backend_opts
|> Keyword.get(:run_opts, [])
|> reject_transport_smuggling("run_opts")
end)
end
defp reject_backend_smuggling(_backend_opts), do: :ok
defp reject_transport_smuggling(options, label) do
options
|> reject_key_smuggling(@transport_override_keys, label)
|> continue_if_ok(fn -> reject_env_smuggling(attr(options, :env), label) end)
|> continue_if_ok(fn -> reject_env_smuggling(attr(options, :process_env), label) end)
end
defp reject_key_smuggling(options, keys, label) when is_list(options) or is_map(options) do
present_keys =
keys
|> Enum.filter(&has_attr?(options, &1))
|> Enum.uniq()
if present_keys == [] do
:ok
else
{:error,
config_error(
"governed Codex strict mode rejects materialized runtime override keys in #{label}",
cause: %{keys: present_keys, surface: label}
)}
end
end
defp reject_key_smuggling(_options, _keys, _label), do: :ok
defp reject_env_smuggling(nil, _label), do: :ok
defp reject_env_smuggling([], _label), do: :ok
defp reject_env_smuggling(%{} = env, label), do: reject_provider_env_keys(Map.keys(env), label)
defp reject_env_smuggling(env, label) when is_list(env) do
if Keyword.keyword?(env) do
env |> Keyword.keys() |> reject_provider_env_keys(label)
else
{:error,
config_error("governed Codex strict mode rejects invalid env override shape",
cause: %{surface: label}
)}
end
end
defp reject_env_smuggling(_env, label) do
{:error,
config_error("governed Codex strict mode rejects invalid env override shape",
cause: %{surface: label}
)}
end
defp reject_provider_env_keys(keys, label) do
rejected =
keys
|> Enum.map(&to_string/1)
|> Enum.filter(&(&1 in @provider_env_keys))
|> Enum.uniq()
if rejected == [] do
:ok
else
{:error,
config_error(
"governed Codex strict mode rejects provider auth env outside the materializer",
cause: %{env_keys: rejected, surface: label}
)}
end
end
defp reject_model_payload_smuggling(nil), do: :ok
defp reject_model_payload_smuggling(model_payload) when is_map(model_payload) do
env_overrides = attr(model_payload, :env_overrides, %{})
backend_metadata = attr(model_payload, :backend_metadata, %{})
config_values = attr(backend_metadata, :config_values, [])
cond do
env_overrides != %{} ->
{:error,
config_error(
"governed Codex strict mode rejects model-payload env overrides outside the materializer",
cause: %{payload_field: :env_overrides}
)}
List.wrap(config_values) != [] ->
{:error,
config_error(
"governed Codex strict mode rejects model-payload config overrides outside the materializer",
cause: %{payload_field: :config_values}
)}
true ->
:ok
end
end
defp reject_model_payload_smuggling(_model_payload), do: :ok
defp continue_if_ok(:ok, fun) when is_function(fun, 0), do: fun.()
defp continue_if_ok(error, _fun), do: error
defp normalize_source(source) when source in @sources, do: {:ok, source}
defp normalize_source(source) when is_binary(source) do
case source do
"verified_materializer" -> {:ok, :verified_materializer}
"provider_auth_backend" -> {:ok, :provider_auth_backend}
_ -> invalid_source(source)
end
end
defp normalize_source(source), do: invalid_source(source)
defp invalid_source(source) do
{:error,
config_error("codex materialized runtime source must be verified_materializer",
cause: %{source: source}
)}
end
defp normalize_target_auth_posture(posture) when posture in @target_auth_postures,
do: {:ok, posture}
defp normalize_target_auth_posture(posture) when is_binary(posture) do
case posture do
"auth_state_preinstalled" -> {:ok, :auth_state_preinstalled}
"materialize_on_attach" -> {:ok, :materialize_on_attach}
"proxy_only" -> {:ok, :proxy_only}
"no_credentials" -> {:ok, :no_credentials}
_ -> invalid_target_auth_posture(posture)
end
end
defp normalize_target_auth_posture(posture), do: invalid_target_auth_posture(posture)
defp invalid_target_auth_posture(posture) do
{:error,
config_error("invalid Codex target auth posture for materialized runtime",
cause: %{target_auth_posture: posture}
)}
end
defp normalize_env(env) when env in [nil, %{}, []], do: {:ok, %{}}
defp normalize_env(env) when is_map(env) do
{:ok, Map.new(env, fn {key, value} -> {to_string(key), to_string(value)} end)}
end
defp normalize_env(env) when is_list(env) do
if Keyword.keyword?(env) do
{:ok,
env |> Map.new() |> Map.new(fn {key, value} -> {to_string(key), to_string(value)} end)}
else
{:error, config_error("codex materialized runtime env must be a map or keyword list")}
end
end
defp normalize_env(_env),
do: {:error, config_error("codex materialized runtime env must be a map or keyword list")}
defp put_config_root_env(env, config_root) do
case Map.get(env, "CODEX_HOME") do
nil ->
{:ok, Map.put(env, "CODEX_HOME", config_root)}
^config_root ->
{:ok, env}
other ->
{:error,
config_error("codex materialized runtime env CODEX_HOME must match config_root",
cause: %{config_root: :redacted, env_codex_home: other}
)}
end
end
defp native_auth_assertion(attrs, assertion_ref) do
assertion = attr(attrs, :native_auth_assertion, %{})
level = attr(assertion, :introspection_level) || attr(attrs, :native_auth_introspection_level)
limits = attr(assertion, :limits) || attr(attrs, :native_auth_limits)
cond do
not present?(assertion_ref) ->
{:error, config_error("codex materialized runtime is missing native auth assertion ref")}
not present?(level) ->
{:error,
config_error(
"codex materialized runtime native auth assertion is missing introspection level"
)}
not is_map(limits) ->
{:error,
config_error("codex materialized runtime native auth assertion is missing limits")}
true ->
{:ok,
%{
ref: assertion_ref,
introspection_level: level,
limits: limits,
redacted?: attr(assertion, :redacted?, true) != false
}}
end
end
defp materialized_ref(attrs, runtime_auth, key) do
value = string_attr(attrs, key) || binding_value(runtime_auth, key)
if present?(value) do
{:ok, value}
else
{:error, config_error("codex materialized runtime is missing #{key}", cause: %{key: key})}
end
end
defp required_string(attrs, key) do
case string_attr(attrs, key) do
value when is_binary(value) ->
{:ok, value}
_ ->
{:error, config_error("codex materialized runtime is missing #{key}", cause: %{key: key})}
end
end
defp materialized_refs_match?(%__MODULE__{} = materialization, runtime_auth) do
[
{:credential_lease_ref, materialization.credential_lease_ref},
{:native_auth_assertion_ref, materialization.native_auth_assertion_ref},
{:connector_binding_ref, materialization.connector_binding_ref},
{:provider_account_ref, materialization.provider_account_ref}
]
|> Enum.all?(fn {key, value} ->
binding = binding_value(runtime_auth, key)
binding in [nil, value]
end)
end
defp ref_match_cause(%__MODULE__{} = materialization, runtime_auth) do
%{
materialized: %{
credential_lease_ref: materialization.credential_lease_ref,
native_auth_assertion_ref: materialization.native_auth_assertion_ref,
connector_binding_ref: materialization.connector_binding_ref,
provider_account_ref: materialization.provider_account_ref
},
runtime_auth: %{
credential_lease_ref: binding_value(runtime_auth, :credential_lease_ref),
native_auth_assertion_ref: binding_value(runtime_auth, :native_auth_assertion_ref),
connector_binding_ref: binding_value(runtime_auth, :connector_binding_ref),
provider_account_ref: binding_value(runtime_auth, :provider_account_ref)
}
}
end
defp binding_value(%RuntimeAuth{} = runtime_auth, :connector_binding_ref),
do: runtime_auth.connector_binding.ref
defp binding_value(%RuntimeAuth{} = runtime_auth, :provider_account_ref),
do: runtime_auth.provider_account_identity.ref
defp binding_value(%RuntimeAuth{} = runtime_auth, key),
do: Map.get(runtime_auth.connector_binding, key)
defp binding_value(metadata, key) when is_map(metadata) do
runtime_auth = attr(metadata, :runtime_auth, %{})
binding = attr(metadata, :connector_binding) || attr(runtime_auth, :connector_binding, %{})
case key do
:connector_binding_ref -> attr(metadata, key) || attr(binding, :ref)
:provider_account_ref -> attr(metadata, key) || attr(binding, key)
_ -> attr(metadata, key) || attr(binding, key)
end
end
defp binding_value(_runtime_auth, _key), do: nil
defp has_attr?(attrs, key) when is_atom(key) do
attrs
|> attr(key)
|> present_override?()
end
defp present_override?(nil), do: false
defp present_override?(value) when is_binary(value), do: String.trim(value) != ""
defp present_override?(value) when is_list(value), do: value != []
defp present_override?(value) when is_map(value), do: map_size(value) > 0
defp present_override?(value), do: not is_nil(value)
defp attr(attrs, key, default \\ nil)
defp attr(nil, _key, default), do: default
defp attr(%{} = attrs, key, default) when is_atom(key) do
Map.get(attrs, key, Map.get(attrs, Atom.to_string(key), default))
end
defp attr(attrs, key, default) when is_list(attrs) and is_atom(key) do
case Keyword.fetch(attrs, key) do
{:ok, value} -> value
:error -> list_value(attrs, Atom.to_string(key), default)
end
end
defp list_value(list, key, default) do
Enum.find_value(list, default, fn
{^key, value} -> value
_other -> nil
end)
end
defp string_attr(attrs, key) do
case attr(attrs, key) do
value when is_binary(value) ->
value = String.trim(value)
if value == "", do: nil, else: value
_other ->
nil
end
end
defp present?(value) when is_binary(value), do: String.trim(value) != ""
defp present?(value), do: not is_nil(value)
defp default_codex_base_url do
module = :"Elixir.Codex.Config.BaseURL"
if Code.ensure_loaded?(module) and function_exported?(module, :default, 0) do
:erlang.apply(module, :default, [])
else
@default_openai_base_url
end
end
defp config_error(message, opts \\ []) do
Error.new(:config_invalid, :config, message, opts)
end
end