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

  1. Sanitize the ID at its origin: strip or reject control characters before the token/parameter is produced.
  2. Have the client send the ID percent-encoded or as a clean string and decode properly server-side.
  3. If legacy IDs contain control chars, migrate them to sanitized equivalents in the external user store.
  4. 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

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

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/5a55505e71525943. Report an issue: GitHub.