permissions: add built-in default profiles (#19900)

## Why

The migration away from `SandboxPolicy` needs new configs to start from
permissions profiles instead of deriving profiles from legacy sandbox
modes. Existing users can have empty `config.toml` files, and we should
not rewrite user-owned config files that may live in shared
repositories.

This PR introduces built-in profile names so an empty config can resolve
to a canonical `PermissionProfile`, while explicit named `[permissions]`
profiles still behave predictably.

## What changed

- Adds built-in `default_permissions` profile names:
  - `:read-only` maps to `PermissionProfile::read_only()`.
- `:workspace` maps to the workspace-write profile, including
project-root metadata carveouts.
- `:danger-no-sandbox` maps to `PermissionProfile::Disabled`, preserving
the distinction between no sandbox and a broad managed sandbox.
- Reserves the `:` prefix for built-in profiles so user-defined
`[permissions]` profiles cannot collide with future built-ins.
- Allows `default_permissions` to reference a built-in profile without
requiring a `[permissions]` table.
- Makes an otherwise empty config choose a built-in profile by
trust/platform context: trusted or untrusted project roots use
`:workspace` when the platform supports that sandbox, while roots
without a trust decision use `:read-only`.
- Keeps legacy `sandbox_mode` configs on the legacy path, and still
rejects user-defined `[permissions]` profiles that omit
`default_permissions` so we do not silently guess among custom profiles.
- Preserves compatibility behavior for implicit defaults: bare
`network.enabled = true` allows runtime network without starting the
managed proxy, explicit profile proxy policy still starts the proxy, and
implicit workspace/add-dir roots keep legacy metadata carveouts.

## Verification

- `cargo test -p codex-core builtin --lib`
- `cargo test -p codex-core profile_network_proxy_config`
- `cargo test -p codex-core
implicit_builtin_workspace_profile_preserves_add_dir_metadata_carveouts`
- `cargo test -p codex-core
permissions_profiles_network_enabled_allows_runtime_network_without_proxy`
- `cargo test -p codex-core
permissions_profiles_proxy_policy_starts_managed_network_proxy`

## Documentation

Public Codex config docs should mention these built-in names when the
`[permissions]` config format is ready to document as stable.









---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19900).
* #20041
* #20040
* #20037
* #20035
* #20034
* #20033
* #20032
* #20030
* #20028
* #20027
* #20026
* #20024
* #20021
* #20018
* #20016
* #20015
* #20013
* #20011
* #20010
* #20008
* __->__ #19900
This commit is contained in:
Michael Bolin
2026-04-28 11:21:39 -07:00
committed by GitHub
parent 3afb185a4f
commit 9e26613657
12 changed files with 702 additions and 166 deletions

View File

@@ -12,6 +12,7 @@ use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use codex_config::CloudRequirementsLoader;
use codex_core::CodexThread;
use codex_core::ThreadManager;
use codex_core::config::Config;
@@ -47,6 +48,7 @@ use crate::PathBufExt;
use crate::TempDirExt;
use crate::get_remote_test_env;
use crate::load_default_config_for_test;
use crate::load_default_config_for_test_with_cloud_requirements;
use crate::responses::WebSocketTestServer;
use crate::responses::output_value_to_text;
use crate::responses::start_mock_server;
@@ -206,6 +208,7 @@ pub struct TestCodexBuilder {
pre_build_hooks: Vec<Box<PreBuildHook>>,
workspace_setups: Vec<Box<WorkspaceSetup>>,
home: Option<Arc<TempDir>>,
cloud_requirements: Option<CloudRequirementsLoader>,
user_shell_override: Option<Shell>,
exec_server_url: Option<String>,
}
@@ -254,6 +257,11 @@ impl TestCodexBuilder {
self
}
pub fn with_cloud_requirements(mut self, cloud_requirements: CloudRequirementsLoader) -> Self {
self.cloud_requirements = Some(cloud_requirements);
self
}
pub fn with_user_shell(mut self, user_shell: Shell) -> Self {
self.user_shell_override = Some(user_shell);
self
@@ -485,7 +493,11 @@ impl TestCodexBuilder {
for hook in self.pre_build_hooks.drain(..) {
hook(home.path());
}
let mut config = load_default_config_for_test(home).await;
let mut config = if let Some(cloud_requirements) = self.cloud_requirements.take() {
load_default_config_for_test_with_cloud_requirements(home, cloud_requirements).await
} else {
load_default_config_for_test(home).await
};
config.cwd = cwd_override;
config.model_provider = model_provider;
if let Ok(path) = codex_utils_cargo_bin::cargo_bin("codex") {
@@ -923,6 +935,7 @@ pub fn test_codex() -> TestCodexBuilder {
pre_build_hooks: vec![],
workspace_setups: vec![],
home: None,
cloud_requirements: None,
user_shell_override: None,
exec_server_url: None,
}