fix: SSRF blocklist + レスポンスサイズ制限 + DB pool max 15 + pool.on(error)
This commit is contained in:
parent
1336b20c90
commit
5a3a510331
33
server.js
33
server.js
|
|
@ -177,7 +177,13 @@ const pool = new Pool({
|
|||
user: process.env.DB_USER || 'gitea',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: 'posimai_brain',
|
||||
max: 5
|
||||
max: 15,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 5000,
|
||||
});
|
||||
// プールレベルの接続エラーをキャッチ(未処理のままにしない)
|
||||
pool.on('error', (err) => {
|
||||
console.error('[DB] Unexpected pool error:', err.message);
|
||||
});
|
||||
|
||||
// ── Gemini ────────────────────────────────
|
||||
|
|
@ -282,15 +288,37 @@ function normalizeCharset(raw) {
|
|||
return 'utf-8';
|
||||
}
|
||||
|
||||
// ── SSRF ガード(fetchMeta / fetchFullTextViaJina 共用)──────────────
|
||||
// RFC 1918 プライベート帯域・ループバック・クラウドメタデータ IP をブロック
|
||||
const SSRF_BLOCKED = /^(127\.|localhost$|::1$|0\.0\.0\.0$|169\.254\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|100\.100\.100\.100|metadata\.google\.internal)/i;
|
||||
function isSsrfSafe(rawUrl) {
|
||||
let parsed;
|
||||
try { parsed = new URL(rawUrl); } catch { return false; }
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return false;
|
||||
if (SSRF_BLOCKED.test(parsed.hostname)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── OGP フェッチ ───────────────────────────
|
||||
const FETCH_META_MAX_BYTES = 2 * 1024 * 1024; // 2 MB 上限
|
||||
async function fetchMeta(url) {
|
||||
if (!isSsrfSafe(url)) {
|
||||
return { title: url.slice(0, 300), desc: '', ogImage: '', favicon: '' };
|
||||
}
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; PosimaiBot/1.0)' },
|
||||
signal: AbortSignal.timeout(6000)
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const buffer = await res.arrayBuffer();
|
||||
|
||||
// レスポンスサイズを 2MB に制限(OGP取得にそれ以上は不要)
|
||||
const contentLength = parseInt(res.headers.get('content-length') || '0', 10);
|
||||
if (contentLength > FETCH_META_MAX_BYTES) throw new Error('Response too large');
|
||||
const rawBuffer = await res.arrayBuffer();
|
||||
const buffer = rawBuffer.byteLength > FETCH_META_MAX_BYTES
|
||||
? rawBuffer.slice(0, FETCH_META_MAX_BYTES)
|
||||
: rawBuffer;
|
||||
|
||||
// 文字コード判定: 1) Content-Typeヘッダー優先 2) HTMLメタタグ確認
|
||||
// iso-8859-1はバイト値0-255をロスレスでデコードするためcharset検出に最適
|
||||
|
|
@ -341,6 +369,7 @@ async function fetchMeta(url) {
|
|||
|
||||
// ── Jina Reader API フェッチ(新規追加)───
|
||||
async function fetchFullTextViaJina(url) {
|
||||
if (!isSsrfSafe(url)) return null;
|
||||
try {
|
||||
console.log(`[Brain API] Fetching full text via Jina Reader for: ${url}`);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue