feat(policy): add --admin-policy flag for supplemental admin policies (#20360)

This commit is contained in:
Gal Zahavi
2026-03-11 10:35:45 -07:00
committed by GitHub
parent 7e9e196793
commit 6900fe5527
12 changed files with 516 additions and 810 deletions

View File

@@ -76,6 +76,7 @@ export interface CliArgs {
yolo: boolean | undefined;
approvalMode: string | undefined;
policy: string[] | undefined;
adminPolicy: string[] | undefined;
allowedMcpServerNames: string[] | undefined;
allowedTools: string[] | undefined;
acp?: boolean;
@@ -97,6 +98,21 @@ export interface CliArgs {
isCommand: boolean | undefined;
}
/**
* Helper to coerce comma-separated or multiple flag values into a flat array.
*/
const coerceCommaSeparated = (values: string[]): string[] => {
if (values.length === 1 && values[0] === '') {
return [''];
}
return values.flatMap((v) =>
v
.split(',')
.map((s) => s.trim())
.filter(Boolean),
);
};
export async function parseArguments(
settings: MergedSettings,
): Promise<CliArgs> {
@@ -166,14 +182,15 @@ export async function parseArguments(
nargs: 1,
description:
'Additional policy files or directories to load (comma-separated or multiple --policy)',
coerce: (policies: string[]) =>
// Handle comma-separated values
policies.flatMap((p) =>
p
.split(',')
.map((s) => s.trim())
.filter(Boolean),
),
coerce: coerceCommaSeparated,
})
.option('admin-policy', {
type: 'array',
string: true,
nargs: 1,
description:
'Additional admin policy files or directories to load (comma-separated or multiple --admin-policy)',
coerce: coerceCommaSeparated,
})
.option('acp', {
type: 'boolean',
@@ -189,11 +206,7 @@ export async function parseArguments(
string: true,
nargs: 1,
description: 'Allowed MCP server names',
coerce: (mcpServerNames: string[]) =>
// Handle comma-separated values
mcpServerNames.flatMap((mcpServerName) =>
mcpServerName.split(',').map((m) => m.trim()),
),
coerce: coerceCommaSeparated,
})
.option('allowed-tools', {
type: 'array',
@@ -201,9 +214,7 @@ export async function parseArguments(
nargs: 1,
description:
'[DEPRECATED: Use Policy Engine instead See https://geminicli.com/docs/core/policy-engine] Tools that are allowed to run without confirmation',
coerce: (tools: string[]) =>
// Handle comma-separated values
tools.flatMap((tool) => tool.split(',').map((t) => t.trim())),
coerce: coerceCommaSeparated,
})
.option('extensions', {
alias: 'e',
@@ -212,11 +223,7 @@ export async function parseArguments(
nargs: 1,
description:
'A list of extensions to use. If not provided, all extensions are used.',
coerce: (extensions: string[]) =>
// Handle comma-separated values
extensions.flatMap((extension) =>
extension.split(',').map((e) => e.trim()),
),
coerce: coerceCommaSeparated,
})
.option('list-extensions', {
alias: 'l',
@@ -258,9 +265,7 @@ export async function parseArguments(
nargs: 1,
description:
'Additional directories to include in the workspace (comma-separated or multiple --include-directories)',
coerce: (dirs: string[]) =>
// Handle comma-separated values
dirs.flatMap((dir) => dir.split(',').map((d) => d.trim())),
coerce: coerceCommaSeparated,
})
.option('screen-reader', {
type: 'boolean',
@@ -643,7 +648,8 @@ export async function loadCliConfig(
...settings.mcp,
allowed: argv.allowedMcpServerNames ?? settings.mcp?.allowed,
},
policyPaths: argv.policy,
policyPaths: argv.policy ?? settings.policyPaths,
adminPolicyPaths: argv.adminPolicy ?? settings.adminPolicyPaths,
};
const { workspacePoliciesDir, policyUpdateConfirmationRequest } =

View File

@@ -61,6 +61,7 @@ export async function createPolicyEngineConfig(
tools: settings.tools,
mcpServers: settings.mcpServers,
policyPaths: settings.policyPaths,
adminPolicyPaths: settings.adminPolicyPaths,
workspacePoliciesDir,
};

View File

@@ -134,6 +134,18 @@ export interface SettingsSchema {
export type MemoryImportFormat = 'tree' | 'flat';
export type DnsResolutionOrder = 'ipv4first' | 'verbatim';
const pathArraySetting = (label: string, description: string) => ({
type: 'array' as const,
label,
category: 'Advanced' as const,
requiresRestart: true as const,
default: [] as string[],
description,
showInDialog: false as const,
items: { type: 'string' as const },
mergeStrategy: MergeStrategy.UNION,
});
/**
* The canonical schema for all settings.
* The structure of this object defines the structure of the `Settings` type.
@@ -156,17 +168,15 @@ const SETTINGS_SCHEMA = {
},
},
policyPaths: {
type: 'array',
label: 'Policy Paths',
category: 'Advanced',
requiresRestart: true,
default: [] as string[],
description: 'Additional policy files or directories to load.',
showInDialog: false,
items: { type: 'string' },
mergeStrategy: MergeStrategy.UNION,
},
policyPaths: pathArraySetting(
'Policy Paths',
'Additional policy files or directories to load.',
),
adminPolicyPaths: pathArraySetting(
'Admin Policy Paths',
'Additional admin policy files or directories to load.',
),
general: {
type: 'object',
@@ -2677,7 +2687,9 @@ type InferSettings<T extends SettingsSchema> = {
? boolean
: T[K]['default'] extends string
? string
: T[K]['default'];
: T[K]['default'] extends ReadonlyArray<infer U>
? U[]
: T[K]['default'];
};
type InferMergedSettings<T extends SettingsSchema> = {
@@ -2691,7 +2703,9 @@ type InferMergedSettings<T extends SettingsSchema> = {
? boolean
: T[K]['default'] extends string
? string
: T[K]['default'];
: T[K]['default'] extends ReadonlyArray<infer U>
? U[]
: T[K]['default'];
};
export type Settings = InferSettings<SettingsSchemaType>;

View File

@@ -481,6 +481,7 @@ describe('gemini.tsx main function kitty protocol', () => {
yolo: undefined,
approvalMode: undefined,
policy: undefined,
adminPolicy: undefined,
allowedMcpServerNames: undefined,
allowedTools: undefined,
experimentalAcp: undefined,

View File

@@ -29,3 +29,9 @@ exports[`ConfigInitDisplay > updates message on McpClientUpdate event 1`] = `
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
"
`;
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 2`] = `
"
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
"
`;