mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
permissions: enforce managed permission profile allowlists (#24852)
## Why Permission profile allowlists are an enterprise security boundary, but they also need to compose across the managed requirements layers added in #24620. A map representation lets each requirements layer add, allow, or revoke individual profiles without replacing an entire array. ## Managed Contract Administrators configure the mergeable allow map with `allowed_permission_profiles`. A recommended enterprise configuration explicitly lists every built-in and custom profile users should be able to select: ```toml default_permissions = "review_only" [allowed_permission_profiles] ":read-only" = true ":workspace" = true review_only = true # ":danger-full-access" is intentionally omitted, so it is denied. [permissions.review_only] extends = ":read-only" ``` - Profiles whose effective merged value is `true` are allowed. - Missing profiles and profiles set to `false` are denied. - This is a closed allowlist: built-in profiles and profiles introduced in future versions are denied unless explicitly allowed. - Explicitly list each built-in profile the enterprise wants to make available. Omit built-ins such as `:danger-full-access` when they should remain unavailable. - Set `default_permissions` explicitly to the allowed profile users should receive when they have no local selection. - Higher-precedence layers override only the profile keys they define. - `false` is only needed when a higher-precedence layer must revoke a `true` inherited from a lower layer. - Explicit keys must refer to known built-in or managed profiles. A custom or narrowed allowlist requires an allowed `default_permissions`. For compatibility, if both `:workspace` and `:read-only` are explicitly allowed, an omitted default resolves to `:workspace`; customer configurations should still set the intended default explicitly. When `allowed_permission_profiles` is absent, existing implicit permission and legacy `sandbox_mode` behavior is unchanged. ## What Changed - Add `allowed_permission_profiles` as a `BTreeMap<String, bool>` that merges per profile across requirements layers. - Enforce managed defaults, strict denial of omitted profiles, and the explicitly allowed standard-pair fallback. - Expose `allowedPermissionProfiles` through `configRequirements/read` and regenerate its schemas. - Add regression coverage for map composition and revocation, managed defaults, strict denial of omitted built-ins, and API output. ## Verification - Focused `codex-config` coverage for layered map composition and revocation - Focused `codex-core` coverage for managed defaults, invalid defaults, strict denial of omitted built-ins, and the standard built-in pair - Focused `codex-app-server` coverage for requirements API output - Scoped Clippy for `codex-config`, `codex-core`, `codex-app-server-protocol`, and `codex-app-server` ## Documentation The managed `requirements.toml` documentation should introduce `allowed_permission_profiles` as a closed permission-profile allowlist before this setting is published on developers.openai.com. --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
954e2878bb
commit
2f108f9fd9
@@ -350,7 +350,8 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
|
||||
.collect()
|
||||
})
|
||||
}),
|
||||
allowed_permissions: requirements.allowed_permissions,
|
||||
allowed_permission_profiles: requirements.allowed_permission_profiles,
|
||||
default_permissions: requirements.default_permissions,
|
||||
allowed_web_search_modes: requirements.allowed_web_search_modes.map(|modes| {
|
||||
let mut normalized = modes
|
||||
.into_iter()
|
||||
@@ -569,29 +570,43 @@ mod tests {
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_config::WindowsRequirementsToml;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn requirements_api_includes_allow_managed_hooks_only() {
|
||||
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
|
||||
allowed_permissions: Some(vec![
|
||||
"managed-standard".to_string(),
|
||||
"managed-build".to_string(),
|
||||
]),
|
||||
allow_managed_hooks_only: Some(true),
|
||||
..ConfigRequirementsToml::default()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
mapped.allowed_permissions,
|
||||
Some(vec![
|
||||
"managed-standard".to_string(),
|
||||
"managed-build".to_string(),
|
||||
])
|
||||
);
|
||||
assert_eq!(mapped.allow_managed_hooks_only, Some(true));
|
||||
assert_eq!(mapped.hooks, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requirements_api_includes_permission_default_and_allowlist() {
|
||||
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
|
||||
allowed_permission_profiles: Some(BTreeMap::from([
|
||||
("managed-build".to_string(), false),
|
||||
("managed-standard".to_string(), true),
|
||||
])),
|
||||
default_permissions: Some("managed-standard".to_string()),
|
||||
..ConfigRequirementsToml::default()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
mapped.allowed_permission_profiles,
|
||||
Some(BTreeMap::from([
|
||||
("managed-build".to_string(), false),
|
||||
("managed-standard".to_string(), true),
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
mapped.default_permissions,
|
||||
Some("managed-standard".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requirements_api_includes_allow_appshots() {
|
||||
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
|
||||
|
||||
Reference in New Issue
Block a user