mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 11:04:42 -07:00
175 lines
4.5 KiB
TypeScript
175 lines
4.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type { InProcessChecker } from '../built-in.js';
|
|
import {
|
|
SafetyCheckDecision,
|
|
type SafetyCheckInput,
|
|
type SafetyCheckResult,
|
|
} from '../protocol.js';
|
|
|
|
import {
|
|
logConsecaPolicyGeneration,
|
|
ConsecaPolicyGenerationEvent,
|
|
logConsecaVerdict,
|
|
ConsecaVerdictEvent,
|
|
} from '../../telemetry/index.js';
|
|
import { debugLogger } from '../../utils/debugLogger.js';
|
|
import type { Config } from '../../config/config.js';
|
|
|
|
import { generatePolicy } from './policy-generator.js';
|
|
import { enforcePolicy } from './policy-enforcer.js';
|
|
import type { SecurityPolicy } from './types.js';
|
|
import type { AgentLoopContext } from '../../config/agent-loop-context.js';
|
|
|
|
export class ConsecaSafetyChecker implements InProcessChecker {
|
|
private static instance: ConsecaSafetyChecker | undefined;
|
|
private currentPolicy: SecurityPolicy | null = null;
|
|
private activeUserPrompt: string | null = null;
|
|
private context: AgentLoopContext | null = null;
|
|
|
|
/**
|
|
* Private constructor to enforce singleton pattern.
|
|
* Use `getInstance()` to access the instance.
|
|
*/
|
|
private constructor() {}
|
|
|
|
static getInstance(): ConsecaSafetyChecker {
|
|
if (!ConsecaSafetyChecker.instance) {
|
|
ConsecaSafetyChecker.instance = new ConsecaSafetyChecker();
|
|
}
|
|
return ConsecaSafetyChecker.instance;
|
|
}
|
|
|
|
/**
|
|
* Resets the singleton instance. Use only in tests.
|
|
*/
|
|
static resetInstance(): void {
|
|
ConsecaSafetyChecker.instance = undefined;
|
|
}
|
|
|
|
setContext(context: AgentLoopContext): void {
|
|
this.context = context;
|
|
}
|
|
|
|
async check(input: SafetyCheckInput): Promise<SafetyCheckResult> {
|
|
debugLogger.debug(
|
|
`[Conseca] check called. History is: ${JSON.stringify(input.context.history)}`,
|
|
);
|
|
|
|
if (!this.context) {
|
|
debugLogger.debug('[Conseca] check failed: Config not initialized');
|
|
return {
|
|
decision: SafetyCheckDecision.ALLOW,
|
|
reason: 'Config not initialized',
|
|
};
|
|
}
|
|
|
|
if (!this.context.config.enableConseca) {
|
|
debugLogger.debug('[Conseca] check skipped: Conseca is not enabled.');
|
|
return {
|
|
decision: SafetyCheckDecision.ALLOW,
|
|
reason: 'Conseca is disabled',
|
|
};
|
|
}
|
|
|
|
const userPrompt = this.extractUserPrompt(input);
|
|
let trustedContent = '';
|
|
|
|
const toolRegistry = this.context.toolRegistry;
|
|
if (toolRegistry) {
|
|
const tools = toolRegistry.getFunctionDeclarations();
|
|
trustedContent = JSON.stringify(tools, null, 2);
|
|
}
|
|
|
|
if (userPrompt) {
|
|
await this.getPolicy(userPrompt, trustedContent, this.context.config);
|
|
} else {
|
|
debugLogger.debug(
|
|
`[Conseca] Skipping policy generation because userPrompt is null`,
|
|
);
|
|
}
|
|
|
|
let result: SafetyCheckResult;
|
|
|
|
if (!this.currentPolicy) {
|
|
result = {
|
|
decision: SafetyCheckDecision.ALLOW, // Fallback if no policy generated yet
|
|
reason: 'No security policy generated.',
|
|
error: 'No security policy generated.',
|
|
};
|
|
} else {
|
|
result = await enforcePolicy(
|
|
this.currentPolicy,
|
|
input.toolCall,
|
|
this.context.config,
|
|
);
|
|
}
|
|
|
|
logConsecaVerdict(
|
|
this.context.config,
|
|
new ConsecaVerdictEvent(
|
|
userPrompt || '',
|
|
JSON.stringify(this.currentPolicy || {}),
|
|
JSON.stringify(input.toolCall),
|
|
result.decision,
|
|
result.reason || '',
|
|
'error' in result ? result.error : undefined,
|
|
),
|
|
);
|
|
|
|
return result;
|
|
}
|
|
|
|
async getPolicy(
|
|
userPrompt: string,
|
|
trustedContent: string,
|
|
config: Config,
|
|
): Promise<SecurityPolicy> {
|
|
if (this.activeUserPrompt === userPrompt && this.currentPolicy) {
|
|
return this.currentPolicy;
|
|
}
|
|
|
|
const { policy, error } = await generatePolicy(
|
|
userPrompt,
|
|
trustedContent,
|
|
config,
|
|
);
|
|
this.currentPolicy = policy;
|
|
this.activeUserPrompt = userPrompt;
|
|
|
|
logConsecaPolicyGeneration(
|
|
config,
|
|
new ConsecaPolicyGenerationEvent(
|
|
userPrompt,
|
|
trustedContent,
|
|
JSON.stringify(policy),
|
|
error,
|
|
),
|
|
);
|
|
|
|
return policy;
|
|
}
|
|
|
|
private extractUserPrompt(input: SafetyCheckInput): string | null {
|
|
const prompt = input.context.history?.turns.at(-1)?.user.text;
|
|
if (prompt) {
|
|
return prompt;
|
|
}
|
|
debugLogger.debug(`[Conseca] extractUserPrompt failed.`);
|
|
return null;
|
|
}
|
|
|
|
// Helper methods for testing state
|
|
getCurrentPolicy(): SecurityPolicy | null {
|
|
return this.currentPolicy;
|
|
}
|
|
|
|
getActiveUserPrompt(): string | null {
|
|
return this.activeUserPrompt;
|
|
}
|
|
}
|