mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 10:34:35 -07:00
fix(security): rate limit web_fetch tool to mitigate DDoS via prompt injection (#19567)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -33,10 +33,46 @@ import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { retryWithBackoff } from '../utils/retry.js';
|
||||
import { WEB_FETCH_DEFINITION } from './definitions/coreTools.js';
|
||||
import { resolveToolDeclaration } from './definitions/resolver.js';
|
||||
import { LRUCache } from 'mnemonist';
|
||||
|
||||
const URL_FETCH_TIMEOUT_MS = 10000;
|
||||
const MAX_CONTENT_LENGTH = 100000;
|
||||
|
||||
// Rate limiting configuration
|
||||
const RATE_LIMIT_WINDOW_MS = 60000; // 1 minute
|
||||
const MAX_REQUESTS_PER_WINDOW = 10;
|
||||
const hostRequestHistory = new LRUCache<string, number[]>(1000);
|
||||
|
||||
function checkRateLimit(url: string): {
|
||||
allowed: boolean;
|
||||
waitTimeMs?: number;
|
||||
} {
|
||||
try {
|
||||
const hostname = new URL(url).hostname;
|
||||
const now = Date.now();
|
||||
const windowStart = now - RATE_LIMIT_WINDOW_MS;
|
||||
|
||||
let history = hostRequestHistory.get(hostname) || [];
|
||||
// Clean up old timestamps
|
||||
history = history.filter((timestamp) => timestamp > windowStart);
|
||||
|
||||
if (history.length >= MAX_REQUESTS_PER_WINDOW) {
|
||||
// Calculate wait time based on the oldest timestamp in the current window
|
||||
const oldestTimestamp = history[0];
|
||||
const waitTimeMs = oldestTimestamp + RATE_LIMIT_WINDOW_MS - now;
|
||||
hostRequestHistory.set(hostname, history); // Update cleaned history
|
||||
return { allowed: false, waitTimeMs: Math.max(0, waitTimeMs) };
|
||||
}
|
||||
|
||||
history.push(now);
|
||||
hostRequestHistory.set(hostname, history);
|
||||
return { allowed: true };
|
||||
} catch (_e) {
|
||||
// If URL parsing fails, we fallback to allowed (should be caught by parsePrompt anyway)
|
||||
return { allowed: true };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a prompt to extract valid URLs and identify malformed ones.
|
||||
*/
|
||||
@@ -258,6 +294,23 @@ ${textContent}
|
||||
const userPrompt = this.params.prompt;
|
||||
const { validUrls: urls } = parsePrompt(userPrompt);
|
||||
const url = urls[0];
|
||||
|
||||
// Enforce rate limiting
|
||||
const rateLimitResult = checkRateLimit(url);
|
||||
if (!rateLimitResult.allowed) {
|
||||
const waitTimeSecs = Math.ceil((rateLimitResult.waitTimeMs || 0) / 1000);
|
||||
const errorMessage = `Rate limit exceeded for host. Please wait ${waitTimeSecs} seconds before trying again.`;
|
||||
debugLogger.warn(`[WebFetchTool] Rate limit exceeded for ${url}`);
|
||||
return {
|
||||
llmContent: `Error: ${errorMessage}`,
|
||||
returnDisplay: `Error: ${errorMessage}`,
|
||||
error: {
|
||||
message: errorMessage,
|
||||
type: ToolErrorType.WEB_FETCH_PROCESSING_ERROR,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const isPrivate = isPrivateIp(url);
|
||||
|
||||
if (isPrivate) {
|
||||
|
||||
Reference in New Issue
Block a user