fix(C4): validate news symbol filters

Normalize and limit /api/news symbols before proxying to Alpaca so only bounded, expected symbol characters reach the upstream news endpoint.

Refs: docs/AUDIT_REDESIGN.md item C4.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
This commit is contained in:
Saravana Achu Mac 2026-05-04 16:47:24 -07:00
parent 9e22f6460c
commit 7c4b08cdd5
2 changed files with 37 additions and 1 deletions

View File

@ -111,6 +111,25 @@ const ALLOWED_SCREENER_SECTORS = new Set([
'Basic Materials',
]);
const NEWS_SYMBOL_PATTERN = /^[A-Z0-9][A-Z0-9./-]{0,15}$/;
const MAX_NEWS_SYMBOLS = 10;
const normalizeNewsSymbolsQuery = (value: unknown): string => {
const raw = String(value || '').trim();
if (!raw) return '';
const symbols = raw
.split(',')
.map((symbol) => symbol.trim().toUpperCase())
.filter(Boolean);
if (symbols.length > MAX_NEWS_SYMBOLS || symbols.some((symbol) => !NEWS_SYMBOL_PATTERN.test(symbol))) {
throw new Error('Invalid news symbols query');
}
return symbols.join(',');
};
interface TradeAuditEvent {
event: string;
userId?: string;
@ -2726,7 +2745,12 @@ RULES:
// ── News: proxy to Alpaca /v1beta1/news ───────────────────────────────
this.app.get('/api/news', this.requireAuth, async (req, res) => {
try {
const symbols = String(req.query.symbols || '').trim().toUpperCase();
let symbols = '';
try {
symbols = normalizeNewsSymbolsQuery(req.query.symbols);
} catch (validationError: any) {
return res.status(400).json({ error: validationError.message });
}
const limit = Math.max(1, Math.min(50, Number(req.query.limit) || 10));
const alpacaKey = config.ALPACA_API_KEY;
const alpacaSecret = config.ALPACA_API_SECRET;

View File

@ -113,6 +113,18 @@ function testAlpacaProxyContracts() {
/this\.app\.get\('\/api\/news'[\s\S]*Math\.max\(1, Math\.min\(50, Number\(req\.query\.limit\) \|\| 10\)\)/,
'/api/news must clamp limit to 1..50',
);
assertSourceIncludes(
'const NEWS_SYMBOL_PATTERN = /^[A-Z0-9][A-Z0-9./-]{0,15}$/;',
'/api/news must constrain symbols to expected characters and length',
);
assertSourceIncludes(
'const MAX_NEWS_SYMBOLS = 10;',
'/api/news must limit the number of symbols sent upstream',
);
assertSourceMatches(
/symbols = normalizeNewsSymbolsQuery\(req\.query\.symbols\)[\s\S]*status\(400\)\.json\(\{ error: validationError\.message \}\)/,
'/api/news must reject invalid symbol queries before proxying to Alpaca',
);
assertSourceIncludes(
'https://data.alpaca.markets/v1beta1/news?${qs.toString()}',
'/api/news must proxy Alpaca news',