{"record":{"id":"bec871dc2eb34a0e","repo":"Tencent/WeKnora","slug":"embed-visitor-id-contains-invalid-characters","errorCode":null,"errorMessage":"embed visitor id contains invalid characters","messagePattern":"embed visitor id contains invalid characters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/types/principal.go","lineNumber":136,"sourceCode":"func EmbedVisitorPrincipal(tenantID uint64, channelID, visitorID string) Principal {\n\treturn Principal{\n\t\tType: PrincipalEmbedVisitor,\n\t\tID:   fmt.Sprintf(\"%d:%s:%s\", tenantID, strings.TrimSpace(channelID), strings.TrimSpace(visitorID)),\n\t}\n}\n\n// ValidateEmbedVisitorID checks the client-supplied anonymous visitor id.\nfunc ValidateEmbedVisitorID(id string) error {\n\tid = strings.TrimSpace(id)\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"empty embed visitor id\")\n\t}\n\tif len(id) > 128 {\n\t\treturn fmt.Errorf(\"embed visitor id too long (max 128)\")\n\t}\n\tfor _, r := range id {\n\t\tif r < 0x20 || r == 0x7f {\n\t\t\treturn fmt.Errorf(\"embed visitor id contains invalid characters\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// MCPOAuthPrincipalFromContext resolves the OAuth token principal for ctx.\n// Embed chat sessions map to a per-visitor principal when X-Embed-Visitor is\n// present; otherwise OAuth falls back to the chat session principal.\nfunc MCPOAuthPrincipalFromContext(ctx context.Context) Principal {\n\tp, ok := PrincipalFromContext(ctx)\n\tif !ok {\n\t\treturn Principal{}\n\t}\n\tp = p.Normalize()\n\tif p.Type != PrincipalEmbedSession {\n\t\treturn p\n\t}\n\tvisitorID := EmbedVisitorIDFromContext(ctx)","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/types/principal.go#L118-L154","documentation":"ValidateEmbedVisitorID rejects visitor ids containing control characters: any rune below 0x20 or the DEL character 0x7f. This prevents header injection and garbled storage of anonymous visitor identifiers.","triggerScenarios":"Calling ensureEmbedSession with a visitor id containing newlines, tabs, NUL bytes, or other control characters (or DEL), typically from unparsed raw input or binary-derived values.","commonSituations":"User-supplied ids passed through unvalidated, ids read from binary sources or corrupted storage, copy-paste including invisible control characters, or injection attempts against the embed endpoint.","solutions":["Sanitize the id on the client: strip or reject control characters before sending","Generate ids programmatically (UUID/hex) so control characters cannot appear","Replace the id with a freshly generated safe value if it came from untrusted input","Log/reject at the edge if the input looks like an injection attempt"],"exampleFix":"// before\nvisitorID := rawUserInput // may contain \\n or \\x00\n// after\nif strings.ContainsFunc(visitorID, func(r rune) bool { return r < 0x20 || r == 0x7f }) {\n    visitorID = uuid.NewString()\n}","handlingStrategy":"validation","validationCode":"func hasControlChars(s string) bool {\n    for _, r := range s { if r < 0x20 || r == 0x7f { return true } }\n    return false\n}\nif hasControlChars(visitorID) { visitorID = uuid.NewString() }","typeGuard":"func isSafeVisitorID(id string) bool {\n    if strings.TrimSpace(id) == \"\" || len(id) > 128 { return false }\n    for _, r := range id { if r < 0x20 || r == 0x7f { return false } }\n    return true\n}","tryCatchPattern":"if err := ValidateEmbedVisitorID(id); err != nil {\n    if strings.Contains(err.Error(), \"invalid characters\") {\n        id = sanitizeOrRegenerate(id)\n    }\n}","preventionTips":["Generate ids programmatically (UUID/hex) so control chars are impossible","Sanitize any id derived from user or binary input before sending","Treat validation failure on untrusted input as a potential injection attempt and log it"],"tags":["validation","embed","security","go"],"backgroundTag":"invalid-characters","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}