feat(policy): implement project policy integrity verification

Adds a security mechanism to detect and prompt for confirmation when project-level policies are added or modified. This prevents unauthorized policy changes from being applied silently.

- PolicyIntegrityManager calculates and persists policy directory hashes.
- Config integrates integrity checks during startup.
- PolicyUpdateDialog prompts users in interactive mode.
- --accept-changed-policies flag supports non-interactive workflows.
- toml-loader refactored to expose file reading logic.
This commit is contained in:
Abhijit Balaji
2026-02-13 15:24:54 -08:00
parent 53511d6ed4
commit c73e47bbbe
14 changed files with 776 additions and 25 deletions
+57 -1
View File
@@ -44,6 +44,9 @@ import {
type HookDefinition,
type HookEventName,
type OutputFormat,
PolicyIntegrityManager,
IntegrityStatus,
type PolicyUpdateConfirmationRequest,
} from '@google/gemini-cli-core';
import {
type Settings,
@@ -95,6 +98,7 @@ export interface CliArgs {
rawOutput: boolean | undefined;
acceptRawOutputRisk: boolean | undefined;
isCommand: boolean | undefined;
acceptChangedPolicies: boolean | undefined;
}
export async function parseArguments(
@@ -286,6 +290,11 @@ export async function parseArguments(
.option('accept-raw-output-risk', {
type: 'boolean',
description: 'Suppress the security warning when using --raw-output.',
})
.option('accept-changed-policies', {
type: 'boolean',
description:
'Automatically accept changed project policies (use with caution).',
}),
)
// Register MCP subcommands
@@ -694,8 +703,54 @@ export async function loadCliConfig(
};
let projectPoliciesDir: string | undefined;
let policyUpdateConfirmationRequest:
| PolicyUpdateConfirmationRequest
| undefined;
if (trustedFolder) {
projectPoliciesDir = new Storage(cwd).getProjectPoliciesDir();
const potentialProjectPoliciesDir = new Storage(
cwd,
).getProjectPoliciesDir();
const integrityManager = new PolicyIntegrityManager();
const integrityResult = await integrityManager.checkIntegrity(
'project',
cwd,
potentialProjectPoliciesDir,
);
if (integrityResult.status === IntegrityStatus.MATCH) {
projectPoliciesDir = potentialProjectPoliciesDir;
} else if (
integrityResult.status === IntegrityStatus.NEW &&
integrityResult.fileCount === 0
) {
// No project policies found
projectPoliciesDir = undefined;
} else {
// Policies changed or are new
if (argv.acceptChangedPolicies) {
debugLogger.warn(
'WARNING: Project policies changed or are new. Auto-accepting due to --accept-changed-policies flag.',
);
await integrityManager.acceptIntegrity(
'project',
cwd,
integrityResult.hash,
);
projectPoliciesDir = potentialProjectPoliciesDir;
} else if (interactive) {
policyUpdateConfirmationRequest = {
scope: 'project',
identifier: cwd,
policyDir: potentialProjectPoliciesDir,
newHash: integrityResult.hash,
};
} else {
debugLogger.warn(
'WARNING: Project policies changed or are new. Loading default policies only. Use --accept-changed-policies to accept.',
);
}
}
}
const policyEngineConfig = await createPolicyEngineConfig(
@@ -765,6 +820,7 @@ export async function loadCliConfig(
coreTools: settings.tools?.core || undefined,
allowedTools: allowedTools.length > 0 ? allowedTools : undefined,
policyEngineConfig,
policyUpdateConfirmationRequest,
excludeTools,
toolDiscoveryCommand: settings.tools?.discoveryCommand,
toolCallCommand: settings.tools?.callCommand,