From 29b901a8b3048ad27dda928a00ac0ff00bff9d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Mar 2026 11:00:05 +0800 Subject: [PATCH] fix(ccm): robust account UUID injection and session ID validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace bytes.Replace-based UUID injection with proper JSON unmarshal/re-marshal through map[string]json.RawMessage — the old approach silently failed when the body used non-canonical JSON escaping. Return 500 when metadata.user_id is present but in an unrecognized format, instead of silently passing through with an empty session ID. --- service/ccm/credential_default.go | 64 +++++++++++++++++++++++-------- service/ccm/service_handler.go | 28 ++++++++++---- 2 files changed, 69 insertions(+), 23 deletions(-) diff --git a/service/ccm/credential_default.go b/service/ccm/credential_default.go index 9c19a2cc9..57e0ca912 100644 --- a/service/ccm/credential_default.go +++ b/service/ccm/credential_default.go @@ -851,37 +851,71 @@ func (c *defaultCredential) injectAccountUUID(bodyBytes []byte) []byte { return bodyBytes } - var body struct { - Metadata struct { - UserID string `json:"user_id"` - } `json:"metadata"` + var body map[string]json.RawMessage + err := json.Unmarshal(bodyBytes, &body) + if err != nil { + return bodyBytes } - if json.Unmarshal(bodyBytes, &body) != nil || body.Metadata.UserID == "" { + metadataRaw, hasMetadata := body["metadata"] + if !hasMetadata { return bodyBytes } - var userIDObject map[string]any - if json.Unmarshal([]byte(body.Metadata.UserID), &userIDObject) != nil { + var metadata map[string]json.RawMessage + err = json.Unmarshal(metadataRaw, &metadata) + if err != nil { return bodyBytes } - existing, _ := userIDObject["account_uuid"].(string) - if existing != "" { + userIDRaw, hasUserID := metadata["user_id"] + if !hasUserID { return bodyBytes } - userIDObject["account_uuid"] = accountUUID - newUserID, err := json.Marshal(userIDObject) + + var userIDStr string + err = json.Unmarshal(userIDRaw, &userIDStr) + if err != nil || userIDStr == "" { + return bodyBytes + } + + var userIDObject map[string]json.RawMessage + err = json.Unmarshal([]byte(userIDStr), &userIDObject) if err != nil { return bodyBytes } - newUserIDStr := string(newUserID) - oldUserIDJSON, err := json.Marshal(body.Metadata.UserID) + existingRaw, hasExisting := userIDObject["account_uuid"] + if hasExisting { + var existing string + if json.Unmarshal(existingRaw, &existing) == nil && existing != "" { + return bodyBytes + } + } + + accountUUIDJSON, err := json.Marshal(accountUUID) if err != nil { return bodyBytes } - newUserIDJSON, err := json.Marshal(newUserIDStr) + userIDObject["account_uuid"] = json.RawMessage(accountUUIDJSON) + + newUserIDBytes, err := json.Marshal(userIDObject) if err != nil { return bodyBytes } - return bytes.Replace(bodyBytes, oldUserIDJSON, newUserIDJSON, 1) + newUserIDRaw, err := json.Marshal(string(newUserIDBytes)) + if err != nil { + return bodyBytes + } + metadata["user_id"] = json.RawMessage(newUserIDRaw) + + newMetadataBytes, err := json.Marshal(metadata) + if err != nil { + return bodyBytes + } + body["metadata"] = json.RawMessage(newMetadataBytes) + + newBodyBytes, err := json.Marshal(body) + if err != nil { + return bodyBytes + } + return newBodyBytes } diff --git a/service/ccm/service_handler.go b/service/ccm/service_handler.go index 07b0556d4..9ce78db53 100644 --- a/service/ccm/service_handler.go +++ b/service/ccm/service_handler.go @@ -82,15 +82,21 @@ func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint { // `user_${deviceId}_account_${accountUuid}_session_${sessionId}` // // ref: cli.js qs() — old metadata constructor -func extractCCMSessionID(bodyBytes []byte) string { +// +// Returns ("", nil) when body has no metadata.user_id (non-message endpoints). +// Returns error when user_id is present but in an unrecognized format. +func extractCCMSessionID(bodyBytes []byte) (string, error) { var body struct { - Metadata struct { + Metadata *struct { UserID string `json:"user_id"` } `json:"metadata"` } err := json.Unmarshal(bodyBytes, &body) if err != nil { - return "" + return "", nil + } + if body.Metadata == nil || body.Metadata.UserID == "" { + return "", nil } userID := body.Metadata.UserID @@ -99,15 +105,16 @@ func extractCCMSessionID(bodyBytes []byte) string { SessionID string `json:"session_id"` } if json.Unmarshal([]byte(userID), &userIDObject) == nil && userIDObject.SessionID != "" { - return userIDObject.SessionID + return userIDObject.SessionID, nil } // legacy template literal format sessionIndex := strings.LastIndex(userID, "_session_") - if sessionIndex < 0 { - return "" + if sessionIndex >= 0 { + return userID[sessionIndex+len("_session_"):], nil } - return userID[sessionIndex+len("_session_"):] + + return "", E.New("unrecognized metadata.user_id format: ", userID) } func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -181,7 +188,12 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { messagesCount = len(request.Messages) } - sessionID = extractCCMSessionID(bodyBytes) + sessionID, err = extractCCMSessionID(bodyBytes) + if err != nil { + s.logger.ErrorContext(ctx, "invalid metadata format: ", err) + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "invalid metadata format") + return + } r.Body = io.NopCloser(bytes.NewReader(bodyBytes)) }