fix(security): prevent Host header injection in story.js (#2102)

* fix(security): prevent Host header injection in story.js

req.headers.host is attacker-controlled and used to construct the
redirect Location header and OG meta tag URLs. An attacker sending
Host: evil.com gets users redirected to evil.com (open redirect) and
social bots see OG tags pointing to evil.com (preview poisoning).

Hardcode the canonical domain instead of trusting the Host header.

* fix(security): replace req.headers.host with hardcoded canonical domain in og-story.js

---------

Co-authored-by: warren618 <warren618@users.noreply.github.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
This commit is contained in:
Haozhe Wu
2026-03-23 12:44:25 +08:00
committed by GitHub
parent ed12bf5c84
commit f18404f6f8
2 changed files with 3 additions and 3 deletions

View File

@@ -31,7 +31,7 @@ function normalizeLevel(rawLevel) {
} }
export default function handler(req, res) { export default function handler(req, res) {
const url = new URL(req.url, `https://${req.headers.host}`); const url = new URL(req.url, 'https://worldmonitor.app');
const countryCode = (url.searchParams.get('c') || '').toUpperCase(); const countryCode = (url.searchParams.get('c') || '').toUpperCase();
const type = url.searchParams.get('t') || 'ciianalysis'; const type = url.searchParams.get('t') || 'ciianalysis';
const score = url.searchParams.get('s'); const score = url.searchParams.get('s');

View File

@@ -16,7 +16,7 @@ const COUNTRY_NAMES = {
const BOT_UA = /twitterbot|facebookexternalhit|linkedinbot|slackbot|telegrambot|whatsapp|discordbot|redditbot|googlebot/i; const BOT_UA = /twitterbot|facebookexternalhit|linkedinbot|slackbot|telegrambot|whatsapp|discordbot|redditbot|googlebot/i;
export default function handler(req, res) { export default function handler(req, res) {
const url = new URL(req.url, `https://${req.headers.host}`); const url = new URL(req.url, 'https://worldmonitor.app');
const countryCode = (url.searchParams.get('c') || '').toUpperCase(); const countryCode = (url.searchParams.get('c') || '').toUpperCase();
const type = url.searchParams.get('t') || 'ciianalysis'; const type = url.searchParams.get('t') || 'ciianalysis';
const ts = url.searchParams.get('ts') || ''; const ts = url.searchParams.get('ts') || '';
@@ -26,7 +26,7 @@ export default function handler(req, res) {
const ua = req.headers['user-agent'] || ''; const ua = req.headers['user-agent'] || '';
const isBot = BOT_UA.test(ua); const isBot = BOT_UA.test(ua);
const baseUrl = `https://${req.headers.host}`; const baseUrl = 'https://worldmonitor.app';
const spaUrl = `${baseUrl}/?c=${countryCode}&t=${type}${ts ? `&ts=${ts}` : ''}`; const spaUrl = `${baseUrl}/?c=${countryCode}&t=${type}${ts ? `&ts=${ts}` : ''}`;
// Real users → redirect to SPA // Real users → redirect to SPA