Tencent/WeKnora · error
external user id contains invalid characters
Error message
external user id contains invalid characters
What it means
validateExternalUserID enforces a character whitelist on external user IDs: any rune below 0x20 (control characters) or 0x7f (DEL) causes rejection. This prevents control characters from corrupting storage, logs, and downstream lookups.
Source
Thrown at internal/middleware/auth.go:669
sub, _ := claims["sub"].(string)
sub = strings.TrimSpace(sub)
if sub == "" {
return "", errors.New("missing subject")
}
return sub, nil
}
func validateExternalUserID(id string) error {
id = strings.TrimSpace(id)
if id == "" {
return errors.New("empty external user id")
}
if len(id) > maxExternalUserIDLen {
return fmt.Errorf("external user id too long (max %d)", maxExternalUserIDLen)
}
for _, r := range id {
if r < 0x20 || r == 0x7f {
return errors.New("external user id contains invalid characters")
}
}
return nil
}
func apiPrincipalAuthErrorMessage(err error) string {
switch {
case errors.Is(err, errMissingDirectHeader):
return "Unauthorized: missing external user id header"
case errors.Is(err, errInvalidExternalUserID):
return "Unauthorized: invalid external user id"
case errors.Is(err, errInvalidExternalUserToken):
return "Unauthorized: invalid external user token"
default:
return "Unauthorized: invalid external user token"
}
}
View on GitHub (pinned to 988cbb0330)
Solutions
- Sanitize the ID at its origin: strip or reject control characters before the token/parameter is produced.
- Have the client send the ID percent-encoded or as a clean string and decode properly server-side.
- If legacy IDs contain control chars, migrate them to sanitized equivalents in the external user store.
- For debugging, hex-encode the failing ID to see exactly which control rune is present.
Example fix
// before: passing raw header value through
id := c.GetHeader("X-External-User-Id")
// after: strip control characters before validation
id := strings.Map(func(r rune) rune {
if r < 0x20 || r == 0x7f {
return -1
}
return r
}, c.GetHeader("X-External-User-Id")) Defensive patterns
Strategy: validation
Validate before calling
func sanitizeID(id string) string {
return strings.Map(func(r rune) rune {
if r < 0x20 || r == 0x7f { return -1 }
return r
}, strings.TrimSpace(id))
}
// apply before sending the ID Type guard
func isPrintableID(id string) bool {
for _, r := range id {
if r < 0x20 || r == 0x7f { return false }
}
return id != ""
} Prevention
- Whitelist-encode IDs (e.g. [A-Za-z0-9._-]) at generation time.
- Never build IDs by concatenating raw bytes or binary output.
- Log failing IDs hex-encoded to identify the offending rune quickly.
When it happens
Trigger: resolveAPIPrincipal path where the external user ID (from the JWT sub or another source) contains control bytes — e.g. embedded newlines, tabs (allowed? no: \t=0x09 < 0x20 is rejected), null bytes, or ANSI escape sequences.
Common situations: IDs copy-pasted from terminals or binary sources containing escape sequences; payloads built by concatenating raw bytes; malicious clients probing for log-injection or storage anomalies.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- missing subject
- empty external user id
- folder name %q must not contain a path separator
- invite code has expired
- join request not found
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/5a55505e71525943.
Report an issue: GitHub.