fix: security hardening round 2

- CORS: origin=null now rejected (was: allowed as same-origin)
- CORS: regex tightened to [\w-]+ to prevent subdomain bypass
- CORS: add *.posimai.soar-enrich.com and posimai.soar-enrich.com explicitly
- Stripe webhook: fix regex capture groups + add uppercase hex support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
posimai 2026-04-05 03:01:06 +09:00
parent 0590d0995d
commit ac8cc6db81
1 changed files with 7 additions and 4 deletions

View File

@ -116,9 +116,11 @@ const extraOrigins = (process.env.ALLOWED_ORIGINS || '')
.split(',').map(o => o.trim()).filter(Boolean); .split(',').map(o => o.trim()).filter(Boolean);
function isAllowedOrigin(origin) { function isAllowedOrigin(origin) {
if (!origin) return true; // 同一オリジン if (!origin) return false; // origin なしは拒否CSRF 対策)
if (process.env.NODE_ENV !== 'production' && /^http:\/\/localhost(:\d+)?$/.test(origin)) return true; // localhost 開発のみ if (process.env.NODE_ENV !== 'production' && /^http:\/\/localhost(:\d+)?$/.test(origin)) return true; // localhost 開発のみ
if (/^https:\/\/posimai-[^.]+\.vercel\.app$/.test(origin)) return true; // 全 Posimai アプリ if (/^https:\/\/posimai-[\w-]+\.vercel\.app$/.test(origin)) return true; // 全 Posimai アプリ(英数字・ハイフンのみ)
if (/^https:\/\/[\w-]+\.posimai\.soar-enrich\.com$/.test(origin)) return true; // 独自ドメイン配下
if (origin === 'https://posimai.soar-enrich.com') return true; // Dashboard
if (extraOrigins.includes(origin)) return true; // 追加許可 if (extraOrigins.includes(origin)) return true; // 追加許可
return false; return false;
} }
@ -2726,8 +2728,9 @@ async function handleStripeWebhook(req, res) {
try { try {
const crypto = require('crypto'); const crypto = require('crypto');
const rawBody = req.body; // Buffer const rawBody = req.body; // Buffer
const [, tsStr, , v1Sig] = sig.match(/t=(\d+),.*v1=([a-f0-9]+)/) || []; const match = sig.match(/t=(\d+).*?,.*?v1=([a-fA-F0-9]+)/);
if (!tsStr || !v1Sig) throw new Error('Invalid signature format'); if (!match || match.length < 3) throw new Error('Invalid signature format');
const [, tsStr, v1Sig] = match;
const tolerance = 300; // 5分 const tolerance = 300; // 5分
if (Math.abs(Date.now() / 1000 - parseInt(tsStr)) > tolerance) { if (Math.abs(Date.now() / 1000 - parseInt(tsStr)) > tolerance) {
throw new Error('Timestamp too old'); throw new Error('Timestamp too old');