From 056fc1b65e996d6a488c2947f3adf3f51bccc32a Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Fri, 21 Aug 2026 21:22:32 -0700 Subject: [PATCH] Expose sandbox bypass in Rust API Teach the Rust code generator about the runtime's allowBypass host capability until the pinned CLI schema includes it, then verify camelCase serialization and omission when unset. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/src/generated/api_types.rs | 3 +++ rust/tests/api_types_test.rs | 29 ++++++++++++++++++++++++++++- scripts/codegen/rust.ts | 5 ++++- scripts/codegen/utils.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 0583c09229..284e02f6fe 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -14500,6 +14500,9 @@ pub struct SandboxConfig { /// Whether to auto-add the current working directory to readwritePaths. Default: true. #[serde(skip_serializing_if = "Option::is_none")] pub add_current_working_directory: Option, + /// Host capability flag (not part of the sandbox policy): when true, exposes a per-command escape hatch so the model can request individual commands run outside the sandbox. + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_bypass: Option, /// Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). #[serde(skip_serializing_if = "Option::is_none")] pub allow_dev_tool_access: Option, diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs index 9b86b1367a..f0075eccb5 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -5,7 +5,8 @@ use github_copilot_sdk::rpc::{ Extension, ExtensionList, ExtensionSource, ExtensionStatus, ExtensionsDisableRequest, - ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, TasksStartAgentRequest, + ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, SandboxConfig, + TasksStartAgentRequest, }; use github_copilot_sdk::session_events::{PermissionRequest, PermissionRequestedData}; @@ -104,6 +105,32 @@ fn permission_event_exposes_managed_approval_required() { assert_eq!(request.managed_approval_required, Some(true)); } +#[test] +fn sandbox_allow_bypass_serializes_as_optional_camel_case() { + let enabled = SandboxConfig { + enabled: true, + allow_bypass: Some(true), + ..Default::default() + }; + assert_eq!( + serde_json::to_value(enabled).unwrap(), + serde_json::json!({ + "allowBypass": true, + "enabled": true, + }) + ); + + let omitted = SandboxConfig { + enabled: true, + allow_bypass: None, + ..Default::default() + }; + assert_eq!( + serde_json::to_value(omitted).unwrap(), + serde_json::json!({ "enabled": true }) + ); +} + fn running_extension(id: &str, name: &str) -> Extension { Extension { id: id.to_string(), diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index b5235ce1ea..9512a0bfc2 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -18,6 +18,7 @@ import { promisify } from "util"; import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import { addManagedApprovalRequiredToPermissionRequests, + addSandboxAllowBypass, type ApiSchema, type DefinitionCollections, EXCLUDED_EVENT_TYPES, @@ -2235,7 +2236,9 @@ async function generate(): Promise { ); const apiSchema = propagateInternalVisibility( postProcessSchema( - stripBooleanLiterals(apiRaw) as JSONSchema7, + stripBooleanLiterals( + addSandboxAllowBypass(apiRaw as JSONSchema7), + ) as JSONSchema7, ), ) as unknown as ApiSchema; diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 590213d72b..ab2c55bb90 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -518,6 +518,32 @@ export function addManagedApprovalRequiredToPermissionRequests(schema: T): T { + const cloned = cloneSchemaForCodegen(schema); + const property: JSONSchema7 = { + description: + "Host capability flag (not part of the sandbox policy): when true, exposes a per-command escape hatch so the model can request individual commands run outside the sandbox.", + type: "boolean", + }; + + for (const definitions of [cloned.definitions, cloned.$defs]) { + if (!definitions) continue; + const definition = definitions.SandboxConfig; + if (!definition || typeof definition !== "object") continue; + const objectDefinition = definition as JSONSchema7; + objectDefinition.properties = { + ...objectDefinition.properties, + allowBypass: + objectDefinition.properties?.allowBypass ?? cloneSchemaForCodegen(property), + }; + } + + return cloned; +} + export function getEnumValueDescriptions(schema: JSONSchema7 | null | undefined): EnumValueDescriptions | undefined { if (!schema || typeof schema !== "object") return undefined;