mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 11:37:38 +00:00
fix: harden Feishu media URL fetching (#16285) (thanks @mbelinky)
Security fix for Feishu extension media fetching.
This commit is contained in:
committed by
GitHub
parent
d82c5ea9d1
commit
5b4121d601
@@ -5,6 +5,7 @@ import { Readable } from "stream";
|
||||
import { listEnabledFeishuAccounts } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
|
||||
import { getFeishuRuntime } from "./runtime.js";
|
||||
import { resolveToolsConfig } from "./tools-config.js";
|
||||
|
||||
// ============ Helpers ============
|
||||
@@ -175,12 +176,9 @@ async function uploadImageToDocx(
|
||||
return fileToken;
|
||||
}
|
||||
|
||||
async function downloadImage(url: string): Promise<Buffer> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return Buffer.from(await response.arrayBuffer());
|
||||
async function downloadImage(url: string, maxBytes: number): Promise<Buffer> {
|
||||
const fetched = await getFeishuRuntime().channel.media.fetchRemoteMedia({ url, maxBytes });
|
||||
return fetched.buffer;
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
|
||||
@@ -189,6 +187,7 @@ async function processImages(
|
||||
docToken: string,
|
||||
markdown: string,
|
||||
insertedBlocks: any[],
|
||||
maxBytes: number,
|
||||
): Promise<number> {
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
const imageUrls = extractImageUrls(markdown);
|
||||
@@ -204,7 +203,7 @@ async function processImages(
|
||||
const blockId = imageBlocks[i].block_id;
|
||||
|
||||
try {
|
||||
const buffer = await downloadImage(url);
|
||||
const buffer = await downloadImage(url, maxBytes);
|
||||
const urlPath = new URL(url).pathname;
|
||||
const fileName = urlPath.split("/").pop() || `image_${i}.png`;
|
||||
const fileToken = await uploadImageToDocx(client, blockId, buffer, fileName);
|
||||
@@ -284,7 +283,7 @@ async function createDoc(client: Lark.Client, title: string, folderToken?: strin
|
||||
};
|
||||
}
|
||||
|
||||
async function writeDoc(client: Lark.Client, docToken: string, markdown: string) {
|
||||
async function writeDoc(client: Lark.Client, docToken: string, markdown: string, maxBytes: number) {
|
||||
const deleted = await clearDocumentContent(client, docToken);
|
||||
|
||||
const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
|
||||
@@ -294,7 +293,7 @@ async function writeDoc(client: Lark.Client, docToken: string, markdown: string)
|
||||
const sortedBlocks = sortBlocksByFirstLevel(blocks, firstLevelBlockIds);
|
||||
|
||||
const { children: inserted, skipped } = await insertBlocks(client, docToken, sortedBlocks);
|
||||
const imagesProcessed = await processImages(client, docToken, markdown, inserted);
|
||||
const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -307,7 +306,12 @@ async function writeDoc(client: Lark.Client, docToken: string, markdown: string)
|
||||
};
|
||||
}
|
||||
|
||||
async function appendDoc(client: Lark.Client, docToken: string, markdown: string) {
|
||||
async function appendDoc(
|
||||
client: Lark.Client,
|
||||
docToken: string,
|
||||
markdown: string,
|
||||
maxBytes: number,
|
||||
) {
|
||||
const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
|
||||
if (blocks.length === 0) {
|
||||
throw new Error("Content is empty");
|
||||
@@ -315,7 +319,7 @@ async function appendDoc(client: Lark.Client, docToken: string, markdown: string
|
||||
const sortedBlocks = sortBlocksByFirstLevel(blocks, firstLevelBlockIds);
|
||||
|
||||
const { children: inserted, skipped } = await insertBlocks(client, docToken, sortedBlocks);
|
||||
const imagesProcessed = await processImages(client, docToken, markdown, inserted);
|
||||
const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -453,6 +457,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
// Use first account's config for tools configuration
|
||||
const firstAccount = accounts[0];
|
||||
const toolsCfg = resolveToolsConfig(firstAccount.config.tools);
|
||||
const mediaMaxBytes = (firstAccount.config?.mediaMaxMb ?? 30) * 1024 * 1024;
|
||||
|
||||
// Helper to get client for the default account
|
||||
const getClient = () => createFeishuClient(firstAccount);
|
||||
@@ -475,9 +480,9 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
case "read":
|
||||
return json(await readDoc(client, p.doc_token));
|
||||
case "write":
|
||||
return json(await writeDoc(client, p.doc_token, p.content));
|
||||
return json(await writeDoc(client, p.doc_token, p.content, mediaMaxBytes));
|
||||
case "append":
|
||||
return json(await appendDoc(client, p.doc_token, p.content));
|
||||
return json(await appendDoc(client, p.doc_token, p.content, mediaMaxBytes));
|
||||
case "create":
|
||||
return json(await createDoc(client, p.title, p.folder_token));
|
||||
case "list_blocks":
|
||||
|
||||
Reference in New Issue
Block a user