Allow Guardian scoring for required computer-use models (#40967)

## Why

Models that require automatic review previously skipped Guardian v2 risk scoring
entirely, even when Guardian was limited to computer-use tools. That scope cannot
use a low-risk result to approve unrelated tools, so the blanket skip is
unnecessary.

## What changed

- Retain Guardian v2 risk scoring for required-review models in
  `ComputerUseOnly` scope while continuing to skip it in standard scope.
- Preserve strict automatic approval review for Node REPL-backed tools, including
  when classification fails.

## Testing

Added coverage for low-risk, high-risk, and invalid classifications across the
Node REPL and computer-use REPL paths, plus an unrelated MCP tool.

GitOrigin-RevId: ee38fabf82e196df0ae13b4216b5892be9b799cf
This commit is contained in:
jif authored and copyberry committed 2026-08-26 20:07:39 +00:00
1 parent ac644ed112
commit daa3eaf10f
4 files changed
+133 -25

No files matched your search

@@ -525,12 +525,15 @@ impl GuardianV2Extension {
}
};
let parent_model = input.thread_store.get::<ModelInfo>();
if parent_model.as_ref().is_some_and(|model| {
config
.config_layer_stack
.requirements()
.auto_review_required_for_model(&model.slug)
}) {
// Computer-use-only scores cannot approve other tools for required models.
if guardian_config.review_scope != GuardianV2ReviewScope::ComputerUseOnly
&& parent_model.as_ref().is_some_and(|model| {
config
.config_layer_stack
.requirements()
.auto_review_required_for_model(&model.slug)
})
{
input.thread_store.remove::<SecurityRiskScore>();
return;
}
@@ -1901,7 +1901,7 @@ async fn contributor_persists_nested_code_mode_action_with_score() -> Result<()>
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn contributor_skips_models_requiring_managed_guardian_review() -> Result<()> {
async fn contributor_skips_required_models_in_standard_scope() -> Result<()> {
skip_if_no_network!(Ok(()));
let thread_server = responses::start_mock_server().await;
@@ -1961,6 +1961,16 @@ async fn contributor_skips_models_requiring_managed_guardian_review() -> Result<
})
.await;
let mut guardian_config = thread_store
.get::<crate::async_scorer::config::GuardianV2Config>()
.expect("Guardian v2 should have initialized")
.as_ref()
.clone();
guardian_config.review_scope = GuardianV2ReviewScope::Standard {
sandboxed_exec_commands: false,
};
thread_store.insert(guardian_config);
let mut model_info = test
.thread_manager
.get_models_manager()