Security: Project-level hook warnings (#15470)

This commit is contained in:
Sehoon Shon
2025-12-23 16:10:46 -05:00
committed by GitHub
parent 873d10df42
commit e6344a8c24
13 changed files with 505 additions and 23 deletions
+13 -2
View File
@@ -8,7 +8,6 @@ import yargs from 'yargs/yargs';
import { hideBin } from 'yargs/helpers';
import process from 'node:process';
import { mcpCommand } from '../commands/mcp.js';
import type { OutputFormat } from '@google/gemini-cli-core';
import { extensionsCommand } from '../commands/extensions.js';
import { hooksCommand } from '../commands/hooks.js';
import {
@@ -33,6 +32,9 @@ import {
WEB_FETCH_TOOL_NAME,
getVersion,
PREVIEW_GEMINI_MODEL_AUTO,
type HookDefinition,
type HookEventName,
type OutputFormat,
} from '@google/gemini-cli-core';
import type { Settings } from './settings.js';
import { saveModelChange, loadSettings } from './settings.js';
@@ -380,12 +382,20 @@ export function isDebugMode(argv: CliArgs): boolean {
);
}
export interface LoadCliConfigOptions {
cwd?: string;
projectHooks?: { [K in HookEventName]?: HookDefinition[] } & {
disabled?: string[];
};
}
export async function loadCliConfig(
settings: Settings,
sessionId: string,
argv: CliArgs,
cwd: string = process.cwd(),
options: LoadCliConfigOptions = {},
): Promise<Config> {
const { cwd = process.cwd(), projectHooks } = options;
const debugMode = isDebugMode(argv);
const loadedSettings = loadSettings(cwd);
@@ -696,6 +706,7 @@ export async function loadCliConfig(
// TODO: loading of hooks based on workspace trust
enableHooks: settings.tools?.enableHooks ?? false,
hooks: settings.hooks || {},
projectHooks: projectHooks || {},
onModelChange: (model: string) => saveModelChange(loadedSettings, model),
});
}
+12
View File
@@ -114,6 +114,7 @@ vi.mock('./config/settings.js', () => ({
security: { auth: {} },
ui: {},
},
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -289,6 +290,7 @@ describe('gemini.tsx main function', () => {
security: { auth: {} },
ui: {},
},
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
} as never);
@@ -522,6 +524,7 @@ describe('gemini.tsx main function kitty protocol', () => {
security: { auth: {} },
ui: {},
},
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
} as never);
@@ -583,6 +586,7 @@ describe('gemini.tsx main function kitty protocol', () => {
security: { auth: {} },
ui: {},
},
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -669,6 +673,7 @@ describe('gemini.tsx main function kitty protocol', () => {
security: { auth: {} },
ui: {},
},
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -737,6 +742,7 @@ describe('gemini.tsx main function kitty protocol', () => {
security: { auth: {} },
ui: { theme: 'non-existent-theme' },
},
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -819,6 +825,7 @@ describe('gemini.tsx main function kitty protocol', () => {
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: { theme: 'test' } },
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -898,6 +905,7 @@ describe('gemini.tsx main function kitty protocol', () => {
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: {} },
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -971,6 +979,7 @@ describe('gemini.tsx main function kitty protocol', () => {
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: {} },
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -1118,6 +1127,7 @@ describe('gemini.tsx main function exit codes', () => {
security: { auth: { selectedType: 'google', useExternal: false } },
ui: {},
},
workspace: { settings: {} },
errors: [],
} as never);
vi.mocked(parseArguments).mockResolvedValue({} as unknown as CliArgs);
@@ -1174,6 +1184,7 @@ describe('gemini.tsx main function exit codes', () => {
} as unknown as Config);
vi.mocked(loadSettings).mockReturnValue({
merged: { security: { auth: {} }, ui: {} },
workspace: { settings: {} },
errors: [],
} as never);
vi.mocked(parseArguments).mockResolvedValue({
@@ -1237,6 +1248,7 @@ describe('gemini.tsx main function exit codes', () => {
} as unknown as Config);
vi.mocked(loadSettings).mockReturnValue({
merged: { security: { auth: {} }, ui: {} },
workspace: { settings: {} },
errors: [],
} as never);
vi.mocked(parseArguments).mockResolvedValue({} as unknown as CliArgs);
+4 -1
View File
@@ -393,6 +393,7 @@ export async function main() {
settings.merged,
sessionId,
argv,
{ projectHooks: settings.workspace.settings.hooks },
);
if (
@@ -464,7 +465,9 @@ export async function main() {
// may have side effects.
{
const loadConfigHandle = startupProfiler.start('load_cli_config');
const config = await loadCliConfig(settings.merged, sessionId, argv);
const config = await loadCliConfig(settings.merged, sessionId, argv, {
projectHooks: settings.workspace.settings.hooks,
});
loadConfigHandle?.end();
// Register config for telemetry shutdown
+2
View File
@@ -60,6 +60,7 @@ vi.mock('./config/settings.js', async (importOriginal) => {
...actual,
loadSettings: vi.fn().mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: {} },
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -171,6 +172,7 @@ describe('gemini.tsx main function cleanup', () => {
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: {} },
workspace: { settings: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
@@ -180,7 +180,7 @@ describe('GeminiAgent', () => {
}),
'test-session-id',
mockArgv,
'/tmp',
{ cwd: '/tmp' },
);
});
@@ -218,7 +218,7 @@ export class GeminiAgent {
const settings = { ...this.settings.merged, mcpServers: mergedMcpServers };
const config = await loadCliConfig(settings, sessionId, this.argv, cwd);
const config = await loadCliConfig(settings, sessionId, this.argv, { cwd });
await config.initialize();
startupProfiler.flush(config);