Files
openclaw/src/telegram/download.ts
chenglun.hu d46b489e21 fix(telegram): add timeout to file download to prevent DoS (CWE-400)
Add AbortSignal.timeout() to both fetch calls in download.ts to prevent
indefinite hangs when Telegram API is slow or unresponsive.

- getTelegramFile(): 30s timeout for metadata API call
- downloadTelegramFile(): 60s timeout for file download

Both functions now accept optional timeoutMs parameter for configurability.

Fixes #6849

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:39:39 +05:30

58 lines
1.7 KiB
TypeScript

import { detectMime } from "../media/mime.js";
import { type SavedMedia, saveMediaBuffer } from "../media/store.js";
export type TelegramFileInfo = {
file_id: string;
file_unique_id?: string;
file_size?: number;
file_path?: string;
};
export async function getTelegramFile(
token: string,
fileId: string,
timeoutMs = 30_000,
): Promise<TelegramFileInfo> {
const res = await fetch(
`https://api.telegram.org/bot${token}/getFile?file_id=${encodeURIComponent(fileId)}`,
{ signal: AbortSignal.timeout(timeoutMs) },
);
if (!res.ok) {
throw new Error(`getFile failed: ${res.status} ${res.statusText}`);
}
const json = (await res.json()) as { ok: boolean; result?: TelegramFileInfo };
if (!json.ok || !json.result?.file_path) {
throw new Error("getFile returned no file_path");
}
return json.result;
}
export async function downloadTelegramFile(
token: string,
info: TelegramFileInfo,
maxBytes?: number,
timeoutMs = 60_000,
): Promise<SavedMedia> {
if (!info.file_path) {
throw new Error("file_path missing");
}
const url = `https://api.telegram.org/file/bot${token}/${info.file_path}`;
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
if (!res.ok || !res.body) {
throw new Error(`Failed to download telegram file: HTTP ${res.status}`);
}
const array = Buffer.from(await res.arrayBuffer());
const mime = await detectMime({
buffer: array,
headerMime: res.headers.get("content-type"),
filePath: info.file_path,
});
// save with inbound subdir
const saved = await saveMediaBuffer(array, mime, "inbound", maxBytes, info.file_path);
// Ensure extension matches mime if possible
if (!saved.contentType && mime) {
saved.contentType = mime;
}
return saved;
}