diff --git a/backend/src/services/apiServer.ts b/backend/src/services/apiServer.ts index 566c3a8..1c46d6e 100644 --- a/backend/src/services/apiServer.ts +++ b/backend/src/services/apiServer.ts @@ -97,6 +97,20 @@ interface RuntimeHealth { canonicalLifecycleOrderRows?: number; } +const ALLOWED_SCREENER_SECTORS = new Set([ + 'Technology', + 'Financial Services', + 'Healthcare', + 'Consumer Cyclical', + 'Consumer Defensive', + 'Industrials', + 'Energy', + 'Utilities', + 'Real Estate', + 'Communication Services', + 'Basic Materials', +]); + interface TradeAuditEvent { event: string; userId?: string; @@ -2818,7 +2832,13 @@ RULES: try { const apiKey = process.env.FMP_API_KEY || 'demo'; const qs = new URLSearchParams(); - if (req.query.sector) qs.set('sector', String(req.query.sector)); + const sector = String(req.query.sector || '').trim(); + if (sector && sector !== 'All') { + if (!ALLOWED_SCREENER_SECTORS.has(sector)) { + return res.status(400).json({ error: 'Unsupported sector filter' }); + } + qs.set('sector', sector); + } if (req.query.marketCapMoreThan) qs.set('marketCapMoreThan', String(req.query.marketCapMoreThan)); if (req.query.marketCapLessThan) qs.set('marketCapLessThan', String(req.query.marketCapLessThan)); if (req.query.betaMoreThan) qs.set('betaMoreThan', String(req.query.betaMoreThan)); diff --git a/backend/verifyMarketDataEndpoints.ts b/backend/verifyMarketDataEndpoints.ts index e22413d..23388cb 100644 --- a/backend/verifyMarketDataEndpoints.ts +++ b/backend/verifyMarketDataEndpoints.ts @@ -168,6 +168,14 @@ function testFmpProxyContracts() { 'https://financialmodelingprep.com/api/v3/stock-screener?${qs.toString()}', '/api/screener must call FMP stock-screener', ); + assertSourceIncludes( + 'const ALLOWED_SCREENER_SECTORS = new Set([', + '/api/screener must keep an explicit sector allow-list', + ); + assertSourceMatches( + /if \(sector && sector !== 'All'\)[\s\S]*!ALLOWED_SCREENER_SECTORS\.has\(sector\)[\s\S]*status\(400\)\.json\(\{ error: 'Unsupported sector filter' \}\)[\s\S]*qs\.set\('sector', sector\)/, + '/api/screener must reject unsupported sector values before building the FMP query', + ); assertSourceIncludes( "qs.set('isEtf', 'false')", '/api/screener must exclude ETFs by default',