Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3834,6 +3834,26 @@ func TestSessionRequests_ManagedSettings(t *testing.T) {
}
})

t.Run("accepts future bypass-permissions modes", func(t *testing.T) {
req := createSessionRequest{ManagedSettings: &ManagedSettings{
Permissions: &ManagedSettingsPermissions{
DisableBypassPermissionsMode: DisableBypassPermissionsMode("future-fail-closed-mode"),
},
}}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
perms := m["managedSettings"].(map[string]any)["permissions"].(map[string]any)
if perms["disableBypassPermissionsMode"] != "future-fail-closed-mode" {
t.Errorf("Expected future mode preserved, got %v", perms["disableBypassPermissionsMode"])
}
})

t.Run("omits managedSettings when nil", func(t *testing.T) {
req := createSessionRequest{}
data, _ := json.Marshal(req)
Expand Down
2 changes: 2 additions & 0 deletions go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ func TestRPCTasksAndHandlersE2E(t *testing.T) {
})

t.Run("should report implemented error for invalid task agent model", func(t *testing.T) {
ctx.ConfigureForTest(t)

session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
Expand Down
14 changes: 9 additions & 5 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1569,21 +1569,25 @@ type ManagedSettings struct {
}

// DisableBypassPermissionsMode is the managed bypass-permissions policy.
type DisableBypassPermissionsMode = rpc.DisableBypassPermissionsMode
//
// The runtime may introduce additional fail-closed modes. Values are serialized
// as strings so callers can use newer modes without waiting for an SDK release.
type DisableBypassPermissionsMode string

const (
// DisableBypassPermissionsModeDisable turns off bypass-permissions mode.
DisableBypassPermissionsModeDisable = rpc.DisableBypassPermissionsModeDisable
DisableBypassPermissionsModeDisable DisableBypassPermissionsMode = "disable"
// DisableBypassPermissionsModeAllowAutoOnly permits only automatic bypass.
DisableBypassPermissionsModeAllowAutoOnly DisableBypassPermissionsMode = "allow-auto-only"
)

// ManagedSettingsPermissions is the permissions-only managed policy injected
// via ManagedSettings. Rule strings use the same vocabulary the runtime
// accepts for fetched managed policy (e.g. "Read(**)", "Shell(git push *)");
// malformed rules are rejected by the runtime at session creation.
type ManagedSettingsPermissions struct {
// DisableBypassPermissionsMode, when set to "disable", turns off
// bypass-permissions ("yolo") mode for the session. Deny-wins: no other
// layer can re-enable it.
// DisableBypassPermissionsMode controls bypass-permissions ("yolo") mode for
// the session. Deny-wins: no other layer can grant broader bypass permissions.
DisableBypassPermissionsMode DisableBypassPermissionsMode `json:"disableBypassPermissionsMode,omitempty"`
// Deny lists operations that must always be denied. Unioned across layers.
Deny []string `json:"deny,omitzero"`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
package com.github.copilot.rpc;

/**
* Known values for the managed bypass-permissions policy.
*
* <p>
* The wire contract is an open string so callers can pass newer fail-closed
* modes directly to
* {@link ManagedSettingsPermissions#setDisableBypassPermissionsMode(String)}.
*/
public final class DisableBypassPermissionsModes {
/** Turns off bypass-permissions mode. */
public static final String DISABLE = "disable";

/** Permits bypass only for automatic operations. */
public static final String ALLOW_AUTO_ONLY = "allow-auto-only";

private DisableBypassPermissionsModes() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public final class ManagedSettingsPermissions {
@JsonProperty("disableBypassPermissionsMode")
private DisableBypassPermissionsMode disableBypassPermissionsMode;
private String disableBypassPermissionsMode;

@JsonProperty("deny")
private List<String> deny;
Expand All @@ -27,19 +27,30 @@ public final class ManagedSettingsPermissions {
private List<String> allow;

/** @return the bypass-permissions policy, or {@code null} when unset */
public DisableBypassPermissionsMode getDisableBypassPermissionsMode() {
public String getDisableBypassPermissionsMode() {
return disableBypassPermissionsMode;
}

/**
* Disables bypass/allow-all permission modes.
* @param value
* bypass-permissions policy
* @return this policy
*/
public ManagedSettingsPermissions setDisableBypassPermissionsMode(String value) {
this.disableBypassPermissionsMode = value;
return this;
}

/**
* Sets the bypass-permissions policy from the generated enum retained for
* source compatibility.
*
* @param value
* bypass-permissions policy
* @return this policy
*/
public ManagedSettingsPermissions setDisableBypassPermissionsMode(DisableBypassPermissionsMode value) {
this.disableBypassPermissionsMode = value;
this.disableBypassPermissionsMode = value == null ? null : value.getValue();
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.copilot.generated.rpc.DisableBypassPermissionsMode;
import com.github.copilot.rpc.DisableBypassPermissionsModes;
import com.github.copilot.rpc.ManagedSettings;
import com.github.copilot.rpc.ManagedSettingsPermissions;
import com.github.copilot.rpc.PermissionRequestResult;
Expand Down Expand Up @@ -41,6 +42,25 @@ void forwardsManagedSettingsOnCreateAndResume() throws Exception {
assertTrue(json.contains("\"disableBypassPermissionsMode\":\"disable\""));
}

@Test
void serializesKnownBypassPermissionsModes() throws Exception {
var permissions = new ManagedSettingsPermissions()
.setDisableBypassPermissionsMode(DisableBypassPermissionsModes.ALLOW_AUTO_ONLY);
var json = new ObjectMapper().writeValueAsString(permissions);

assertEquals(DisableBypassPermissionsModes.ALLOW_AUTO_ONLY, permissions.getDisableBypassPermissionsMode());
assertTrue(json.contains("\"disableBypassPermissionsMode\":\"allow-auto-only\""));
}

@Test
void acceptsFutureBypassPermissionsModes() throws Exception {
var permissions = new ManagedSettingsPermissions().setDisableBypassPermissionsMode("future-fail-closed-mode");
var json = new ObjectMapper().writeValueAsString(permissions);

assertEquals("future-fail-closed-mode", permissions.getDisableBypassPermissionsMode());
assertTrue(json.contains("\"disableBypassPermissionsMode\":\"future-fail-closed-mode\""));
}

@Test
void preservesExplicitEmptyPermissionArrays() throws Exception {
// Security-critical: a present empty allow list admits nothing, while an
Expand Down
16 changes: 8 additions & 8 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2155,15 +2155,15 @@ impl Client {
/// auto-generated token for SDK-spawned TCP servers) as the `token`
/// param. Server-side, the token is required when the server was
/// started with `COPILOT_CONNECTION_TOKEN`.
#[expect(
clippy::field_reassign_with_default,
reason = "generated requests can gain optional fields without requiring SDK changes"
)]
async fn connect_handshake(&self) -> Result<Option<u32>> {
let params = crate::generated::api_types::ConnectRequest {
token: self.inner.effective_connection_token.clone(),
enable_git_hub_telemetry_forwarding: self
.inner
.on_github_telemetry
.is_some()
.then_some(true),
};
let mut params = crate::generated::api_types::ConnectRequest::default();
params.token = self.inner.effective_connection_token.clone();
params.enable_git_hub_telemetry_forwarding =
self.inner.on_github_telemetry.is_some().then_some(true);
let value = self
.call(
crate::generated::api_types::rpc_methods::CONNECT,
Expand Down
Loading