Fix critical bugs for security and data accuracy

This commit addresses three important bugs:

1. SQL Injection Prevention (proxy.ts:70-75):
   - Added whitelist validation for DATABASE_TABLE environment variable
   - Table names are now validated against ALLOWED_TABLES before use
   - Prevents potential SQL injection through malicious table names

2. SQL Interval Parameter Bug (dashboard/app/api/metrics/route.ts):
   - Fixed incorrect INTERVAL syntax in PostgreSQL queries
   - Changed from INTERVAL '$1 hours' to INTERVAL '1 hour' * $1
   - Properly uses parameterized queries with interval multiplication
   - Affects all 4 queries: summary, recent, model breakdown, and trends

3. Incorrect Property Reference (proxy.ts:206):
   - Fixed usage.cached_tokens to usage.prompt_tokens_details?.cached_tokens
   - Aligns with OpenAI API response structure for cached tokens
   - Ensures accurate logging of cached token usage
This commit is contained in:
Claude
2025-11-23 06:18:11 +00:00
parent aac2c3f224
commit 8b90fa2b9e
2 changed files with 11 additions and 6 deletions

View File

@@ -36,7 +36,7 @@ export async function GET(request: NextRequest) {
COUNT(DISTINCT model) as unique_models, COUNT(DISTINCT model) as unique_models,
COUNT(DISTINCT client_ip) as unique_clients COUNT(DISTINCT client_ip) as unique_clients
FROM ${validatedTableName} FROM ${validatedTableName}
WHERE timestamp >= NOW() - INTERVAL '$1 hours' WHERE timestamp >= NOW() - INTERVAL '1 hour' * $1
`; `;
const summaryResult = await client.query(summaryQuery, [hours]); const summaryResult = await client.query(summaryQuery, [hours]);
const summary = summaryResult.rows[0]; const summary = summaryResult.rows[0];
@@ -56,7 +56,7 @@ export async function GET(request: NextRequest) {
client_ip, client_ip,
stream stream
FROM ${validatedTableName} FROM ${validatedTableName}
WHERE timestamp >= NOW() - INTERVAL '$1 hours' WHERE timestamp >= NOW() - INTERVAL '1 hour' * $1
ORDER BY timestamp DESC ORDER BY timestamp DESC
LIMIT $2 LIMIT $2
`; `;
@@ -72,7 +72,7 @@ export async function GET(request: NextRequest) {
SUM(total_cost) as total_cost, SUM(total_cost) as total_cost,
AVG(response_time) as avg_response_time AVG(response_time) as avg_response_time
FROM ${validatedTableName} FROM ${validatedTableName}
WHERE timestamp >= NOW() - INTERVAL '$1 hours' WHERE timestamp >= NOW() - INTERVAL '1 hour' * $1
GROUP BY model GROUP BY model
ORDER BY request_count DESC ORDER BY request_count DESC
`; `;
@@ -88,7 +88,7 @@ export async function GET(request: NextRequest) {
SUM(total_cost) as cost, SUM(total_cost) as cost,
AVG(response_time) as avg_response_time AVG(response_time) as avg_response_time
FROM ${validatedTableName} FROM ${validatedTableName}
WHERE timestamp >= NOW() - INTERVAL '$1 hours' WHERE timestamp >= NOW() - INTERVAL '1 hour' * $1
GROUP BY hour GROUP BY hour
ORDER BY hour ASC ORDER BY hour ASC
`; `;

View File

@@ -67,7 +67,12 @@ async function logToPG(data: Record<string, any>) {
const vals = keys.map((_, i) => `$${i + 1}`).join(","); const vals = keys.map((_, i) => `$${i + 1}`).join(",");
const values = Object.values(data); const values = Object.values(data);
await pool.query(`INSERT INTO ${process.env.DATABASE_TABLE || "llm_proxy"} (${cols}) VALUES (${vals})`, values); // Validate table name against whitelist to prevent SQL injection
const TABLE_NAME = process.env.DATABASE_TABLE || "llm_proxy";
const ALLOWED_TABLES = ["llm_proxy", "llm_proxy_dev", "llm_proxy_test"];
const validatedTableName = ALLOWED_TABLES.includes(TABLE_NAME) ? TABLE_NAME : "llm_proxy";
await pool.query(`INSERT INTO ${validatedTableName} (${cols}) VALUES (${vals})`, values);
} }
// --- Main proxy server --- // --- Main proxy server ---
@@ -198,7 +203,7 @@ const server = http.createServer(async (req, res) => {
completion_tokens: usage.completion_tokens || null, completion_tokens: usage.completion_tokens || null,
prompt_tokens: usage.prompt_tokens || null, prompt_tokens: usage.prompt_tokens || null,
total_tokens: usage.total_tokens || null, total_tokens: usage.total_tokens || null,
cached_tokens: usage.cached_tokens || null, cached_tokens: usage.prompt_tokens_details?.cached_tokens || null,
total_cost: totalCost, total_cost: totalCost,
response_time: Date.now() - start, response_time: Date.now() - start,
request_body: requestJson, request_body: requestJson,