fix: Together/Jina に SSRF ガード + Jina レスポンスサイズ上限 1MB

This commit is contained in:
posimai 2026-04-09 23:49:25 +09:00
parent 5a3a510331
commit 5de1174363
1 changed files with 9 additions and 4 deletions

View File

@ -385,7 +385,11 @@ async function fetchFullTextViaJina(url) {
return null; 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(); let markdown = await jinaResponse.text();
if (markdown.length > 1024 * 1024) markdown = markdown.slice(0, 1024 * 1024);
// Markdown Content: マーカーの後ろを抽出 // Markdown Content: マーカーの後ろを抽出
const contentMarker = 'Markdown Content:'; const contentMarker = 'Markdown Content:';
@ -2322,7 +2326,7 @@ ${excerpt}
// fire-and-forget アーカイブ: Jina Reader → Gemini 要約(直列) // fire-and-forget アーカイブ: Jina Reader → Gemini 要約(直列)
async function archiveShare(shareId, url) { 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]); await pool.query(`UPDATE together_shares SET archive_status='failed' WHERE id=$1`, [shareId]);
return; return;
} }
@ -2332,7 +2336,9 @@ ${excerpt}
signal: AbortSignal.timeout(30000), signal: AbortSignal.timeout(30000),
}); });
if (!jinaRes.ok) throw new Error(`Jina ${jinaRes.status}`); 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: ..." を抽出 // Jina Reader のレスポンス先頭から "Title: ..." を抽出
const titleMatch = fullContent.match(/^Title:\s*(.+)/m); 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 || {}; 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 (!group_id || !shared_by) return res.status(400).json({ error: 'group_id と shared_by は必須です' });
if (url) { if (url) {
try { const p = new URL(url); if (!['http:', 'https:'].includes(p.protocol)) throw new Error(); } if (!isSsrfSafe(url)) return res.status(400).json({ error: 'url は http/https のみ有効です' });
catch { return res.status(400).json({ error: 'url は http/https のみ有効です' }); }
} }
try { try {
const grpCheck = await pool.query('SELECT id FROM together_groups WHERE id=$1', [group_id]); const grpCheck = await pool.query('SELECT id FROM together_groups WHERE id=$1', [group_id]);