From 4a57df8bf959376c502ecb149c5a3d6ccfe026c8 Mon Sep 17 00:00:00 2001 From: Rafli Ahmad Zulfikar Date: Wed, 23 Sep 2026 14:41:03 +0700 Subject: [PATCH] fix(usage): key live byApiKey stats by full api key to prevent team-key collision maskApiKey kept only the first 8 characters of the key. API keys minted from the same machine id share that prefix, so every key of an instance collapsed into one sk-XXXXXXX*** bucket per model/provider, and the dashboard attributed one holder's usage to another. Key the live path by the full api key - matching the daily rollup (aggregateEntryToDay) and the lastUsed overlay - and keep the last 4 characters in maskApiKey so masked keys stay distinguishable in the UI. --- src/lib/db/repos/usageRepo.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/db/repos/usageRepo.js b/src/lib/db/repos/usageRepo.js index 41199937..4ea3b8bd 100644 --- a/src/lib/db/repos/usageRepo.js +++ b/src/lib/db/repos/usageRepo.js @@ -5,8 +5,9 @@ import { getMeta, setMeta } from "../helpers/metaStore.js"; function maskApiKey(key) { if (!key || typeof key !== "string") return null; - if (key.length <= 8) return key.charAt(0) + "***"; - return key.slice(0, 8) + "***"; + if (key.length <= 12) return key.charAt(0) + "***"; + // Keep the tail: keys sharing a machine-id prefix (team keys) must not collide. + return key.slice(0, 8) + "***" + key.slice(-4); } const PENDING_TIMEOUT_MS = 60 * 1000; @@ -634,6 +635,8 @@ export async function getUsageStats(period = "all") { const keyInfo = apiKeyMap[r.apiKey]; const keyName = keyInfo?.name || r.apiKey.slice(0, 8) + "..."; const apiKeyMasked = maskApiKey(r.apiKey); + // Key by the FULL api key (same as the daily rollup + lastUsed overlay) + // — masking here collided all keys sharing a prefix into one bucket. const akKey = `${r.apiKey}|${r.model}|${r.provider || "unknown"}`; if (!stats.byApiKey[akKey]) { stats.byApiKey[akKey] = { requests: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0, rawModel: r.model, provider: providerDisplayName, apiKeyMasked, keyName, apiKeyKey: apiKeyMasked, lastUsed: r.timestamp };