@@ -214,8 +214,8 @@ func (s *RequestStatistics) Record(ctx context.Context, record coreusage.Record)
214214 s .updateAPIStats (stats , modelName , RequestDetail {
215215 Timestamp : timestamp ,
216216 LatencyMs : normaliseLatency (record .Latency ),
217- Source : record .Source ,
218- AuthIndex : record .AuthIndex ,
217+ Source : sanitizeUsageDetailSource ( record .Source ) ,
218+ AuthIndex : sanitizeUsageDetailAuthIndex ( record .AuthIndex ) ,
219219 Tokens : detail ,
220220 Failed : failed ,
221221 })
@@ -308,6 +308,7 @@ func (s *RequestStatistics) Snapshot() StatisticsSnapshot {
308308
309309 result .APIs = make (map [string ]APISnapshot , len (s .apis ))
310310 for apiName , stats := range s .apis {
311+ apiName = sanitizeUsageAPIIdentifier (apiName )
311312 apiSnapshot := APISnapshot {
312313 TotalRequests : stats .TotalRequests ,
313314 TotalTokens : stats .TotalTokens ,
@@ -317,6 +318,9 @@ func (s *RequestStatistics) Snapshot() StatisticsSnapshot {
317318 for modelName , modelStatsValue := range stats .Models {
318319 requestDetails := make ([]RequestDetail , len (modelStatsValue .Details ))
319320 copy (requestDetails , modelStatsValue .Details )
321+ for i := range requestDetails {
322+ requestDetails [i ] = sanitizeRequestDetail (requestDetails [i ])
323+ }
320324 apiSnapshot .Models [modelName ] = ModelSnapshot {
321325 TotalRequests : modelStatsValue .TotalRequests ,
322326 TotalTokens : modelStatsValue .TotalTokens ,
@@ -386,6 +390,7 @@ func (s *RequestStatistics) RestoreSnapshot(snapshot StatisticsSnapshot) MergeRe
386390
387391func (s * RequestStatistics ) loadSnapshot (snapshot StatisticsSnapshot ) {
388392 for apiName , apiSnapshot := range snapshot .APIs {
393+ apiName = sanitizeUsageAPIIdentifier (apiName )
389394 stats := s .apiStatsForKey (apiName )
390395 var apiRequests int64
391396 var apiTokens int64
@@ -449,6 +454,7 @@ func normaliseRequestDetails(details []RequestDetail) ([]RequestDetail, detailAg
449454 if detail .Timestamp .IsZero () {
450455 detail .Timestamp = now
451456 }
457+ detail = sanitizeRequestDetail (detail )
452458 out = append (out , detail )
453459 totals .requests ++
454460 totals .tokens += detail .Tokens .TotalTokens
@@ -557,14 +563,127 @@ func normaliseUsageIdentifier(value string) string {
557563 return trimRunes (value , maxUsageIdentifierRunes )
558564}
559565
566+ func sanitizeRequestDetail (detail RequestDetail ) RequestDetail {
567+ detail .Source = sanitizeUsageDetailSource (detail .Source )
568+ detail .AuthIndex = sanitizeUsageDetailAuthIndex (detail .AuthIndex )
569+ return detail
570+ }
571+
572+ func sanitizeUsageAPIIdentifier (value string ) string {
573+ raw := strings .TrimSpace (value )
574+ normalized := normaliseUsageIdentifier (raw )
575+ if isSafeUsageIdentifier (normalized ) {
576+ return normalized
577+ }
578+ return "api-key:" + shortUsageHash (raw )
579+ }
580+
581+ func sanitizeUsageDetailSource (value string ) string {
582+ raw := strings .TrimSpace (value )
583+ normalized := normaliseUsageIdentifier (raw )
584+ if normalized == unknownUsageBucket || isSafeUsageIdentifier (normalized ) {
585+ return normalized
586+ }
587+ return "source:" + shortUsageHash (raw )
588+ }
589+
590+ func sanitizeUsageDetailAuthIndex (value string ) string {
591+ value = strings .TrimSpace (value )
592+ if value == "" {
593+ return ""
594+ }
595+ normalized := trimRunes (value , maxUsageIdentifierRunes )
596+ lower := strings .ToLower (normalized )
597+ if strings .HasPrefix (lower , "auth:" ) {
598+ hash := strings .TrimPrefix (lower , "auth:" )
599+ if isHexIdentifier (hash , 12 ) || isHexIdentifier (hash , 16 ) || isHexIdentifier (hash , 64 ) {
600+ return normalized
601+ }
602+ return "auth:" + shortUsageHash (value )
603+ }
604+ if isHexIdentifier (lower , 16 ) || isHexIdentifier (lower , 64 ) {
605+ return normalized
606+ }
607+ return "auth:" + shortUsageHash (value )
608+ }
609+
610+ func isSafeUsageIdentifier (value string ) bool {
611+ value = strings .TrimSpace (value )
612+ if value == "" {
613+ return true
614+ }
615+ lower := strings .ToLower (value )
616+ if lower == unknownUsageBucket || lower == overflowUsageBucket {
617+ return true
618+ }
619+ if strings .HasPrefix (lower , "api-key:" ) {
620+ hash := strings .TrimPrefix (lower , "api-key:" )
621+ return isHexIdentifier (hash , 8 ) || isHexIdentifier (hash , 12 ) || isHexIdentifier (hash , 64 )
622+ }
623+ if strings .HasPrefix (lower , "source:" ) {
624+ hash := strings .TrimPrefix (lower , "source:" )
625+ return isHexIdentifier (hash , 12 ) || isHexIdentifier (hash , 64 )
626+ }
627+ if isHTTPRoute (value ) || isSafePathIdentifier (value ) {
628+ return true
629+ }
630+ switch lower {
631+ case "gemini" , "gemini-cli" , "aistudio" , "vertex" , "claude" , "codex" , "openai" ,
632+ "openai-compatibility" , "openai-compatible" , "antigravity" , "github-copilot" ,
633+ "gitlab" , "cursor" , "kiro" , "kilo" , "kimi" , "iflow" , "codebuddy" , "local" :
634+ return true
635+ default :
636+ return false
637+ }
638+ }
639+
640+ func isSafePathIdentifier (value string ) bool {
641+ if ! strings .HasPrefix (value , "/" ) {
642+ return false
643+ }
644+ lower := strings .ToLower (value )
645+ return ! strings .Contains (value , "?" ) &&
646+ ! strings .Contains (lower , "key" ) &&
647+ ! strings .Contains (lower , "token" ) &&
648+ ! strings .Contains (lower , "auth" )
649+ }
650+
651+ func isHTTPRoute (value string ) bool {
652+ parts := strings .Fields (value )
653+ if len (parts ) != 2 || ! strings .HasPrefix (parts [1 ], "/" ) {
654+ return false
655+ }
656+ switch strings .ToUpper (parts [0 ]) {
657+ case "GET" , "POST" , "PUT" , "PATCH" , "DELETE" , "HEAD" , "OPTIONS" :
658+ return true
659+ default :
660+ return false
661+ }
662+ }
663+
664+ func isHexIdentifier (value string , length int ) bool {
665+ if len (value ) != length {
666+ return false
667+ }
668+ for _ , r := range value {
669+ if (r < '0' || r > '9' ) && (r < 'a' || r > 'f' ) {
670+ return false
671+ }
672+ }
673+ return true
674+ }
675+
560676func secretUsageBucket (value string ) string {
561677 value = strings .TrimSpace (value )
562678 if value == "" {
563679 return ""
564680 }
681+ return fmt .Sprintf ("api-key:%s" , shortUsageHash (value )[:8 ])
682+ }
683+
684+ func shortUsageHash (value string ) string {
565685 sum := sha256 .Sum256 ([]byte (value ))
566- hash := hex .EncodeToString (sum [:])[:8 ]
567- return fmt .Sprintf ("api-key:%s" , hash )
686+ return hex .EncodeToString (sum [:])[:12 ]
568687}
569688
570689func trimRunes (value string , maxRunes int ) string {
0 commit comments