Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
76 lines
2.5 KiB
Go
76 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// maxPersistedSessionIDLength bounds the persisted client session identifier to the
|
|
// usage_logs.session_id column width (VARCHAR(255)). Longer values are rejected so
|
|
// distinct identifiers can never alias through truncation.
|
|
const maxPersistedSessionIDLength = 255
|
|
|
|
// clientSessionIDHeaders extends the OpenAI-compatible sticky-session signals with
|
|
// native protocol identifiers that are safe to persist but must not alter OpenAI
|
|
// scheduling behavior.
|
|
var clientSessionIDHeaders = append(
|
|
append([]string(nil), explicitOpenAIHeaderSessionNames...),
|
|
claudeCodeSessionHeader,
|
|
)
|
|
|
|
// ExtractClientSessionID resolves the explicit client-provided session identifier from
|
|
// request headers for usage-log correlation and returns it sanitized. It is
|
|
// protocol-agnostic and shared by every gateway handler so all supported protocols
|
|
// record session_id through one seam. Returns "" when no valid identifier is present.
|
|
//
|
|
// This value feeds only usage_logs.session_id persistence. It does NOT affect sticky
|
|
// routing, account selection, request_id semantics, or upstream prompt caching, which
|
|
// keep their own (intentionally broader) session-signal resolution.
|
|
func ExtractClientSessionID(c *gin.Context) string {
|
|
if c == nil || c.Request == nil {
|
|
return ""
|
|
}
|
|
for _, header := range clientSessionIDHeaders {
|
|
if sessionID := sanitizeSessionID(c.GetHeader(header)); sessionID != "" {
|
|
return sessionID
|
|
}
|
|
}
|
|
if isGrokRequestContext(c) {
|
|
if sessionID := sanitizeSessionID(c.GetHeader(grokConversationIDHeader)); sessionID != "" {
|
|
return sessionID
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// sanitizeSessionID normalizes a raw client-supplied session identifier for safe
|
|
// persistence: it trims surrounding whitespace, rejects the value outright if it
|
|
// contains any control character (CR/LF/tab/NUL/…) so a log- or header-injection style
|
|
// payload cannot slip into stored correlation data, and rejects values longer than
|
|
// the DB column bound. Absent or invalid input yields "".
|
|
func sanitizeSessionID(raw string) string {
|
|
if !utf8.ValidString(raw) {
|
|
return ""
|
|
}
|
|
trimmed := strings.TrimSpace(raw)
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
count := 0
|
|
for _, r := range trimmed {
|
|
if r < 0x20 || r == 0x7f {
|
|
// An explicit correlation id never legitimately contains control
|
|
// characters; drop the whole value rather than persist a mangled or
|
|
// partially-injected identifier.
|
|
return ""
|
|
}
|
|
count++
|
|
if count > maxPersistedSessionIDLength {
|
|
return ""
|
|
}
|
|
}
|
|
return trimmed
|
|
}
|