Skip to content

Commit 5411c88

Browse files
committed
fix(sdk): enforce safeguards for injected policy
Treat direct managedSettings injection as a managed session in every language SDK and document the compatible-runtime requirement. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5d4d699-33e2-4a55-9d48-57d2e483dd3d
1 parent 849f07a commit 5411c88

15 files changed

Lines changed: 99 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the fu
1111

1212
Session create and resume accept a new optional `managedSettings` option that injects an enterprise permissions policy at session startup, alongside the existing `enableManagedSettings` self-fetch flag. The current contract is permissions-only: `disableBypassPermissionsMode` (the literal `"disable"`), plus `deny`, `ask`, and `allow` rule lists. The layer composes restrictively with any server- or device-level managed settings (deny/ask are unioned, every present allow list must admit a tool, and `disableBypassPermissionsMode` is deny-wins).
1313

14-
This layer is startup-only and is not persisted with the session, so it must be re-supplied on resume to remain in effect; omitting it on resume clears the previously injected layer. It can be combined with `enableManagedSettings`. Older runtimes that do not recognize the field reject session creation (fail-closed) rather than silently ignoring it, so it requires a Copilot CLI runtime whose schema includes managed settings.
14+
This layer is startup-only and is not persisted with the session, so it must be re-supplied on resume to remain in effect; omitting it on resume clears the previously injected layer. It can be combined with `enableManagedSettings`. Older runtimes may ignore the additive field, so hosts must not rely on injected policy until they ship a Copilot CLI runtime whose schema includes managed settings.
1515

1616
```ts
1717
const session = await client.createSession({

dotnet/src/Client.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ private CopilotSession InitializeSession(
785785
session.RegisterTools(config.Tools ?? []);
786786
session.RegisterPermissionHandler(
787787
config.OnPermissionRequest,
788-
config.EnableManagedSettings is true);
788+
config.EnableManagedSettings is true || config.ManagedSettings is not null);
789789
session.RegisterMcpAuthHandler(config.OnMcpAuthRequest);
790790
session.RegisterCommands(config.Commands);
791791
session.RegisterElicitationHandler(config.OnElicitationRequest);

dotnet/src/Types.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,8 +3033,8 @@ public sealed class ManagedSettingsPermissions
30333033
/// re-supplied on <see cref="CopilotClient.ResumeSessionAsync"/> to remain in
30343034
/// effect; omitting it on resume clears the previously injected layer. It can be
30353035
/// combined with <see cref="SessionConfigBase.EnableManagedSettings"/>. Older
3036-
/// runtimes that do not recognize the <c>managedSettings</c> field reject session
3037-
/// creation (fail-closed).
3036+
/// runtimes may ignore this additive field, so hosts must not rely on injected
3037+
/// policy until they ship a compatible runtime.
30383038
/// </remarks>
30393039
public sealed class ManagedSettings
30403040
{

dotnet/test/Unit/ClientSessionLifetimeTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,6 @@ public async Task CreateSessionAsync_Serializes_ManagedSettings_Permissions()
459459

460460
await using var session = await client.CreateSessionAsync(new SessionConfig
461461
{
462-
EnableManagedSettings = true,
463462
ManagedSettings = new ManagedSettings
464463
{
465464
Permissions = new ManagedSettingsPermissions
@@ -474,7 +473,10 @@ public async Task CreateSessionAsync_Serializes_ManagedSettings_Permissions()
474473
});
475474

476475
var request = Assert.Single(server.Requests, request => request.Method == "session.create");
477-
Assert.True(request.Params.GetProperty("enableManagedSettings").GetBoolean());
476+
Assert.False(request.Params.TryGetProperty("enableManagedSettings", out _));
477+
var managedField = typeof(CopilotSession).GetField("_managedSettingsEnabled", BindingFlags.Instance | BindingFlags.NonPublic)
478+
?? throw new InvalidOperationException("Managed settings field was not found.");
479+
Assert.True((bool)managedField.GetValue(session)!);
478480
var permissions = request.Params.GetProperty("managedSettings").GetProperty("permissions");
479481
Assert.Equal("disable", permissions.GetProperty("disableBypassPermissionsMode").GetString());
480482
Assert.Equal("shell(rm*)", Assert.Single(permissions.GetProperty("deny").EnumerateArray()).GetString());

go/client.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,10 @@ func extractTransformCallbacks(config *SystemMessageConfig) (*SystemMessageConfi
750750
return wireConfig, callbacks
751751
}
752752

753+
func hasManagedSettings(enableManagedSettings *bool, managedSettings *ManagedSettings) bool {
754+
return (enableManagedSettings != nil && *enableManagedSettings) || managedSettings != nil
755+
}
756+
753757
func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Session, error) {
754758
if config == nil {
755759
config = &SessionConfig{}
@@ -913,7 +917,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
913917
sessionID,
914918
c.client,
915919
"",
916-
config.EnableManagedSettings != nil && *config.EnableManagedSettings,
920+
hasManagedSettings(config.EnableManagedSettings, config.ManagedSettings),
917921
)
918922

919923
s.registerTools(config.Tools)
@@ -1242,7 +1246,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
12421246
sessionID,
12431247
c.client,
12441248
"",
1245-
config.EnableManagedSettings != nil && *config.EnableManagedSettings,
1249+
hasManagedSettings(config.EnableManagedSettings, config.ManagedSettings),
12461250
)
12471251

12481252
session.registerTools(config.Tools)

go/client_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3442,6 +3442,15 @@ func TestSessionRequests_ManagedSettings(t *testing.T) {
34423442
"allow": []any{"Read(**)"},
34433443
}
34443444

3445+
t.Run("direct injection enables managed safeguards", func(t *testing.T) {
3446+
if !hasManagedSettings(nil, settings) {
3447+
t.Fatal("expected injected managed settings to enable managed safeguards")
3448+
}
3449+
if hasManagedSettings(nil, nil) {
3450+
t.Fatal("expected an ordinary session to remain unmanaged")
3451+
}
3452+
})
3453+
34453454
t.Run("includes managedSettings on create when set", func(t *testing.T) {
34463455
req := createSessionRequest{EnableManagedSettings: Bool(true), ManagedSettings: settings}
34473456
data, err := json.Marshal(req)

java/src/main/java/com/github/copilot/SessionRequestBuilder.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,8 @@ static void configureSession(CopilotSession session, SessionConfig config) {
372372
if (config.getOnPermissionRequest() != null) {
373373
session.registerPermissionHandler(config.getOnPermissionRequest());
374374
}
375-
session.setManagedSettingsEnabled(config.getEnableManagedSettings().orElse(false));
375+
session.setManagedSettingsEnabled(
376+
config.getEnableManagedSettings().orElse(false) || config.getManagedSettings() != null);
376377
if (config.getOnMcpAuthRequest() != null) {
377378
session.registerMcpAuthHandler(config.getOnMcpAuthRequest());
378379
}
@@ -423,7 +424,8 @@ static void configureSession(CopilotSession session, ResumeSessionConfig config)
423424
if (config.getOnPermissionRequest() != null) {
424425
session.registerPermissionHandler(config.getOnPermissionRequest());
425426
}
426-
session.setManagedSettingsEnabled(config.getEnableManagedSettings().orElse(false));
427+
session.setManagedSettingsEnabled(
428+
config.getEnableManagedSettings().orElse(false) || config.getManagedSettings() != null);
427429
if (config.getOnMcpAuthRequest() != null) {
428430
session.registerMcpAuthHandler(config.getOnMcpAuthRequest());
429431
}

java/src/test/java/com/github/copilot/ManagedSettingsTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,16 @@ void rejectsUnsupportedBypassValue() {
4141
var permissions = new ManagedSettingsPermissions();
4242
assertThrows(IllegalArgumentException.class, () -> permissions.setDisableBypassPermissionsMode("enable"));
4343
}
44+
45+
@Test
46+
void directInjectionEnablesManagedSafeguards() throws Exception {
47+
var session = new CopilotSession("session-1", null);
48+
var settings = new ManagedSettings().setPermissions(new ManagedSettingsPermissions());
49+
50+
SessionRequestBuilder.configureSession(session, new SessionConfig().setManagedSettings(settings));
51+
52+
var field = CopilotSession.class.getDeclaredField("managedSettingsEnabled");
53+
field.setAccessible(true);
54+
assertEquals(true, field.getBoolean(session));
55+
}
4456
}

nodejs/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,9 @@ export class CopilotClient {
14671467
this.onGetTraceContext,
14681468
{
14691469
mcpAuthHandler: config.onMcpAuthRequest,
1470-
managedSettingsEnabled: config.enableManagedSettings,
1470+
managedSettingsEnabled:
1471+
config.enableManagedSettings === true ||
1472+
config.managedSettings !== undefined,
14711473
}
14721474
);
14731475
s.registerTools(config.tools);
@@ -1706,7 +1708,8 @@ export class CopilotClient {
17061708
this.onGetTraceContext,
17071709
{
17081710
mcpAuthHandler: config.onMcpAuthRequest,
1709-
managedSettingsEnabled: config.enableManagedSettings,
1711+
managedSettingsEnabled:
1712+
config.enableManagedSettings === true || config.managedSettings !== undefined,
17101713
}
17111714
);
17121715
session.registerTools(config.tools);

nodejs/src/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,8 +2586,9 @@ export interface SessionConfigBase {
25862586
* when both are supplied the injected, server, and device restrictions all
25872587
* apply.
25882588
*
2589-
* Requires a Copilot runtime whose RPC schema includes `managedSettings`;
2590-
* older runtimes reject the unknown field rather than silently ignoring it.
2589+
* Requires a Copilot runtime whose RPC schema includes `managedSettings`.
2590+
* Older runtimes may ignore this additive field, so hosts must not rely on
2591+
* injected policy until they ship a compatible runtime.
25912592
*/
25922593
managedSettings?: ManagedSettings;
25932594

0 commit comments

Comments
 (0)