From 5de117436382a64fae69da277a925b71fefff2db Mon Sep 17 00:00:00 2001 From: posimai Date: Thu, 9 Apr 2026 23:49:25 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Together/Jina=20=E3=81=AB=20SSRF=20?= =?UTF-8?q?=E3=82=AC=E3=83=BC=E3=83=89=20+=20Jina=20=E3=83=AC=E3=82=B9?= =?UTF-8?q?=E3=83=9D=E3=83=B3=E3=82=B9=E3=82=B5=E3=82=A4=E3=82=BA=E4=B8=8A?= =?UTF-8?q?=E9=99=90=201MB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index 52e10571..dc5decaf 100644 --- a/server.js +++ b/server.js @@ -385,7 +385,11 @@ async function fetchFullTextViaJina(url) { return null; } + // レスポンスサイズを 1MB に制限(AI 分析に必要な本文量の上限) + const jinaContentLength = parseInt(jinaResponse.headers.get('content-length') || '0', 10); + if (jinaContentLength > 1024 * 1024) return null; let markdown = await jinaResponse.text(); + if (markdown.length > 1024 * 1024) markdown = markdown.slice(0, 1024 * 1024); // Markdown Content: マーカーの後ろを抽出 const contentMarker = 'Markdown Content:'; @@ -2322,7 +2326,7 @@ ${excerpt} // fire-and-forget アーカイブ: Jina Reader → Gemini 要約(直列) async function archiveShare(shareId, url) { - if (!url) { + if (!url || !isSsrfSafe(url)) { await pool.query(`UPDATE together_shares SET archive_status='failed' WHERE id=$1`, [shareId]); return; } @@ -2332,7 +2336,9 @@ ${excerpt} signal: AbortSignal.timeout(30000), }); if (!jinaRes.ok) throw new Error(`Jina ${jinaRes.status}`); - const fullContent = await jinaRes.text(); + let fullContent = await jinaRes.text(); + // レスポンスサイズを 1MB に制限(DB の full_content カラムおよびGemini入力量の上限) + if (fullContent.length > 1024 * 1024) fullContent = fullContent.slice(0, 1024 * 1024); // Jina Reader のレスポンス先頭から "Title: ..." を抽出 const titleMatch = fullContent.match(/^Title:\s*(.+)/m); @@ -2452,8 +2458,7 @@ ${excerpt} const { group_id, shared_by, url = null, title = null, message = '', tags = [] } = req.body || {}; if (!group_id || !shared_by) return res.status(400).json({ error: 'group_id と shared_by は必須です' }); if (url) { - try { const p = new URL(url); if (!['http:', 'https:'].includes(p.protocol)) throw new Error(); } - catch { return res.status(400).json({ error: 'url は http/https のみ有効です' }); } + if (!isSsrfSafe(url)) return res.status(400).json({ error: 'url は http/https のみ有効です' }); } try { const grpCheck = await pool.query('SELECT id FROM together_groups WHERE id=$1', [group_id]);