import { Router } from 'express'; import { resolveAndValidateHost, safeFetch } from './ssrf-guard'; const router = Router(); router.get('/image-proxy', async (req, res) => { try { const url = String(req.query.url || ''); if (!/^https?:\/\//i.test(url)) { return res.status(400).json({ error: 'Invalid URL' }); } const hostAllowed = await resolveAndValidateHost(url); if (!hostAllowed) { return res.status(400).json({ error: 'URL resolves to a blocked address' }); } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); const response = await safeFetch(url, { signal: controller.signal }); clearTimeout(timeout); if (!response || !response.ok) { return res.status(response?.status ?? 502).end(); } const contentType = response.headers.get('content-type') || ''; if (!contentType.toLowerCase().startsWith('image/')) { return res.status(415).json({ error: 'Unsupported content type' }); } const arrayBuffer = await response.arrayBuffer(); const MAX_BYTES = 8 * 1024 * 1024; if (arrayBuffer.byteLength > MAX_BYTES) { return res.status(413).json({ error: 'Image too large' }); } res.setHeader('Content-Type', contentType); res.setHeader('Cache-Control', 'public, max-age=3600'); res.send(Buffer.from(arrayBuffer)); } catch (err) { if ((err as { name?: string })?.name === 'AbortError') { return res.status(504).json({ error: 'Timeout fetching image' }); } console.error('Image proxy error:', err); res.status(502).json({ error: 'Failed to fetch image' }); } }); export default router;