Add startup tool allowlists for threads (#45711)

## What changed

- Add `AllowedTools` to the extension API, captured once at thread startup to restrict which tools can be advertised or executed. An empty list disables all tools; an absent value preserves ordinary tool setup. Callers must supply the list again when resuming a thread.
- Filter tool registration and hosted tool specifications before Code Mode and discovery. Match names with their namespaces and require generated tools such as `exec` and `wait` to be explicitly allowed.
- Move Guardian reviewer tool selection into the allowlist while retaining feature and sandbox restrictions, with a fallback for older reviewer sessions.

## Testing

Add coverage for selected, empty, and absent allowlists across tool sources and Code Mode. Add managed-thread tests verifying advertised tools, rejection of excluded calls, and that replacing extension state after startup cannot change the captured allowlist.

GitOrigin-RevId: 33c1722c4241f368963d8385fa0415112e16a72d
This commit is contained in:
jif
2026-09-15 14:45:35 +00:00
committed by copyberry
parent 7f01a84eff
commit 2fdcdeaf0e
12 changed files with 328 additions and 93 deletions
+9
View File
@@ -69,6 +69,7 @@ pub(crate) struct Session {
pub(super) features: ManagedFeatures,
pub(crate) guardian_context_mode: GuardianContextMode,
pub(super) isolation: codex_extension_api::SessionIsolation,
pub(crate) allowed_tools: Option<Arc<codex_extension_api::AllowedTools>>,
pub(crate) windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode,
pub(super) multi_agent_version: OnceLock<MultiAgentVersion>,
@@ -946,6 +947,13 @@ impl Session {
.get::<codex_extension_api::SessionIsolation>()
.map(|policy| *policy)
.unwrap_or_default();
let allowed_tools = thread_extension_init
.get::<codex_extension_api::AllowedTools>()
.or_else(|| {
// Older reviewer rollouts predate the explicit startup setting.
crate::guardian::is_basic_session_source(&session_configuration.session_source)
.then(|| Arc::new(codex_guardian_reviewer::reviewer_allowed_tools()))
});
let mcp_thread_init = thread_extension_init.clone();
let thread_extension_data = codex_extension_api::ExtensionData::new_with_init(
thread_id.to_string(),
@@ -1695,6 +1703,7 @@ impl Session {
features: config.features.clone(),
guardian_context_mode,
isolation,
allowed_tools,
windows_sandbox_proxy_settings_mode,
multi_agent_version,
mcp_refresh: McpRefresh::new(),
+2
View File
@@ -6110,6 +6110,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
features: config.features.clone(),
guardian_context_mode: GuardianContextMode::from_features(&config.features),
isolation: codex_extension_api::SessionIsolation::Inherit,
allowed_tools: None,
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),
@@ -8291,6 +8292,7 @@ where
features: config.features.clone(),
guardian_context_mode: GuardianContextMode::from_features(&config.features),
isolation: codex_extension_api::SessionIsolation::Inherit,
allowed_tools: None,
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),