Skip to content

Commit 6898071

Browse files
joshspicerCopilot
andcommitted
agentHost: graduate the legacy-settings managed-permissions bridge
The bridge from legacy VS Code settings to the Copilot SDK's per-session managedSettings.permissions shipped behind the experimental opt-in chat.agentHost.copilot.mapLegacySettingsToManagedSettings because the runtime treated any managed rule from any source as activating the global managed policy, so one narrow restriction forced unmatched shell/read/write/URL/factory requests to prompt. github/copilot-agent-runtime#16249 makes client/session-injected managed permissions non-activating, which removes that broadening. The bundled runtime VS Code spawns contains the fix, so the bridge becomes the default enforcement path. The setting is deprecated and its value is now ignored rather than honored as an opt-out: the mappings only ever contribute administrator-configured restrictions, so honoring an explicit false would let a user switch off an enterprise policy. Also drops the now-inert opt-in fixture from the per-tool auto-approval tests added in #333264, so no test implies the gate still influences the result. Refs #332011 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1dbbce1 commit 6898071

3 files changed

Lines changed: 33 additions & 45 deletions

File tree

src/vs/platform/agentHost/common/agentHostManagedSettings.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export interface IAgentHostManagedSettingsPermissions {
6565
ask?: string[];
6666
}
6767

68+
/** Deprecated and ignored; honoring `false` would let a user lift an admin restriction. */
6869
export const AgentHostMapLegacySettingsToManagedSettingsSettingId = 'chat.agentHost.copilot.mapLegacySettingsToManagedSettings';
6970

7071
/**
@@ -276,7 +277,6 @@ const managedPermissionsSettings: readonly IManagedPermissionsSettingMapping[] =
276277
];
277278

278279
export const managedPermissionsConfigurationIds = [
279-
AgentHostMapLegacySettingsToManagedSettingsSettingId,
280280
...managedPermissionsSettings.flatMap(mapping => [mapping.settingId, ...mapping.additionalSettingIds ?? []]),
281281
];
282282

@@ -301,17 +301,10 @@ function isStringArrayOrUndefined(value: unknown): boolean {
301301
* Combines every mapping's contribution into the single document sent to the
302302
* host, deduplicating rules that more than one setting produced.
303303
*
304-
* Contributing any rule at all makes the runtime's managed policy "active",
305-
* which causes unmatched shell, read, write, URL and factory requests to require
306-
* approval. That is broader than any individual mapping intends, but it errs
307-
* toward prompting, and the alternative — an `allow` list — resolves to
308-
* auto-approval. See the module comment.
304+
* Client-injected managed permissions are non-activating, so these rules bind
305+
* without forcing unmatched requests to prompt. See the module comment.
309306
*/
310307
export function resolveManagedSettingsPermissions(configurationService: IConfigurationService): IAgentHostManagedSettingsPermissions {
311-
if (getGlobalConfigurationValue<boolean>(configurationService, AgentHostMapLegacySettingsToManagedSettingsSettingId) !== true) {
312-
return {};
313-
}
314-
315308
const deny = new Set<string>();
316309
const ask = new Set<string>();
317310
let disableBypassPermissionsMode: 'disable' | undefined;

src/vs/platform/agentHost/test/common/agentHostManagedSettings.test.ts

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ suite('AgentHostManagedSettings', () => {
2222

2323
test('combines restrictive contributions from explicitly configured global values', () => {
2424
const configurationService = createConfigurationService({
25-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
2625
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, policyValue: false },
2726
[TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID]: { defaultValue: true, userValue: false },
2827
});
@@ -35,7 +34,6 @@ suite('AgentHostManagedSettings', () => {
3534

3635
test('respects global precedence and ignores defaults and workspace values', () => {
3736
const configurationService = createConfigurationService({
38-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
3937
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, userValue: false, policyValue: true },
4038
[TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID]: { defaultValue: false, workspaceValue: false, workspaceFolderValue: false },
4139
});
@@ -45,11 +43,9 @@ suite('AgentHostManagedSettings', () => {
4543

4644
test('does not promote user or application preferences to managed bypass restrictions', () => {
4745
const userConfigurationService = createConfigurationService({
48-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
4946
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, userValue: false },
5047
});
5148
const applicationConfigurationService = createConfigurationService({
52-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
5349
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, applicationValue: false },
5450
});
5551

@@ -59,27 +55,47 @@ suite('AgentHostManagedSettings', () => {
5955
], [{}, {}]);
6056
});
6157

62-
test('does not map legacy settings while the compatibility bridge is disabled', () => {
58+
test('maps legacy settings without any opt-in present', () => {
6359
const configurationService = createConfigurationService({
64-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false },
6560
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, userValue: false },
6661
[TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID]: { defaultValue: true, userValue: false },
6762
});
6863

69-
assert.deepStrictEqual(resolveManagedSettingsPermissions(configurationService), {});
64+
assert.deepStrictEqual(resolveManagedSettingsPermissions(configurationService), {
65+
ask: ['Shell'],
66+
});
7067
});
7168

72-
test('returns an empty contribution after explicit restrictions are removed', () => {
73-
const configurationService = createConfigurationService({
74-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, applicationValue: true },
75-
});
69+
test('ignores the deprecated opt-in, so it cannot switch off a mapped restriction', () => {
70+
const restricted = {
71+
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
72+
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
73+
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['evil.example'] },
74+
};
75+
76+
assert.deepStrictEqual([
77+
resolveManagedSettingsPermissions(createConfigurationService({
78+
...restricted,
79+
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: true, userValue: false },
80+
})),
81+
resolveManagedSettingsPermissions(createConfigurationService({
82+
...restricted,
83+
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: true, userValue: true },
84+
})),
85+
], [
86+
{ deny: ['Domain(evil.example)'] },
87+
{ deny: ['Domain(evil.example)'] },
88+
]);
89+
});
90+
91+
test('returns an empty contribution when no legacy setting is restricted', () => {
92+
const configurationService = createConfigurationService({});
7693

7794
assert.deepStrictEqual(resolveManagedSettingsPermissions(configurationService), {});
7895
});
7996

8097
test('deduplicates a rule that more than one entry produces', () => {
8198
const configurationService = createConfigurationService({
82-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
8399
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
84100
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
85101
// Three spellings of the same host, which all normalize to one rule.
@@ -96,7 +112,6 @@ suite('AgentHostManagedSettings', () => {
96112

97113
test('reduces denied domains to the host the network filter matches on', () => {
98114
const configurationService = createConfigurationService({
99-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
100115
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
101116
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
102117
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: {
@@ -112,7 +127,6 @@ suite('AgentHostManagedSettings', () => {
112127

113128
test('denies configured domains while the network filter is on', () => {
114129
const configurationService = createConfigurationService({
115-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
116130
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
117131
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['evil.com', '*.tracker.example'] },
118132
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [], policyValue: ['github.com'] },
@@ -125,7 +139,6 @@ suite('AgentHostManagedSettings', () => {
125139

126140
test('denies every domain when the filter is on and neither list is configured', () => {
127141
const configurationService = createConfigurationService({
128-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
129142
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
130143
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [] },
131144
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
@@ -136,7 +149,6 @@ suite('AgentHostManagedSettings', () => {
136149

137150
test('contributes nothing from domain lists while the network filter is off', () => {
138151
const configurationService = createConfigurationService({
139-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
140152
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false },
141153
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['evil.com'] },
142154
});
@@ -146,7 +158,6 @@ suite('AgentHostManagedSettings', () => {
146158

147159
test('skips denied domain patterns the SDK cannot express', () => {
148160
const configurationService = createConfigurationService({
149-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
150161
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
151162
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['$(evil)', 'ok.example'] },
152163
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
@@ -159,7 +170,6 @@ suite('AgentHostManagedSettings', () => {
159170

160171
test('maps a bare wildcard denial onto the all-domains family rule', () => {
161172
const configurationService = createConfigurationService({
162-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
163173
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
164174
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['*'] },
165175
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
@@ -170,7 +180,6 @@ suite('AgentHostManagedSettings', () => {
170180

171181
test('requires approval for explicitly denied terminal commands', () => {
172182
const configurationService = createConfigurationService({
173-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
174183
[TERMINAL_AUTO_APPROVE_SETTING_ID]: {
175184
defaultValue: {},
176185
policyValue: { rm: false, 'git push': false, npm: true },
@@ -184,7 +193,6 @@ suite('AgentHostManagedSettings', () => {
184193

185194
test('skips terminal denials the SDK shell grammar cannot express', () => {
186195
const configurationService = createConfigurationService({
187-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
188196
[TERMINAL_AUTO_APPROVE_SETTING_ID]: {
189197
defaultValue: {},
190198
policyValue: {
@@ -202,7 +210,6 @@ suite('AgentHostManagedSettings', () => {
202210

203211
test('keeps an absolute command path that VS Code treats as a literal', () => {
204212
const configurationService = createConfigurationService({
205-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
206213
// Starts and ends with `/` but the trailing segment is not a flag list,
207214
// so the auto-approver reads it as a path rather than a regular expression.
208215
[TERMINAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: {}, policyValue: { '/usr/bin/rm': false } },
@@ -215,7 +222,6 @@ suite('AgentHostManagedSettings', () => {
215222

216223
test('skips a wildcard command key rather than broadening it', () => {
217224
const configurationService = createConfigurationService({
218-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
219225
// `*` is a literal in VS Code but a command-boundary wildcard in the SDK,
220226
// so bridging this would require approval for every git command.
221227
[TERMINAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: {}, policyValue: { 'git *': false, 'rm': false } },
@@ -228,7 +234,6 @@ suite('AgentHostManagedSettings', () => {
228234

229235
test('treats a long-form sub-command denial like a bare false', () => {
230236
const configurationService = createConfigurationService({
231-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
232237
[TERMINAL_AUTO_APPROVE_SETTING_ID]: {
233238
defaultValue: {},
234239
policyValue: { rm: { approve: false }, ls: { approve: true } },
@@ -242,7 +247,6 @@ suite('AgentHostManagedSettings', () => {
242247

243248
test('locks the bypass mode when a tool is marked ineligible for auto-approval', () => {
244249
const configurationService = createConfigurationService({
245-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
246250
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: false } },
247251
});
248252

@@ -253,7 +257,6 @@ suite('AgentHostManagedSettings', () => {
253257

254258
test('contributes nothing when every tool is left eligible for auto-approval', () => {
255259
const configurationService = createConfigurationService({
256-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
257260
// Only `true` entries: the policy re-affirms the default and removes nothing.
258261
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: true, runTask: true } },
259262
});
@@ -263,7 +266,6 @@ suite('AgentHostManagedSettings', () => {
263266

264267
test('contributes nothing from the default empty per-tool auto-approval map', () => {
265268
const configurationService = createConfigurationService({
266-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
267269
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: {} },
268270
});
269271

@@ -272,15 +274,12 @@ suite('AgentHostManagedSettings', () => {
272274

273275
test('does not promote user, application, or workspace per-tool auto-approval values', () => {
274276
const userConfigurationService = createConfigurationService({
275-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
276277
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, userValue: { fetch: false } },
277278
});
278279
const applicationConfigurationService = createConfigurationService({
279-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
280280
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, applicationValue: { fetch: false } },
281281
});
282282
const workspaceConfigurationService = createConfigurationService({
283-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
284283
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, workspaceValue: { fetch: false } },
285284
});
286285

@@ -293,7 +292,6 @@ suite('AgentHostManagedSettings', () => {
293292

294293
test('clears the bypass lock after the per-tool auto-approval policy is removed', () => {
295294
const configurationService = createConfigurationService({
296-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
297295
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {} },
298296
});
299297

@@ -302,7 +300,6 @@ suite('AgentHostManagedSettings', () => {
302300

303301
test('ignores a malformed per-tool auto-approval value without throwing', () => {
304302
const configurationService = createConfigurationService({
305-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
306303
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: ['fetch'] },
307304
});
308305

@@ -311,7 +308,6 @@ suite('AgentHostManagedSettings', () => {
311308

312309
test('fails closed and locks the bypass mode for a non-boolean per-tool auto-approval entry', () => {
313310
const configurationService = createConfigurationService({
314-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
315311
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: 'no' } },
316312
});
317313

@@ -322,7 +318,6 @@ suite('AgentHostManagedSettings', () => {
322318

323319
test('does not duplicate the bypass lock across the global and per-tool auto-approval policies', () => {
324320
const configurationService = createConfigurationService({
325-
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
326321
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, policyValue: false },
327322
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: false } },
328323
});

src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,9 +1614,9 @@ configurationRegistry.registerConfiguration({
16141614
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: {
16151615
type: 'boolean',
16161616
markdownDescription: nls.localize('chat.agentHost.copilot.mapLegacySettingsToManagedSettings', "When enabled, maps supported legacy VS Code settings to equivalent Copilot SDK managed settings for local Agent Host sessions. Only restrictions are mapped, and only from globally-scoped values — workspace and folder values are ignored. Applies to local sessions using the Copilot agent; remote hosts and other agents are unaffected. This compatibility bridge is temporary and is not used for new settings."),
1617-
default: false,
1617+
default: true,
16181618
scope: ConfigurationScope.APPLICATION_MACHINE,
1619-
tags: ['experimental', 'advanced'],
1619+
tags: ['advanced'],
16201620
},
16211621
[AgentHostOpus48PromptEnabledSettingId]: {
16221622
type: 'boolean',

0 commit comments

Comments
 (0)